Skip to content

Coming from Kivy

If you have written a Kivy app, most of what you know carries over to ApkPy: a Python file, screens, widgets, callbacks. What changes is underneath. Kivy ships a Python interpreter in the APK and draws every widget itself with OpenGL; ApkPy translates your Python into Android source when you build, so the phone gets Android's own views and no interpreter.

This page maps the Kivy pieces you use every day onto ApkPy, shows one small app written both ways, and says plainly what does not carry over. For the broader comparison, including BeeWare and Flet, see ApkPy vs Kivy, BeeWare and Flet.

The same counter, twice

In Kivy, an app is a class with a build() method:

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


class CounterApp(App):
    def build(self):
        layout = BoxLayout(orientation="vertical", padding=24, spacing=16)
        self.count = Label(text="0", font_size=48)
        button = Button(text="Add one")
        button.bind(on_press=self.add_one)
        layout.add_widget(self.count)
        layout.add_widget(button)
        return layout

    def add_one(self, instance):
        self.count.text = str(int(self.count.text) + 1)


CounterApp().run()

In ApkPy, the screen and its components are declared at the top of the file, callbacks are plain functions, and the look lives in a stylesheet:

from apkpy_lib import Screen, button, label, run

home = Screen(id="home")
count = label("0", id="count", screen=home)


def add_one():
    count.set_value(str(int(count.get_value()) + 1))


button("Add one", id="add", command=add_one, screen=home)

style = """
home {
    padding: 24px;
    gap: 16px;
}

count {
    font-size: 48px;
}
"""

if __name__ == "__main__":
    run(start_screen=home)

python writehere.py shows it in the desktop Previewer, as python main.py does for Kivy. apkpy run turns it into an APK, where count is an Android TextView and the button an Android Button.

What maps to what

In Kivy In ApkPy
class MyApp(App), build(), MyApp().run() Screen(id=...) for each screen, run(start_screen=home)
KV language the calls in Python, and a style = """...""" stylesheet for the look
BoxLayout, GridLayout components stack in their screen; row(), column() and container() group them; gap and padding in the stylesheet
Label(text=...), label.text = ... label("..."), set_value(...)
Button(on_press=...) button("...", command=...)
TextInput, .text inputs("placeholder"), get_value()
Switch, CheckBox, Slider inputs(type="switch"), type="checkbox", type="range", with on_change
Spinner(values=...) segmented(options) or chips(options)
Image(source=...) image(src)
RecycleView list_view(items=...), or a virtual collection for long feeds
ScreenManager, manager.current = "name" on_click_navigate(screen) to open one, back() to return
Clock.schedule_interval(fn, 1) timer.every(1, fn)
Clock.schedule_once(fn, 2) timer.after(2, fn)
JsonStore storage.set() and storage.get(); db for SQLite tables
plyer.notification.notify(...) notify(title, message)
plyer.vibrator haptics.tap(), haptics.heavy()
plyer.camera camera.open()
pyjnius.autoclass(...) native.java(), or @native in Python since 1.13.0
buildozer.spec apkpy.toml
buildozer android debug apkpy run
buildozer android release apkpy release, or apkpy release --aab for Google Play

Three things change in how you think about it:

  • Callbacks are functions, not methods. There is no self: the components you need are names at the top of the file, as count is above.
  • Values are read and written, not bound. Where Kivy binds properties, you call get_value() and set_value(), or use a state value that updates the screen when it changes.
  • The look is a stylesheet. Colours, sizes, spacing and corners go in one CSS-like block, with a light and a dark theme if you want them; see Themes and styling.

Building: no WSL, no Colab

Buildozer runs on Linux and macOS, so on Windows it asks for WSL. ApkPy builds on Windows directly: apkpy setup downloads a JDK, Gradle and the Android SDK into your user folder, and apkpy run builds the APK. Python to APK on Windows walks through it.

Calls into Android also work differently. In Kivy, autoclass("android.os.Vibrator") finds the class when that line runs on the phone, so a misspelt name shows up as a crash there. In ApkPy the block is compiled with the app and checked against android.jar, so the same mistake stops the build instead. See Your own Java.

What does not carry over

This is the price of not shipping an interpreter, said once:

  • Packages from pip. requests, numpy, pandas and the rest are imported by an interpreter, and there is none on the phone. ApkPy has its own calls for network requests, data and security, files and the device. If your app is built around a Python library, Kivy is the better fit.
  • Classes. ApkPy translates a listed vocabulary of Python: functions, control flow, lists, dicts, f-strings, try/except, math and common re patterns. A Kivy app organised in classes is rewritten as functions; anything outside the list stops the build at its line and says what to write instead. Compatibility and limits has the list.
  • The canvas. Kivy's drawing instructions, custom-drawn widgets and games have no counterpart: ApkPy builds interfaces from Android's components, with charts for data.
  • Other platforms. Kivy reaches iOS and desktop from the same code; ApkPy builds Android only.

What you get in exchange: an APK of about 1.5 MB for a small app, signed and shrunk; no interpreter to start; Android's own scrolling, keyboard, accessibility and dark mode; and a generated project you can open in Android Studio.

Next: the end-to-end tutorial, or the Showcase of four complete apps.