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, ascountis above. - Values are read and written, not bound. Where Kivy binds properties, you
call
get_value()andset_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,pandasand 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,mathand commonrepatterns. 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.