Zornux docs
Get started Spec

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:

zornux
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:

zornux
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:

zornux
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
Centralize auth

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:

zornux
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:

zornux
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:

zornux
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
JSON round-trip

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:

zornux
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:

zornux
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
on_end callback

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):

zornux
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