Essential API reference¶
This page is the fastest route from an API name to working code. It documents
the supported public surface exported by apkpy_lib; generated Java helper
classes are implementation details.
Conventions¶
from apkpy_lib import Screen, Theme, button, label, run
home = Screen(id="home", scroll=True)
label("Hello", id="title", screen=home)
button("Continue", id="continue", command=lambda: None, screen=home)
style = """
home { padding: 24px; gap: 12px; }
title { font-size: 28px; font-weight: bold; }
continue { border-radius: 14px; }
"""
run(start_screen=home, theme=Theme(mode="dark"))
- Pass
screen=to attach a top-level component to a screen. - Pass
parent=to put a component inside acontainerorcard. - An
idis both the stable component name and its CSS-like selector. - Network, uploads and typed database work run outside the UI thread. Their callbacks return to the Previewer/Android UI thread.
- The Previewer exercises layout and application flow. Permissions, Firebase, codecs, background restrictions and GPS still need an Android test.
App, screens and navigation¶
| API | Signature | Returns |
|---|---|---|
Screen |
Screen(id, background_image=None, scroll=False) |
screen definition |
run |
run(start_screen=None, theme=None) |
starts the app/Previewer |
bottom_nav |
bottom_nav(screens, labels=None, icons=None) |
BottomNav |
on_click_navigate |
on_click_navigate(screen, data=None) |
callback suitable for command= |
app_bar |
app_bar(title, leading=None, actions=None, id=None, screen=None) |
app-bar definition |
sliver_app_bar |
sliver_app_bar(title, image, expanded_height=240, pinned=True, leading=None, actions=None, id=None, screen=None) |
collapsible app bar |
action |
action(icon, command=None, label=None, id=None) |
app-bar action |
Navigation data is read on the destination screen:
details = Screen(id="details", scroll=True)
button(
"Open note",
command=on_click_navigate(details, {"note_id": 42}),
screen=home,
)
selected_id = details.get_param("note_id", 0)
Components¶
| API | Exact public signature |
|---|---|
label |
label(text, id=None, screen=None, parent=None, variant=None) |
button |
button(text, id=None, command=None, screen=None, parent=None, variant=None, icon=None) |
inputs |
inputs(placeholder="", id=None, type="text", screen=None, parent=None, on_change=None) |
image |
image(src, id=None, screen=None, parent=None, *, placeholder=None, fallback=None, cache=True, fade_in=False, blur=0, tint=None, aspect_ratio=None) |
video |
video(src, id=None, screen=None, parent=None, *, poster=None, autoplay=False, controls=True, loop=False, muted=False, preload=True, aspect_ratio="16:9", fit="contain", on_ready=None, on_progress=None, on_end=None, on_error=None) |
avatar |
avatar(src, size=48, status=None, id=None, screen=None, parent=None, *, placeholder=None, fallback=None, cache=True, fade_in=True, blur=0, tint=None) |
container |
container(id=None, screen=None, parent=None) |
card |
card(title=None, subtitle=None, image=None, content=None, actions=None, id=None, variant="elevated", screen=None, parent=None) |
list_view |
list_view(items=None, id=None, screen=None, parent=None, on_click=None, rich=False) |
Common returned-component methods are get_value(), set_value(value),
show() and hide(). Images expose set_src(). Videos expose play(),
pause(), stop(), seek(seconds), set_source(), set_speed() and
set_muted().
Input types include text, password, number, date, time, textarea,
select, checkbox, radio and range. Options and range values are passed
through set_items() or set_value() as shown in the component guides.
Layout¶
actions = container(id="actions", screen=home)
save = button("Save", parent=actions)
cancel = button("Cancel", variant="outlined", parent=actions)
responsive(
mobile=column(save, cancel),
tablet=row(save, cancel),
breakpoint=600,
parent=actions,
)
| API | Purpose |
|---|---|
row(*children) |
horizontal composition |
column(*children) |
vertical composition |
responsive(mobile, tablet=None, landscape=None, breakpoint=600, ...) |
switches layout by viewport |
Virtual collections and live state¶
feed = virtual_collection(
[],
template={
"title": "{author}",
"subtitle": "{message}",
"meta": "{time}",
"image": "{avatar}",
},
on_end_reached=load_more,
on_refresh=reload,
prefetch=4,
screen=home,
)
| Method | Contract |
|---|---|
set_items(items, title=None, subtitle=None, image=None, has_more=True) |
replace the dataset and finish refresh; legacy rich-row keys remain supported |
append_items(items, has_more=True) |
append a page without resetting position |
prepend_items(items) |
insert above the visible anchor |
update_item(id, changes, key="id", optimistic=False) |
patch one keyed row |
remove_item(id, key="id", optimistic=False) |
remove one keyed row |
merge_items(items, key="id") |
update matches and append new keys |
commit(mutation_id=None) |
accept an optimistic snapshot |
rollback(mutation_id=None) |
restore one optimistic snapshot |
finish_load(has_more=True) |
release a failed/empty load latch |
refresh() |
start the guarded refresh callback |
scroll_to_end() |
bring the newest row into view |
scroll_to_top() |
bring the first row into view |
scroll_to_item(id, key="id") |
bring one keyed row into view |
Rows that take the height they need¶
item_height= accepts a number — every row that height, which is what a feed
of uniform cards wants — or "auto", where each row wraps its own content:
thread = virtual_collection(
turns,
template={"title": "{author}", "markdown": "{message}"},
item_height="auto",
screen=chat,
)
A conversation needs "auto": one fixed height gives "yes" the same space as
a twenty-line answer, so one floats in a void and the other is cut off
mid-sentence. With "auto" the text also stops being clipped to one line
unless title-lines / subtitle-lines say otherwise.
Looking at the message that just arrived¶
Adding a row does not move the viewport, which is right for a feed and wrong for a conversation: you send a question and end up looking at your own question while the answer grows below the fold.
The scroll is animated over the duration the theme's motion preset gives
the nav moment, and motion="none" makes it a jump. scroll_to_item(id)
takes the same key= the mutations take.
Two honest limits. It moves once, when you call it — it does not follow text that keeps arriving, so a long streamed answer still grows past the bottom edge. And a very long jump on Android takes longer than the stated duration, because the RecyclerView re-aims as it goes and cannot know a row's height before laying it out.
The avatar slot¶
A template slot named avatar draws a circle with up to two initials taken
from its value, over a colour the value itself picks:
The same name lands on the same colour on the phone and in the Previewer — one palette, read directly by one and written into the generated Java by the other. An empty value hides the circle instead of leaving a coloured hole.
It is a mark for a name, not a picture: for a photo or a remote image, use
the image slot.
The markdown slot¶
A template slot named markdown renders its value as Markdown instead of
plain text — headings, emphasis, links, lists, quotes and fenced code blocks.
It is the same renderer the markdown() component uses, so a code block looks
the same in a row as it does on a page.
Put an assistant's answer here rather than in subtitle: read as plain text
with backticks in it, an answer with code in it is not an answer.
Add code-copy: button; to the collection's stylesheet — or to a
markdown() component's — and every fenced block gets a tappable Copy
under it that puts exactly that block on the clipboard. A block of code you
cannot copy is a block of code you retype by hand.
All of these are opt-in. A collection that asks for none of them generates the same Java and XML it always did, and never carries the Markdown renderer into the APK.
state(initial, id=None) returns a reactive value with get, set,
increment, decrement, toggle, bind and bind_visibility.
count = state(0, id="cart_count")
badge = label("0 items", screen=home)
count.bind(badge, template="{value} items")
button("Add", command=lambda: count.increment(), screen=home)
Use lifecycle(screen, on_mount=None, on_resume=None, on_pause=None,
on_destroy=None) to start and stop screen-owned work.
Typed SQLite Data Core¶
notes = db.model(
"notes",
fields={
"id": db.integer(primary_key=True, auto_increment=True),
"title": db.text(required=True, max_length=120),
"favorite": db.boolean(default=False),
"updated_at": db.datetime(default=db.now()),
},
indexes=[db.index("idx_notes_updated", ["updated_at"])],
)
db.schema("notes_app", version=1, models=[notes])
| Area | Public API |
|---|---|
| Fields | integer, real, text, boolean, datetime, json, blob, now |
| Model | model(name, fields, indexes=None) and index(name, fields, unique=False) |
| CRUD | insert, insert_many, get, find, update, delete, count |
| Filters | eq, ne, gt, gte, lt, lte, contains, starts_with, ends_with, in_, is_null, and_, or_ |
| Order | asc(name), desc(name) |
| Schema | schema(name, version, models, migrations=None) |
| Migration | migration, create_table, add_column, rename_column, create_index, rename_index, drop_index, rename_table, sql |
CRUD callbacks:
| Call | on_result receives |
|---|---|
insert |
new row ID |
insert_many |
inserted row count |
get |
JSON object or empty value |
find |
iterable JsonRows |
update / delete |
affected row count |
count |
integer count |
Every operation accepts on_error(message). Use parameterized filters and
values; do not build SQL strings with user input.
Reactive Data¶
folder_notes = db.relation(
"folder_notes",
parent=folders,
child=notes,
foreign_key="folder_id",
parent_as="folder",
children_as="notes",
on_delete="cascade",
)
db.schema(
"knowledge_vault_live",
version=1,
models=[folders, notes],
relations=[folder_notes],
)
live_notes = notes.observe(
filters=[db.eq("folder_id", active_folder_id)],
include=["folder"],
screen=notes_screen,
on_change=lambda rows: notes_feed.set_items(rows),
)
| Area | Public API |
|---|---|
| Relation | db.relation(name, parent, child, foreign_key, parent_as, children_as, on_delete) |
| Eager read | get(..., include=None) and find(..., include=None) |
| Observer | model.observe(..., screen, on_change, on_error=None) |
| Observer control | refresh(), update_query(...), close() |
on_delete accepts restrict, cascade or set_null. Includes are limited
to one level and are loaded in batches. Observers pause and resume with their
screen, coalesce rapid invalidations and deliver callbacks on the UI thread.
See Reactive Data for lifecycle and migration rules.
HTTP and JSON¶
def received(success, body):
if success:
title.set_value(json_get(body, "title"))
else:
snackbar("Request failed")
https.get("https://api.example.com/note/42", on_response=received)
https.get, post, put, patch and delete call
on_response(success, body). json_get(json_text, "items.0.title") safely
reads a dotted path.
Pass a dict as data= and it goes out as JSON with its types intact — a
number stays a number, and the text a user typed is escaped by the
serialiser rather than by you:
https.post(
"https://api.example.com/messages",
data={
"model": "some-model",
"max_tokens": 1024,
"messages": [{"role": "user", "content": question.get_value()}],
},
headers={"x-api-key": storage.get("api_key", "")},
timeout=120,
on_response=answered,
)
Content-Type: application/json is set for you when the body starts with
{ or [ and you did not choose a header yourself. Pass a string as data=
to send anything else — form-encoded bodies, XML — exactly as written.
timeout= is in seconds and applies to waiting for the response; the
default is 60 and the ceiling is 600. Raise it when the other end thinks
before it answers, as a language model does.
WebSocket¶
websocket.connect(
"room",
"wss://example.com/live",
headers={"Authorization": "Bearer " + token},
on_open=lambda: status.set_value("Live"),
on_message=lambda message: messages.prepend_items([{"text": message}]),
on_error=lambda message: status.set_value("Offline · " + message),
reconnect=True,
)
websocket.send("room", "hello")
websocket.close("room")
connect() also accepts protocols, on_close, reconnect_delay,
max_reconnect_delay and ping_interval. Sends made during the handshake are
queued with a bounded limit.
Storage and cryptography¶
| Object | Methods |
|---|---|
storage |
set, get, delete, clear, keys |
crypto |
hash_password, verify_password, encrypt, decrypt |
files |
download, path, exists, delete |
Encrypted values are tied to the app/device key. Copying only the ciphertext to another installation is not a backup strategy. Never ship API secrets in client code.
Overlays and feedback¶
| API | Callback shape |
|---|---|
bottom_sheet(..., on_select=...) |
selected item |
modal(..., on_confirm=..., on_cancel=...) |
no arguments |
menu / popup_menu / context_menu |
selected item |
date_picker / time_picker |
selected value |
snackbar(message, action=None, on_action=None, duration=3000) |
action callback |
confirm(title, message, on_result=None) |
boolean result |
Overlay objects expose open() and close().
Android integrations¶
| API | What it controls |
|---|---|
permissions |
runtime permission checks and requests |
notify / push |
local notifications and Firebase Cloud Messaging |
camera / gallery |
capture and media selection |
location |
current, continuous and foreground-service tracking |
map_view |
tiles, markers, route line, user position and follow mode |
routes |
cancellable driving, walking or cycling route request |
service |
periodic and one-shot WorkManager tasks |
audio / video |
background audio and Media3 video |
uploads |
multipart transfer with progress and cancellation |
For setup requirements and complete examples, continue to the module reference or the guide index.