Kivy tutorial 001: Say Hello

Central themes: Starting an App, getting Kivy running

It’s compulsory that the introduction to any programming project should contain a "Hello World!" application. Since Kivy is a GUI framework, that means starting an application and displaying the words on the screen. Start by adding each of the following lines to your program:

from kivy.app import App

This imports the App class, something you’ll be using in any Kivy application. Your instance of this class will create the Kivy window and serve as the top level of your application

from kivy.uix.label import Label

This introduces one of Kivy’s most important components; the Widget. Your entire application will be built with Widgets, each of which does a single (relatively) small task. For instance, Label is a Widget that displays some text, Button is (obviously) a button, and Layouts are Widgets that contain other Widgets and lay them out in some particular arrangement.

You can find the documentation for Label here. We’ll need this later!.

In every Kivy application, your first task is to create an App subclass as follows:

class YourApp(App):
    def build(self):
    root_widget = Label(text='Hello world!')
    return root_widget

The build method is the only important addition you have to make, and is the usually the first entry point for your use of a Kivy application. This method must instantiate and return what will be your root widget, the top level widget of your Kivy application, which will contain anything else you add.

The root widget will automatically fill the window, so in this case the Label text will appear right in the middle.

In our case, the application will only ever need a single Widget; the Label displaying our text. We set the text by simply passing it as an argument. This works automatically because text is a Kivy property of the Label widget…but that doesn’t matter right now.

Finally, add a line to start and run the Application:

YourApp().run()

This instantiates and runs the instance of your App. Any Kivy application is created and started with some variation of these basic steps.

Now…run the code!

python your_filename.py

You should see a Window something like the following image. Congratulations, you’ve written and run your first Kivy application.

Hello world application

Full code

your_filename.py:

from kivy.app import App
from kivy.uix.label import Label

class YourApp(App):
    def build(self):
    root_widget = Label(text='Hello world!')
    return root_widget

YourApp().run()

Kivy tutorial 001: Say Hello

This is number 1 in a series of introductory Kivy tutorials.

Central themes: Starting an App, getting Kivy running

It’s essentially compulsory that the introduction to any programming project should be a “Hello World!” application. Since Kivy is a GUI framework, that means opening a window and displaying the words on the screen. Start by adding each of the following lines to your program:

from kivy.app import App

This imports the App class, which you’ll use as the core object of any Kivy application. Your instance of this class will create the Kivy window and serve as the top level of your application

from kivy.uix.label import Label

This next import introduces one of Kivy’s most important components; the Widget. Your entire application will be built with Widgets, each of which does a single (relatively) small task. For instance, Label is a Widget that displays some text, Button is (obviously) a button, and Layouts are Widgets that contain other Widgets and control their positions according to certain rules.

You can find the documentation for Label here. We’ll need this later!.

In every Kivy application, your first task is to create an App subclass as follows:

class YourApp(App):
    def build(self):
        root_widget = Label(text='Hello world!')
        return root_widget

The build method is the only important addition you have to make, and is your application’s entry point. This method must instantiate and return what will be your root widget, the top level widget of your Kivy application, which will in turn contain all your other gui objects.

The root widget will automatically fill the window, so in this case the Label text will appear right in the middle.

In our case, the application only needs a single Widget; the Label displaying our text. We set the text by simply passing it as an argument. This works automatically because text is a Kivy property of the Label widget…more on that later.

Finally, add a line of code to start the app:

YourApp().run()

This instantiates and runs the instance of your App. Any Kivy application is created and started with some variation of these basic steps.

Now…run the code!

python your_filename.py

You should see a Window something like the following image. Congratulations, you’ve written and run your first Kivy application.

Hello world application

Next tutorial: Improving appearances, customising widgets using Kivy Properties

Full code

your_filename.py:

from kivy.app import App
from kivy.uix.label import Label

class YourApp(App):
    def build(self):
      root_widget = Label(text='Hello world!')
      return root_widget

YourApp().run()

python-for-android 2019.08.09 released: running under Python 2 no longer supported

python-for-android is a packaging tool for Python apps on Android. You can create your own Python distribution including the modules and dependencies you want, and bundle it in an APK along with your own code.


python-for-android 2019.08.09 has just been released! I haven’t been making a blog post for every new version now that we’ve switched to a monthly releases, but this one includes an especially major change: python-for-android no longer runs under Python 2. If this affects you, simply run python-for-android (or buildozer) under Python 3.6 or newer, they should run exactly the same as ever.

Note that you can still target Python 2 on Android, simply add python2 to your requirements as you would normally. However, this won’t last forever, support for Python 2 will probably be totally removed early in 2020.

Other changes in this release include further testing improvements, significantly improved documentation on developing with python-for-android, and some preparations for further big changes in the future. See the release note for full details.

python-for-android 2019.08.09 released: running under Python 2 no longer supported

python-for-android is a packaging tool for Python apps on Android. You can create your own Python distribution including the modules and dependencies you want, and bundle it in an APK along with your own code.


python-for-android 2019.08.09 has just been released! I haven’t been making a blog post for every new version now that we’ve switched to a monthly releases, but this one includes an especially major change: python-for-android no longer runs under Python 2. If this affects you, simply run python-for-android (or buildozer) under Python 3.6 or newer, they should run exactly the same as ever.

Note that you can still target Python 2 on Android, simply add python2 to your requirements as you would normally. However, this won’t last forever, support for Python 2 will probably be totally removed early in 2020.

Other changes in this release include further testing improvements, significantly improved documentation on developing with python-for-android, and some preparations for further big changes in the future. See the release note for full details.

A delayed resize layout in Kivy

A user on the Kivy Discord just raised the question of how to delay widget updates during resize events. The problem was that the widgets did some heavy processing (generating matplotlib graphs) that would be very slow if called for every tiny update during a larger resize event.

This is a good opportunity to return to the flexibility of Kivy layouts. It’s very easy to add some simple behaviour that delays updates until a short period has passed without the size changing. Here’s a quick implementation I threw together:

from kivy.uix.anchorlayout import AnchorLayout
from kivy.clock import Clock
from kivy.properties import ObjectProperty, NumericProperty

from functools import partial

class DelayedResizeLayout(AnchorLayout):

    do_layout_event = ObjectProperty(None, allownone=True)

    layout_delay_s = NumericProperty(0.2)

    def do_layout(self, *args, **kwargs):
        if self.do_layout_event is not None:
            self.do_layout_event.cancel()
        real_do_layout = super().do_layout
        self.do_layout_event = Clock.schedule_once(
            lambda dt: real_do_layout(*args, **kwargs),
            self.layout_delay_s)

This layout could be used as the root widget of a whole application, to delay the resizing of all the content, or somewhere within the app to delay only a small part of it.

And a simple example:

from kivy.uix.button import Button
from kivy.base import runTouchApp

button = Button(text='example button')
layout = DelayedResizeLayout()
layout.add_widget(button)
runTouchApp(layout)

A delayed resize layout in Kivy

A user on the Kivy Discord just raised the question of how to delay widget updates during resize events. The problem was that the widgets did some heavy processing (generating matplotlib graphs) that would be very slow if called for every tiny update during a larger resize event.

This is a good opportunity to return to the flexibility of Kivy layouts. It’s very easy to add some simple behaviour that delays updates until a short period has passed without the size changing. Here’s a quick implementation I threw together:

from kivy.uix.anchorlayout import AnchorLayout
from kivy.clock import Clock
from kivy.properties import ObjectProperty, NumericProperty

from functools import partial

class DelayedResizeLayout(AnchorLayout):

    do_layout_event = ObjectProperty(None, allownone=True)

    layout_delay_s = NumericProperty(0.2)

    def do_layout(self, *args, **kwargs):
        if self.do_layout_event is not None:
            self.do_layout_event.cancel()
        real_do_layout = super().do_layout
        self.do_layout_event = Clock.schedule_once(
            lambda dt: real_do_layout(*args, **kwargs),
            self.layout_delay_s)

This layout could be used as the root widget of a whole application, to delay the resizing of all the content, or somewhere within the app to delay only a small part of it.

And a simple example:

from kivy.uix.button import Button
from kivy.base import runTouchApp

button = Button(text='example button')
layout = DelayedResizeLayout()
layout.add_widget(button)
runTouchApp(layout)

Widget interactions between Python and Kv

One of the biggest Kivy confusions I see is how different widgets can access one another when they’re in different parts of the application. Fortunately, it’s generally straightforward to do so. This post gives examples of methods you might use in different situations.

The emphasis here is on what you can do, not what you should. If you aren’t sure which way is best in a given situation, go ahead and choose what seems best at the time, but don’t be afraid to revisit it later.

How can a widget access its parent?

Use the parent property of the widget:

child = Widget()
parent = Widget()

assert child.parent is None  # child widget isn't added to any parent

parent.add_widget(child)

assert child.parent == parent

How can a widget access its children?

Use the children property. This is a list containing all the children you added.

child = Widget()
parent = Widget()

assert len(parent.children) == 0  # no children added to parent

parent.add_widget(child)

print(parent.children)  # [<kivy.uix.widget.Widget object at 0x???>]
assert child in parent.children

Note: The children list is actually backwards, i.e. the last widget you add will by default be the first in the list. This is a bit surprising, and only really happens for backwards compatibility.

How can a widget in Python access children from its kv rule?

Option 1: ids

You can give the widgets in your kv rule ids to access them from Python.

# main.py

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.app import App

class KvRuleWidget(BoxLayout):
    def on_touch_down(self, touch):
        print('We can get references to all the children using the ids dict.')

        # syntax is `self.ids.<id_text>`

        assert self.ids.middle in self.children
        assert self.ids.bottom in self.children

        # widgets can be accessed from deep in the kv rule
        assert self.ids.top_left not in self.children
        assert self.ids.top_right not in self.children

class ExampleApp(App):
    def build(self):
        return KvRuleWidget()
# example.kv

<KvRuleWidget>:
    orientation: 'vertical'
    BoxLayout:
        orientation: 'horizontal'
        Label:
            id: top_left
            text: 'top left'
        Label:
            id: top_right
            text: 'top right'
    Label:
        id: middle
        text: 'middle'
    Label:
        id: bottom
        text: 'bottom'

Note: You cannot set up widget ids from Python code, if you write e.g. w = Widget(id='some_name') this will not crash but the id will not be available in any ids dictionary.

Option 2: properties

You can use Kivy properties to pass around references to widgets.

# main.py

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.app import App
from kivy.properties import ObjectProperty

class KvRuleWidget(BoxLayout):
    top_right_label = ObjectProperty()

    def on_touch_down(self, touch):
        print('The top right label is {}'.format(self.top_right_label))

class ExampleApp(App):
    def build(self):
        return KvRuleWidget()
# example.kv

<KvRuleWidget>:
    orientation: 'vertical'
    top_right_label: top_right  # note that we used an id to set the property
    BoxLayout:
        orientation: 'horizontal'
        Label:
            id: top_right
            text: 'top left'
        Label:
            text: 'top right'
    Label:
        text: 'middle'
    Label:
        text: 'bottom'

Option 3: The parent and children properties

It is possible to walk through the widget tree using the parent and children properties.

This is usually a bad idea and is prone to breakage if the structure of the widget tree changes. However, it’s still possible.

# main.py

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.app import App
from kivy.properties import ObjectProperty

class KvRuleWidget(BoxLayout):
    def on_touch_down(self, touch):

        # get a reference to the top right label only by walking through the widget tree
        top_right_label = self.children[-1].children[0]

        print('The top right label is {}'.format(self.top_right_label))

class ExampleApp(App):
    def build(self):
        return KvRuleWidget()
# example.kv

# note: this time there are no ids at all
<KvRuleWidget>:
    orientation: 'vertical'
    BoxLayout:
        orientation: 'horizontal'
        Label:
            text: 'top left'
        Label:
            text: 'top right'
    Label:
        text: 'middle'
    Label:
        text: 'bottom'

How can a widget in Kv access children defined in Python?

Sometimes you might have some children defined via a Kv rule, and others created dynamically in Python. You can access the Python widgets in kv by saving references to them in Kivy properties:

# main.py

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.app import App
from kivy.properties import ObjectProperty

class KvRuleWidget(BoxLayout):
    label_created_in_python = ObjectProperty()

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        # add a widget from python code
        label = Label(text='label created in Python')
        self.add_widget(label)
        self.label_created_in_python = label  # save a reference

class ExampleApp(App):
    def build(self):
        return KvRuleWidget()
# example.kv

<KvRuleWidget>:
    orientation: 'vertical'
    Label:
        text: 'label created in Kv'
    Label:
        text: 'the label created in Python has text "{}"'.format(root.label_created_in_python.text)

How can a widget defined in a kv rule access a widget defined in another kv rule?

Sometimes you might have two widgets in very different places that need to talk to one another somehow. Usually the best way to achieve this is to consider how they are related to one another, and pass information between them via their common relations.

Also see the next Section for how to access any widget from anywhere, without worrying about how the widgets are related. However, that usually isn’t such a good choice in the long run.

The following example is deliberately very simple, but the same principles can be used to link together widgets across your whole program using references passed around where the kv rules meet.

# main.py

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.app import App
from kivy.properties import ObjectProperty

class IncrementCounterButton(Button):
    counter = NumericProperty(0)
    def on_press(self):
        self.counter += 1

class CounterLabel(Label):
    counter = NumericProperty(0)

class RootWidget(BoxLayout):
    pass

class ExampleApp(App):
    def build(self):
        return RootWidget()
# example.kv

<IncrementCounterButton>:
    text: 'press me'

<CounterLabel>:
    text: 'the counter value is {}'.format(app.counter)  # `app` in kv is equivalent to `App.get_running_app()` in Python

<RootWidget>:
    orientation: 'vertical'
    CounterLabel:
        counter: button.counter  # this means the CounterLabel's counter will always match the button's counter
    IncrementCounterButton:
        id: button

How can any widget access any other widget from anywhere?

Sometimes you really do want widgets to interact with one another without any good relationship between them. You can do this in a convenient way by using a Kivy property in the App class.

Note: This is notionally similar to using a global variable, and is often bad practice for all the same reasons.

The following example is quite contrived to keep it simple. In this case you could probably think of a better way to do the same thing, perhaps using the methods from the previous Sections.

# main.py

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.app import App
from kivy.properties import ObjectProperty

class IncrementCounterButton(Button):
    def on_press(self):
        # You can always access your App class from Python as follows:
        App.get_running_app().counter += 1

class CounterLabel(Label):
    pass

class ExampleApp(App):
    counter = NumericProperty(0)

    def build(self):
        boxlayout = BoxLayout(orientation='vertical')
        label = CounterLabel()
        button = IncrementCounterButton()

        boxlayout.add_widget(label)
        boxlayout.add_widget(button)

        return boxlayout
# example.kv

<IncrementCounterButton>:
    text: 'press me'

<CounterLabel>:
    text: 'the counter value is {}'.format(app.counter)  # `app` in kv is equivalent to `App.get_running_app()` in Python

Widget interactions between Python and Kv

One of the biggest Kivy confusions I see is how different widgets can access one another when they’re in different parts of the application. Fortunately, it’s generally straightforward to do so. This post gives examples of methods you might use in different situations.

The emphasis here is on what you can do, not what you should. If you aren’t sure which way is best in a given situation, go ahead and choose what seems best at the time, but don’t be afraid to revisit it later.

How can a widget access its parent?

Use the parent property of the widget:

child = Widget()
parent = Widget()

assert child.parent is None  # child widget isn't added to any parent

parent.add_widget(child)

assert child.parent == parent

How can a widget access its children?

Use the children property. This is a list containing all the children you added.

child = Widget()
parent = Widget()

assert len(parent.children) == 0  # no children added to parent

parent.add_widget(child)

print(parent.children)  # [<kivy.uix.widget.Widget object at 0x???>]
assert child in parent.children

Note: The children list is actually backwards, i.e. the last widget you add will by default be the first in the list. This is a bit surprising, and only really happens for backwards compatibility.

How can a widget in Python access children from its kv rule?

Option 1: ids

You can give the widgets in your kv rule ids to access them from Python.

# main.py

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.app import App

class KvRuleWidget(BoxLayout):
    def on_touch_down(self, touch):
        print('We can get references to all the children using the ids dict.')

        # syntax is `self.ids.<id_text>`

        assert self.ids.middle in self.children
        assert self.ids.bottom in self.children

        # widgets can be accessed from deep in the kv rule
        assert self.ids.top_left not in self.children
        assert self.ids.top_right not in self.children

class ExampleApp(App):
    def build(self):
        return KvRuleWidget()
# example.kv

<KvRuleWidget>:
    orientation: 'vertical'
    BoxLayout:
        orientation: 'horizontal'
        Label:
            id: top_left
            text: 'top left'
        Label:
            id: top_right
            text: 'top right'
    Label:
        id: middle
        text: 'middle'
    Label:
        id: bottom
        text: 'bottom'

Note: You cannot set up widget ids from Python code, if you write e.g. w = Widget(id='some_name') this will not crash but the id will not be available in any ids dictionary.

Option 2: properties

You can use Kivy properties to pass around references to widgets.

# main.py

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.app import App
from kivy.properties import ObjectProperty

class KvRuleWidget(BoxLayout):
    top_right_label = ObjectProperty()

    def on_touch_down(self, touch):
        print('The top right label is {}'.format(self.top_right_label))

class ExampleApp(App):
    def build(self):
        return KvRuleWidget()
# example.kv

<KvRuleWidget>:
    orientation: 'vertical'
    top_right_label: top_right  # note that we used an id to set the property
    BoxLayout:
        orientation: 'horizontal'
        Label:
            id: top_right
            text: 'top left'
        Label:
            text: 'top right'
    Label:
        text: 'middle'
    Label:
        text: 'bottom'

Option 3: The parent and children properties

It is possible to walk through the widget tree using the parent and children properties.

This is usually a bad idea and is prone to breakage if the structure of the widget tree changes. However, it’s still possible.

# main.py

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.app import App
from kivy.properties import ObjectProperty

class KvRuleWidget(BoxLayout):
    def on_touch_down(self, touch):

        # get a reference to the top right label only by walking through the widget tree
        top_right_label = self.children[-1].children[0]

        print('The top right label is {}'.format(self.top_right_label))

class ExampleApp(App):
    def build(self):
        return KvRuleWidget()
# example.kv

# note: this time there are no ids at all
<KvRuleWidget>:
    orientation: 'vertical'
    BoxLayout:
        orientation: 'horizontal'
        Label:
            text: 'top left'
        Label:
            text: 'top right'
    Label:
        text: 'middle'
    Label:
        text: 'bottom'

How can a widget in Kv access children defined in Python?

Sometimes you might have some children defined via a Kv rule, and others created dynamically in Python. You can access the Python widgets in kv by saving references to them in Kivy properties:

# main.py

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.app import App
from kivy.properties import ObjectProperty

class KvRuleWidget(BoxLayout):
    label_created_in_python = ObjectProperty()

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        # add a widget from python code
        label = Label(text='label created in Python')
        self.add_widget(label)
        self.label_created_in_python = label  # save a reference

class ExampleApp(App):
    def build(self):
        return KvRuleWidget()
# example.kv

<KvRuleWidget>:
    orientation: 'vertical'
    Label:
        text: 'label created in Kv'
    Label:
        text: 'the label created in Python has text "{}"'.format(root.label_created_in_python.text)

How can a widget defined in a kv rule access a widget defined in another kv rule?

Sometimes you might have two widgets in very different places that need to talk to one another somehow. Usually the best way to achieve this is to consider how they are related to one another, and pass information between them via their common relations.

Also see the next Section for how to access any widget from anywhere, without worrying about how the widgets are related. However, that usually isn’t such a good choice in the long run.

The following example is deliberately very simple, but the same principles can be used to link together widgets across your whole program using references passed around where the kv rules meet.

# main.py

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.app import App
from kivy.properties import ObjectProperty

class IncrementCounterButton(Button):
    counter = NumericProperty(0)
    def on_press(self):
        self.counter += 1

class CounterLabel(Label):
    counter = NumericProperty(0)

class RootWidget(BoxLayout):
    pass

class ExampleApp(App):
    def build(self):
        return RootWidget()
# example.kv

<IncrementCounterButton>:
    text: 'press me'

<CounterLabel>:
    text: 'the counter value is {}'.format(app.counter)  # `app` in kv is equivalent to `App.get_running_app()` in Python

<RootWidget>:
    orientation: 'vertical'
    CounterLabel:
        counter: button.counter  # this means the CounterLabel's counter will always match the button's counter
    IncrementCounterButton:
        id: button

How can any widget access any other widget from anywhere?

Sometimes you really do want widgets to interact with one another without any good relationship between them. You can do this in a convenient way by using a Kivy property in the App class.

Note: This is notionally similar to using a global variable, and is often bad practice for all the same reasons.

The following example is quite contrived to keep it simple. In this case you could probably think of a better way to do the same thing, perhaps using the methods from the previous Sections.

# main.py

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.app import App
from kivy.properties import ObjectProperty

class IncrementCounterButton(Button):
    def on_press(self):
        # You can always access your App class from Python as follows:
        App.get_running_app().counter += 1

class CounterLabel(Label):
    pass

class ExampleApp(App):
    counter = NumericProperty(0)

    def build(self):
        boxlayout = BoxLayout(orientation='vertical')
        label = CounterLabel()
        button = IncrementCounterButton()

        boxlayout.add_widget(label)
        boxlayout.add_widget(button)

        return boxlayout
# example.kv

<IncrementCounterButton>:
    text: 'press me'

<CounterLabel>:
    text: 'the counter value is {}'.format(app.counter)  # `app` in kv is equivalent to `App.get_running_app()` in Python

An update on python-for-android: v2019.06.06 released and future plans

python-for-android is a packaging tool for Python apps on Android. You can create your own Python distribution including the modules and dependencies you want, and bundle it in an APK along with your own code.


python-for-android 2019.06.06 has just been released! This release contains 198 commits from 31 different contributors. Many thanks to everyone involved.

Major changes in this release include:

  • Added support for running your setup.py when packaging your app, enabling your code to be installed as a module in the on-device Python environment. This also makes it easy to build Cython or other compiled components.
  • Added some tooling for requesting and checking for app permissions at runtime.
  • Added initial support for matplotlib.
  • Updated many recipes, and especially the SDL2 backend components, for improved performance and stability on Android.
  • Ongoing improvements to our test coverage and infrastructure.
  • Removed a significant amount of dead code relating to long-deprecated build configurations, and unified other parts of the build to reduce duplication of effort between bootstraps.
  • Updated the release model to target regular, smaller releases.

Of course there have also been a wide range of bugfixes and improvements. See the release notes for a full changelog.

Release model

In this release we’ve transitioned to a calendar-based version system. Future releases will continue to be of the form YYYY.MM.DD. We’re initially targeting one release every four weeks. This scheme represents how python-for-android is normally best used: many changes are driven by updates in the Android ecosystem and build toolchain, and in practice it’s usually best to be working from the most recent possible python-for-android release.

This has been made possible by the hard work of various contributors, setting up and continuing to improve python-for-android’s suite of tests and continuous integration services. In the past we haven’t done a great job of keeping up releases alongside major improvements, but this should now be much more straightforward.

If you’re using buildozer then this doesn’t directly affect you, as buildozer was already configured to use a more recent python-for-android version. Buildozer will now automatically transition to use the latest release, represented by the master branch in the python-for-android git repository.

Future work

We’ve had some questions about Google’s plan to require 64-bit support for apps on Google Play, starting in August 2019. In fact python-for-android already supports this, just pass the required architecture as an argument to the build:

p4a apk --arch=arm64-v8a

That said, we’re working to improve our testing and documentation around 64-bit builds, to make sure everything works as expected.

We also don’t currently support multi-architecture builds in a single output APK. This should be technically possible, but hasn’t ever been a focus because it would significantly increase the APK size. We may revisit this, but in the meantime you can upload one APK of each type to Google Play and python-for-android will automatically handle versioning different architectures correctly (i.e. arm64-v8a is an ‘upgrade’ for devices that support it, so that APK will be preferred).

The other current focus is on improving our test infrastructure, especially increasing test coverage and automation. This should further increase the ease of making regular releases, and our confidence that everything continues to work correctly!

These points are just general goals for python-for-android. There is always other maintenance work to do, and contributions of all types are always welcome.

An update on python-for-android: v2019.06.06 released and future plans

python-for-android is a packaging tool for Python apps on Android. You can create your own Python distribution including the modules and dependencies you want, and bundle it in an APK along with your own code.


python-for-android 2019.06.06 has just been released! This release contains 198 commits from 31 different contributors. Many thanks to everyone involved.

Major changes in this release include:

  • Added support for running your setup.py when packaging your app, enabling your code to be installed as a module in the on-device Python environment. This also makes it easy to build Cython or other compiled components.
  • Added some tooling for requesting and checking for app permissions at runtime.
  • Added initial support for matplotlib.
  • Updated many recipes, and especially the SDL2 backend components, for improved performance and stability on Android.
  • Ongoing improvements to our test coverage and infrastructure.
  • Removed a significant amount of dead code relating to long-deprecated build configurations, and unified other parts of the build to reduce duplication of effort between bootstraps.
  • Updated the release model to target regular, smaller releases.

Of course there have also been a wide range of bugfixes and improvements. See the release notes for a full changelog.

Release model

In this release we’ve transitioned to a calendar-based version system. Future releases will continue to be of the form YYYY.MM.DD. We’re initially targeting one release every four weeks. This scheme represents how python-for-android is normally best used: many changes are driven by updates in the Android ecosystem and build toolchain, and in practice it’s usually best to be working from the most recent possible python-for-android release.

This has been made possible by the hard work of various contributors, setting up and continuing to improve python-for-android’s suite of tests and continuous integration services. In the past we haven’t done a great job of keeping up releases alongside major improvements, but this should now be much more straightforward.

If you’re using buildozer then this doesn’t directly affect you, as buildozer was already configured to use a more recent python-for-android version. Buildozer will now automatically transition to use the latest release, represented by the master branch in the python-for-android git repository.

Future work

We’ve had some questions about Google’s plan to require 64-bit support for apps on Google Play, starting in August 2019. In fact python-for-android already supports this, just pass the required architecture as an argument to the build:

p4a apk --arch=arm64-v8a

That said, we’re working to improve our testing and documentation around 64-bit builds, to make sure everything works as expected.

We also don’t currently support multi-architecture builds in a single output APK. This should be technically possible, but hasn’t ever been a focus because it would significantly increase the APK size. We may revisit this, but in the meantime you can upload one APK of each type to Google Play and python-for-android will automatically handle versioning different architectures correctly (i.e. arm64-v8a is an ‘upgrade’ for devices that support it, so that APK will be preferred).

The other current focus is on improving our test infrastructure, especially increasing test coverage and automation. This should further increase the ease of making regular releases, and our confidence that everything continues to work correctly!

These points are just general goals for python-for-android. There is always other maintenance work to do, and contributions of all types are always welcome.