Developing Android Apps with Python using Kivy

In this post we are going to cover how to develop a Natural User Interface (NUI) application using Kivy, a Python cross platform library, but more specifically we are going to use KivyMD, which is a collection of Material Design compliant widgets that can be use inside Kivy, making our app suitable for mobile devices and Android systems.

logo

What MD stands for?

MD is short for Material Design, and Material Design is a set of standards made by google to specify how to build user interfaces for modern applications.

Based on these specifications several libraries have been create in several languages, and KivyMD is one of these libraries.

Getting Started

The first thing we have to do in order to start working with KivyMD, is to install this library using our terminal.

Dependencies Installation

pip3 install kivymd

Some tutorials recommend you install the Kivy framework first, but since Kivy is one on the installation requirements for KivyMD, it is not necessary, during the installation of KivyMD, Kivy is going to be installed as well.

We also recommend you use a virtual environment to follow this post. We have a post about virtual environments.

Base code

We are going to start using a base code that has some relevant imports to define our application widgets.

from kivymd.uix.screen import MDScreen
from kivymd.app import MDApp
from kivy.uix.image import Image
from kivymd.uix.button import MDFillRoundFlatButton
from kivymd.uix.textfield import MDTextField
from kivymd.uix.label import MDLabel
from kivymd.uix.toolbar import MDToolbar


class ConverterApp(MDApp):

    # callbacks

    def build(self):

        screen = MDScreen()

        # fill in

        return screen


if __name__ == '__main__':

    ConverterApp().run()

This base code contains the class imports from the KivyMD library to define widgets such as text fields, labels and buttons, among others. We have also defined a ConvertApp class which inherits from MDApp, this way we can extend the behavior of a KivyMD GUI.

At first instance, we are going to focus on the build method, in here we are going to define the firsts widget we want to add to our application. Then we will add custom methods to this class to define the interactions between the user and the application. We are going to save this code as main.py in our file system.

Running our App

In order to run our app we just have to run this Python script.

python3 main.py

A new blank and empy screen will be displayed.

Application Description

For demonstration purposes our application is going to be a small and fast to code temperature converter, we will develop a tool to convert from celsius degrees to fahrenheit degrees and vice-versa. We will and the minimum elements to crete an appealing application.

Inserting our Widgets

The process to define and insert widgets into our empty application is the following.

  1. Define inside the build method an instance of a widget class (MDTextField, MDLabel, MDToolbar, etc..).
  2. Pass setting parameters in the previous definition.
  3. Allocate the definition as an instance attribute in the ConverterApp class.
  4. Bind the screen object with defined widgets using the add_widget method.

Let's see how we can follow this recipe by coding some widgets into our converter application.

Toolbar

One of the most common widgets found in applications is a toolbar in the header of the screen, in this toolbar you can find the title for the app and buttons for some interactions. We are going to add a toolbar with a title and a custom action button.

Following the previous recipe we will have the following.

# toolbar
self.toolbar = MDToolbar(title="Convertio (Celsius to Fahrenheit)")
self.toolbar.pos_hint = {"top": 1}
self.toolbar.right_action_items = [["rotate-3d-variant", lambda x: self.flip()]]
screen.add_widget(self.toolbar)

We can also tune up some attributes after the widget definition, like the position in the screen. We also added a right action button using the rotate-3d-variant icon and a callback to the method flip whe a user taps on this icon.

if we run now this application we will see the following.

toolbar

Our application is taking shape, let's add now a logo image.

self.logo = Image(source="logo.png", pos_hint={"center_x": 0.5, "center_y": 0.7})
screen.add_widget(self.logo)

In this case we defined the post_hint attribute in the Image instantiation, all attributes that can defined in instantiation can also be set after.

Another thing to point out is that we can define the position of our widget using position indexes, this was the case for the toolbar, but we can also define the position relative to the screen size using the attributes center_x and center_y in ratio values.

Input

Now we are going to define a text field for temperature input.

self._input = MDTextField(
    hint_text="Enter temperature in Celsius",
    halign="center",
    size_hint=(0.8, 1),
    pos_hint={"center_x": 0.5, "center_y": 0.5},
    font_size=22
)
screen.add_widget(self._input)

In this case we added extra settings, like horizontal alignment and font size. If we run this application we will have the following.

input

Labels

Now to show the output of the temperature conversion we are going to add some labels.

self.label = MDLabel(
    halign="center",
    pos_hint={"center_x": 0.5, "center_y": 0.35},
    theme_text_color="Secondary"
)

self.converted_label = MDLabel(
    halign="center",
    pos_hint={"center_x": 0.5, "center_y": 0.3},
    theme_text_color="Primary",
    font_style="H5"
)

screen.add_widget(self.label)
screen.add_widget(self.converted_label)

For these labels we added additional setting for visual style, since this is a library based on the google material design specifications, it uses similar naming suchas Primary and Secondary.

We are going to update these labels with the computer converted temperature setting the text attribute.

Convert Button

And finally, we are going to add the last widget which is a button to perform the conversion of the inputted temperature.

self.convert_button = MDFillRoundFlatButton(
            text="CONVERT",
            font_size=17,
            pos_hint={"center_x": 0.5, "center_y": 0.15},
            on_press=self.convert
        )

        screen.add_widget(self.convert_button)

The most relevant attribute to set in here is the on_press attribute, we have to assign to this attribute a callback function, which is going to be an instance method defined as convert.

But in order for this to work, we are going to add the method signature for this convert method with a pass statement in the callbacks section of the code.

class ConverterApp(MDApp):

    # callbacks
    def convert(self):

        pass

If we now run this application we will have the following.

full

State attribute

For our ConvertApp we are going to add a state variable to track the state of direction of the conversion, as default we want to convert for celsius degrees to fahrenheit degrees, but we can flip this direction by clicking or tapping in the toolbar button. This state variable is defined at the beginning of the build method.

self.ctof = True

A value set to True on this instance attribute means default conversion and a False value as the opposite direction for conversion.

Callbacks

Now that we have all the widgets required to display in our application, we are going to code the interaction with the user, we will define some callback functions to be executed. These functions are called callback because they are invoked upon an event on the application, such as on_press event. Every widget has its own set of possible events and we can find a full list of available widgets and their events in the official KivyMD components documentation.

Flip callback

We want to change the direction of the conversion upon clicking or tapping on the toolbar button.

def flip(self):

    self.ctof = not self.ctof

    self.converted_label.text = ""
    self._input.text = ""

    if self.ctof:
        self.toolbar.text = "Convertio (Celsius to Fahrenheit)"
        self._input.hint_text = "Enter temperature in Celsius"
    else:
        self.toolbar.text = "Convertio (Fahrenheit to Celsius)"
        self._input.hint_text = "Enter temperature in Fahrenheit"

This method not only changes the value for the ctof attribute, it also updates the toolbar text and the hint text for the input widget.

Convert callback

For our application we want convert the input temperature and display this conversion in the output labels upon the click or tap on the convert button.

We have already assigned the callback function in the button definition, let's now code the internals of this function.

def convert(self, args):

    if self.ctof:
        celsius = float(self._input.text)

        fahrenheit = celsius * (9 / 5) + 32

        self.converted_label.text = str(round(fahrenheit, 2))

        self.label.text = "In Fahrenheit is:"
    else:
        fahrenheit = float(self._input.text)

        celsius = (fahrenheit - 32) * (5 / 9)

        self.converted_label.text = str(round(celsius, 2))

        self.label.text = "In Celsius is:"

Finally we will perform the temperature conversion, base on the value of ctof we wil do one conversion or the other. Converting the input text value into a float value, applying the respective formula and finally updating the output labels.

Another final touch to improve the look and feel of our application we are going to change the default color theme. We are going the set this in our build method.

self.theme_cls.primary_palette = "Indigo"

if we run now our application we are gonna have the following.

indigo

Creating Android Package

The final step is to create an android package of our application, this process is quite extensive and we are going to cover it in a future post, for now we will provide you with the Kivy documentation page on how to package a Kivy application, but we want to mention that the ways to do this packaging is only available in Linux systems.

Complete code

This is the complete code for this post.

from kivymd.uix.screen import MDScreen
from kivymd.app import MDApp
from kivy.uix.image import Image
from kivymd.uix.button import MDFillRoundFlatButton
from kivymd.uix.textfield import MDTextField
from kivymd.uix.label import MDLabel
from kivymd.uix.toolbar import MDToolbar


class ConverterApp(MDApp):

    def flip(self):

        self.ctof = not self.ctof

        self.converted_label.text = ""
        self._input.text = ""

        if self.ctof:
            self.toolbar.text = "Convertio (Celsius to Fahrenheit)"
            self._input.hint_text = "Enter temperature in Celsius"
        else:
            self.toolbar.text = "Convertio (Fahrenheit to Celsius)"
            self._input.hint_text = "Enter temperature in Fahrenheit"

    def convert(self, args):

        if self.ctof:
            celsius = float(self._input.text)

            fahrenheit = celsius * (9 / 5) + 32

            self.converted_label.text = str(round(fahrenheit, 2))

            self.label.text = "In Fahrenheit is:"
        else:
            fahrenheit = float(self._input.text)

            celsius = (fahrenheit - 32) * (5 / 9)

            self.converted_label.text = str(round(celsius, 2))

            self.label.text = "In Celsius is:"

    def build(self):

        screen = MDScreen()

        # state
        self.ctof = True
        self.theme_cls.primary_palette = "Indigo"

        # toolbar
        self.toolbar = MDToolbar(title="Convertio (Celsius to Fahrenheit)")
        self.toolbar.pos_hint = {"top": 1}
        self.toolbar.right_action_items = [["rotate-3d-variant", lambda x: self.flip()]]
        screen.add_widget(self.toolbar)

        # logo

        self.logo = Image(source="logo.png", pos_hint={"center_x": 0.5, "center_y": 0.7})
        screen.add_widget(self.logo)

        # input

        self._input = MDTextField(
            hint_text="Enter temperature in Celsius",
            halign="center",
            size_hint=(0.8, 1),
            pos_hint={"center_x": 0.5, "center_y": 0.5},
            font_size=22
        )
        screen.add_widget(self._input)

        # labels

        self.label = MDLabel(
            halign="center",
            pos_hint={"center_x": 0.5, "center_y": 0.35},
            theme_text_color="Secondary"
        )

        self.converted_label = MDLabel(
            halign="center",
            pos_hint={"center_x": 0.5, "center_y": 0.3},
            theme_text_color="Primary",
            font_style="H5"
        )

        screen.add_widget(self.label)
        screen.add_widget(self.converted_label)

        # convert button

        self.convert_button = MDFillRoundFlatButton(
            text="CONVERT",
            font_size=17,
            pos_hint={"center_x": 0.5, "center_y": 0.15},
            on_press=self.convert
        )

        screen.add_widget(self.convert_button)

        return screen


if __name__ == '__main__':

    ConverterApp().run()

Conclusion

Python is another language that you can use to develop Android application, and deliver really good looking apps, this is possible using Kivy and KivyMD and if you are used to develop GUI application using other libraries like Tkinter or Qt in Python you will find this library really useful, thanks for coming by this post and give us your thoughts.

0 Comments

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel