Mobile
Android Data & Storage
Mobile apps live on spotty networks and must feel fast even when the server is slow. This page covers the data patterns that make that possible: structured API calls, local persistence, caching strategies, and offline-first design.
Structured API calls
The http capability returns parsed JSON as a Zornux
value — maps, lists, text, and numbers. Access nested fields with dot
notation:
screen OrderDetail receives orderId
state order = nothing
when screen opens
create response = http.get("https://api.example.com/orders/" + orderId)
order = response
end
column padding 16 spacing 12
if order is not nothing
text "Order #" + order.id style heading
text "Status: " + order.status style subheading
text "Total: $" + order.total
divider
text "Items" style subheading
for each item in order.items
row padding 8 spacing 12 align center
text item.name
spacer
text item.quantity + "x"
text "$" + item.price
end
end
else
progress
end
end
end
POST, PUT, and DELETE
Send data to an API with http.post and http.put.
Pass the request body as a map:
function create_task with title, description
create body = { "title": title, "description": description }
create result = http.post("https://api.example.com/tasks", body)
give back result
end
function update_task with id, title, done
create body = { "title": title, "done": done }
http.put("https://api.example.com/tasks/" + id, body)
end
function delete_task with id
http.delete("https://api.example.com/tasks/" + id)
end
Authentication headers
Pass headers — including authorization tokens — as the last argument:
screen Dashboard
state data = nothing
when screen opens
create token = secure_storage.get("auth_token")
create headers = { "Authorization": "Bearer " + token }
data = http.get("https://api.example.com/dashboard", headers)
end
column padding 16
if data is not nothing
text "Welcome, " + data.user.name style heading
end
end
end
Extract the header construction into a helper function so every API call shares the same token retrieval and header format — and a single place to handle token expiry.
Error handling for API calls
Network requests can fail. Wrap them in try/catch
and give the user a way to retry:
screen Products
state products = []
state loading = true
state error_text = ""
function load_products
loading = true
error_text = ""
try
products = http.get("https://api.example.com/products")
loading = false
catch error
error_text = "Could not load products. Check your connection."
loading = false
end
end
when screen opens
load_products()
end
column scroll padding 16 spacing 12
text "Products" style heading
if loading
progress
else if error_text is not ""
text error_text
button "Try again"
when tapped
load_products()
end
end
else
for each product in products
card
column padding 16
text product.name style subheading
text "$" + product.price
end
end
end
end
end
end
Local storage
For simple key-value persistence (settings, drafts, small data), use
store. Values survive app restarts:
screen Settings
state theme = "light"
state language = "en"
state notifications_on = true
when screen opens
create saved_theme = store.load("theme")
if saved_theme is not ""
theme = saved_theme
end
create saved_lang = store.load("language")
if saved_lang is not ""
language = saved_lang
end
end
column padding 24 spacing 16
text "Settings" style heading
row spacing 12 align center
text "Dark mode"
switch notifications_on
end
button "Save"
when tapped
store.save("theme", theme)
store.save("language", language)
show "Settings saved"
end
end
end
end
Caching API responses
Show cached data immediately while refreshing from the network. This eliminates blank screens on repeat visits:
screen Articles
state articles = []
state refreshing = false
when screen opens
# show cached data first
create cached = store.load("cached_articles")
if cached is not ""
articles = from_json(cached)
end
# then fetch fresh data
refreshing = true
try
create fresh = http.get("https://api.example.com/articles")
articles = fresh
store.save("cached_articles", to_json(fresh))
catch error
if length of articles is 0
show "No internet and no cached data"
end
end
refreshing = false
end
column scroll refreshable padding 16 spacing 8
text "Articles" style heading
if refreshing
progress
end
for each article in articles
card
column padding 16
text article.title style subheading
text article.summary
text article.date style caption
end
end
end
end
end
to_json(value) serializes a Zornux value to a JSON string for storage. from_json(text) parses it back. Both are built-in functions — no imports needed.
Offline-first patterns
For apps that must work without a connection, combine local storage with connectivity checks and a sync-on-reconnect strategy:
mobile app "FieldNotes"
state notes = []
state pending_sync = []
function load_notes
create saved = store.load("notes")
if saved is not ""
notes = from_json(saved)
end
create pending = store.load("pending_sync")
if pending is not ""
pending_sync = from_json(pending)
end
end
function save_note with text
create note = { "text": text, "created": now(), "synced": false }
add note to notes
add note to pending_sync
store.save("notes", to_json(notes))
store.save("pending_sync", to_json(pending_sync))
try_sync()
end
function try_sync
if connectivity.is_online() and length of pending_sync is greater than 0
try
http.post("https://api.example.com/notes/batch", pending_sync)
pending_sync = []
store.save("pending_sync", "[]")
show "Notes synced"
catch error
# will retry next time
end
end
end
screen NoteList
state new_note = ""
when screen opens
load_notes()
end
when screen resumes
try_sync()
end
column scroll padding 16 spacing 12
text "Field Notes" style heading
text (length of pending_sync) + " unsynced" style caption
row spacing 8
input new_note "New note..."
button "Add"
when tapped
if new_note is not ""
save_note(new_note)
new_note = ""
end
end
end
end
divider
for each note in notes
card
row padding 16 align center
text note.text
spacer
if note.synced
icon "cloud_done" label "Synced"
else
icon "cloud_off" label "Pending sync"
end
end
end
end
end
end
start with NoteList
Paginated lists
For large data sets, load items in pages. Fetch the next page when the user scrolls to the bottom:
screen ProductCatalog
state products = []
state page = 1
state has_more = true
state loading = false
function load_page
if loading or not has_more
give back nothing
end
loading = true
try
create result = http.get("https://api.example.com/products?page=" + page + "&limit=20")
for each product in result.items
add product to products
end
has_more = result.has_more
page = page + 1
catch error
show "Failed to load more items"
end
loading = false
end
when screen opens
load_page()
end
column scroll on_end load_page padding 16 spacing 8
text "Catalog" style heading
for each product in products
card
row padding 16 spacing 12 align center
image product.thumbnail
width 64
height 64
corner_radius 8
end
column spacing 4
text product.name style subheading
text "$" + product.price
end
end
end
end
if loading
progress
end
if not has_more
text "No more products" style caption
end
end
end
The on_end modifier on a scrollable column calls the named function when the user scrolls near the bottom. The function should guard against duplicate calls with a loading flag.
Secure token management
Store authentication tokens in secure_storage (encrypted
keystore), never in store (unencrypted preferences):
function login with email, password
create body = { "email": email, "password": password }
create result = http.post("https://api.example.com/auth/login", body)
secure_storage.save("access_token", result.access_token)
secure_storage.save("refresh_token", result.refresh_token)
end
function authenticated_get with url
create token = secure_storage.get("access_token")
create headers = { "Authorization": "Bearer " + token }
give back http.get(url, headers)
end
function logout
secure_storage.remove("access_token")
secure_storage.remove("refresh_token")
store.save("cached_user", "")
end
What's next
- Android Architecture — navigation patterns, shared state, lifecycle, and multi-module projects.
- Android UI Patterns — theming, custom widgets, forms, dark mode, and accessibility.
- Mobile Capabilities — camera, location, notifications, biometrics, and native extensions.