Write the app in Python.
Keep the Android output.
ApkPy is a source-to-source compiler for native Android apps. You describe screens, state and callbacks in Python; ApkPy generates Activities, XML layouts, drawables, resources and manifest entries that Android understands directly.
01 from apkpy_lib import Screen, Theme
02 from apkpy_lib import card, button, run
03
04 home = Screen(id="home", scroll=True)
05 card(title="Available balance",
06 content="€ 8,420.16", screen=home)
07 button("Transfer", variant="filled",
08 command=open_transfer, screen=home)
09
10 run(start_screen=home, theme=Theme(mode="dark"))
The error should tell you what to change.
Previewer callbacks, imports, Data Core declarations, compilation and Android toolchain checks now share one readable report: a stable code, the useful application line, the original cause and a concrete correction.
Learn to read an ApkPy errorAPKPY E1102 - This import is not available
Where
writehere.py:1
Received
bottom_nva
How to fix
1. Did you mean:
from apkpy_lib import bottom_nav
The same 100-note app was built with ApkPy, Flet and BeeWare/Toga, then installed and measured on one emulator. The comparison uses debug builds on every side, so the build class is the same; what you publish is the release, which R8 shrinks to 1.48 MiB. The programs, line counts, raw starts, memory samples and artifact hashes are available for review.
Open the generated Java, XML and Gradle project in Android Studio. ApkPy does not hide a web page or Python interpreter inside the APK.
Start from the product you are building¶
If this is your first project, use the complete Knowledge Vault tutorial before choosing a feature-specific guide. It ends with a generated Android project you can open and inspect.
One source file, two useful feedback loops¶
The Previewer is for short iteration cycles. Android generation is for checking the real platform output. They share the same declarations and callbacks, but each target does the job it is good at.
python writehere.pyHot Previewer
Open the interface in seconds. Exercise navigation, inputs, callbacks, storage and responsive breakpoints before starting Gradle.
- Fast UI iteration
- Desktop state and callback testing
- Phone, tablet and resizable presets
apkpy buildNative Android project
Inspect exactly what will run on the device: Java Activities, XML layouts, Material resources, services and permissions.
- Openable in Android Studio
- No WebView or embedded Python interpreter
- Device APIs stay native
What one button becomes¶
This is a representative view of the compiler pipeline. Names and generated attributes can vary with the screen and theme, but the mapping is direct: a Python declaration becomes an Android view and its callback becomes a Java listener.
button(
"Transfer",
id="transfer",
variant="filled",
command=open_transfer,
screen=home,
)
<MaterialButton
android:id="@+id/transfer"
android:text="Transfer"
android:minHeight="48dp"
android:background="@drawable/…" />
btn_transfer.setOnClickListener(
view -> {
pythonCallback_open_transfer();
}
);
Generated code is an output. Keep behavior in writehere.py; rebuilding may replace the Android files. Read the source-of-truth rules →
A real Android project, not a screenshot exporter¶
The compiler creates only the helpers a project needs. A simple screen stays small; media, OAuth, advanced layout or image caching add their runtime pieces when those APIs appear in the source.
app/src/main/
├── AndroidManifest.xml # activities, services, permissions
├── java/com/example/app/
│ ├── Screen_homeActivity.java
│ ├── Screen_libraryActivity.java
│ └── ApkpyMediaService.java # only when media is used
└── res/
├── layout/ # phone layouts
├── layout-sw600dp/ # tablet layouts when responsive
├── drawable/ # shapes, vectors and local images
├── menu/ # navigation and toolbar actions
└── values/ # themes, colors and strings
Build more than static screens¶
Compose a reusable hierarchy and override it through component or ID selectors.
Read and update values using normal supported Python control flow.
Keep local data, call APIs and download private app files without a separate plugin layer.
Keep long timelines responsive while the application remains in control of cursors and conflict rules.
Generate an Android foreground media service, notification controls and persistent libraries.
Use browser authorization and generated deep-link handling without embedding a client secret.
Declare the API and let ApkPy add the matching permission and native Android integration.
Build notes, articles, comments and knowledge bases with native selectable text and recycled hierarchy rows.
No browser hidden in the app.
rich_text() and markdown() compile to Android
Spannable text, while tree_view() uses a native
visible-row RecyclerView.
Open the native rich-content guide →
Native audio is already a system player¶
ApkPy's music API is not limited to playing a sound inside the current screen.
The generated Android project can keep a queue in a foreground media service,
publish a native MediaSession, show metadata and previous/play/next controls
in the notification and lock screen, handle audio focus, synchronize a full
player and mini-player, and keep favourites and editable playlists.
audio.play_background(
"https://cdn.example.com/night-drive.mp3",
title="Night Drive",
artist="Nova",
art="https://cdn.example.com/night-drive.jpg",
)
audio.now_playing(progress=seek, time=elapsed, cover=cover,
title=title, artist=artist)
audio.controls(play_pause=play_pause, shuffle=shuffle, repeat=repeat)
mini_player(open=player)
See the complete capability matrix, queues, playlists and offline files. The documentation also states the current limits clearly: automatic audio caching, adaptive quality, guaranteed gapless playback, crossfade and DRM are not claimed as supported features.
Data code you can ship¶
SQLite, REST and cryptography are part of the normal ApkPy workflow. The Previewer uses local Python backends for fast testing; Android generation maps the same calls to SQLiteDatabase, background HttpURLConnection, SharedPreferences and Android Keystore.
db.execute(
"CREATE TABLE IF NOT EXISTS tracks "
"(id INTEGER PRIMARY KEY, title TEXT)"
)
db.execute(
"INSERT INTO tracks(title) VALUES (?)",
[title_input.get_value()],
)
rows = db.query(
"SELECT id, title FROM tracks "
"ORDER BY id DESC"
)
track_list.set_items(rows, title="title", subtitle="id")
The ? placeholder is bound by SQLite instead of concatenated into the query. This handles apostrophes correctly and prevents SQL injection.
def loaded(success, response):
if success:
tracks.set_items(
response,
title="name",
subtitle="artist",
image="cover",
)
else:
snackbar("Could not refresh library")
https.get(
"https://api.example.com/tracks",
headers={"Authorization": "Bearer " + auth.token()},
on_response=loaded,
)
Requests run away from the UI thread. The callback receives the body on success and also receives structured error bodies for HTTP 4xx/5xx responses.
See the complete REST surface →# storage is encrypted automatically
storage.set("session", auth.token())
token = storage.get("session", "")
# hash values that must only be verified
password_hash = crypto.hash_password(password)
valid = crypto.verify_password(candidate, password_hash)
# encrypt database fields that must be read later
ciphertext = crypto.encrypt(private_note)
db.execute(
"INSERT INTO notes(content) VALUES (?)",
[ciphertext],
)
plain_text = crypto.decrypt(ciphertext)
Android keeps the AES key in Android Keystore. Passwords use salted PBKDF2 with 200,000 iterations and should never be stored with reversible encryption.
Read the threat model →Do not put permanent service secrets inside an APK. Use HTTPS, short-lived tokens or OAuth with PKCE, and keep privileged authorization on a server you control.
Four small products used as regression tests¶
The showcase is executable code, not a set of design mockups. Every app has four screens, working navigation and generated Android output. Together they deliberately exercise different palettes and layout choices.

Lumen
Balance surfaces, transactions, Material actions and four destinations.
Open case study →
Onda
Responsive metrics, semantic status colors and a quiet daily plan.
Open case study →
Northline
A boarding pass, itinerary hierarchy and practical trip actions.
Open case study →
Afterglow
Packaged artwork, track rows, listening actions and a saved library.
Open case study →The workflow in three commands¶
python writehere.pyIterateOpen the Hot Previewer and test interface behavior.
apkpy buildInspectGenerate a ZIP project for Android Studio.
apkpy runInstallCompile a debug APK; add --qr or --usb.
Questions developers usually ask¶
Does the APK contain Python?
No. ApkPy compiles the supported source model into native Android Java, XML and resources. The generated application does not bundle a Python interpreter.
Can I open the result in Android Studio?
Yes. apkpy build produces a Gradle project ZIP intended for inspection, emulator testing and normal Android tooling.
Is every Python library supported?
No. ApkPy supports a deliberate Python subset plus its own Android-facing APIs. Arbitrary CPython packages cannot automatically become Java. The API reference is the contract.
Should I trust only the desktop preview?
No. Use the Previewer for fast feedback, then test device-only behavior, permissions, services and final rendering on an emulator or physical Android device. See the renderer comparison.
Install ApkPy, open the Previewer, then inspect what it generated.
Closed source, public contract
ApkPy's compiler is proprietary while active development continues. Open-sourcing may be considered later; if the project is permanently abandoned, the core source will be released as open source so it can be maintained and continued. Until an explicit source release and new licence are published, the current proprietary licence remains in force. Read the project continuity policy.