a2uicatalog · Vol. 15 LIVE_ARTIFACT

Share an article, read it offline: A2UI on Android

2026-10-07 5 min read series a2uicatalog · Vol 15 build 8095cce

A few days ago I put the A2UI catalog on Android. The only way I really get my head around something new is to build with it, so I ported one of my favourite things I've made: my article analyser. I share a link from the browser, pick how I want it read, and a minute later I have a structured reading on my phone that still opens on the train with no signal.

The article analyser on a Pixel 7 Pro: share a link, pick a lens, read the result natively. I used my own Share To Pattern article with the Challenge lens, read by Gemini 3.7 Flash.

How the Android article analyser works

Until now the analyser ran as an MCP App, with a Progressive Web App as the share target and a Cloudflare durable store that keeps each analysis as a compressed A2UI payload. It was a bit hacky, and opening the workspace took more thought than starting an analysis. The Android version keeps the same store and swaps the front end for a native share target. It's a nice example of why A2UI is interesting: the payload is declarative, so it doesn't care what draws it.

Here is the whole path in 24 seconds, drawn with the catalog's own architecture atom:

0:00

The steps, in order. Each links to the code in the android/article-analyser folder:

On the phone

  1. Share sheet. The app is a share target for any link. You pick a lens (explain, apply, challenge or situate) and can add something to look for or push back on.
  2. Queue. The share sheet only saves the link and queues a WorkManager job, then closes. The job runs when there's a network, so a share works with no signal.
  3. Fetch. When the job runs, the phone downloads the page itself, on its own network. It's a plain request with no browser cookies, so articles behind a login or paywall won't read. If the page is too thin to quote, the server tries the address itself.
  4. MCP call. The app calls a read_article tool on my MCP server, signed in with OAuth and PKCE.

On the server

  1. read_article. Gemini reads the article through a runbook and returns an A2UI payload.
  2. The prompt. It's short, and it's in the repo in full.

Back on the phone

  1. Offline library. Each reading is kept as a small file, and a sync with the server's store adds readings made elsewhere.
  2. Native reading. The concept ladder draws in Jetpack Compose, beside Google's own Material 3 components.

An offline library that syncs

Every reading is kept on the phone as a small file next to its payload, so the app opens with no signal. The server's store stays the source of truth: a sync adds readings made elsewhere, collects any the phone timed out on, and drops the ones I've deleted.

Alan Blount at Google, in a comment on my LinkedIn post, calls this pattern "cached configurable static apps". I like that, because it's what the screen is: a static payload, cached on the device, and configured by the data it carries.

The phone is the MCP client

In an MCP App the host is Claude, ChatGPT or Gemini Enterprise: it runs an MCP client for its model, and the client calls the tools. Here the phone app is the client itself, and it also draws the result. There's no model on the phone. The app makes a plain JSON-RPC tools/call with an OAuth token and the reading happens on the server, so the same tool and the same store work from Claude, from the web and from my phone.

How the model reads the article

The analyser reads with the Toulmin method (claim, grounds, warrant) plus some steelmanning, and the prompt tells the model to quote the article word for word. That's why I find it useful for complicated topics: every rung of the reading comes with a quotation I can check against the source.

The model only gets the tools a call needs. When the phone sends the article text, it can stamp a reading and nothing else, so it can't go fetching anything. The system prompt says the article text is data and not instructions, and that every quotation must be verbatim. Each reader is capped at 20 readings a day.

From WebView to native Jetpack Compose

My first version drew the reading in a WebView, and the screen sat blank for a couple of seconds while it loaded. In the last article I said I'd port the most-used atoms to native. The first two are concept_ladder and concept_rung, which is what a reading is made of. The Compose version opens without that blank screen.

They use the same colour tokens as the web version, so a payload can restyle the ladder in both places. Compose and a browser lay text out differently, so they match closely rather than pixel for pixel.

One thing to know about versions. The analyser stamps its readings as A2UI v1.0, but Google's androidx.a2ui alpha only accepts v0.9 and v0.9.1. The library's adapt() function converts a v1.0 reading to v0.9 before the engine sees it, and swaps any component type the app can't draw for a visible placeholder instead of a crash. So Android runs on A2UI v0.9 today, and the same payload still opens unchanged on the web and in Claude.

One thing to look out for if you write native atoms for androidx.a2ui. In 1.0.0-alpha01, declaring a component's object and list fields (the ladder's rungs and palette, for example) as typed properties makes the engine reject the whole component, and Google's Column draws a red error chip in its place. The native ladder declares no typed properties and reads its fields from the raw payload instead, with data bindings resolved. As a safety net, if the engine can't take a payload at all, the app falls back to the bundled web renderer, so a reading never comes up blank.

The source

It's all in the android/article-analyser folder of the repo: the Kotlin app, the server's read_article tool, the whole prompt, and a README that walks through each step with the code. The catalog library itself is on Maven Central as ai.a2uicatalog:atomic-catalog.

My server address and sign-in details are placeholders, so to run the app you need an MCP server with three tools (read_article, list_readings and delete_reading) and OAuth with PKCE. The README spells out the contract.

The limits

  • I've run it on one phone, a Pixel 7 Pro, and I install it by hand. It isn't on the Play Store.
  • androidx.a2ui is still alpha and only speaks A2UI v0.9 and v0.9.1, so readings are converted from v1.0 on the way in. Its API may change, and its source carries a TODO to move to v1.0.
  • The sign-in token is kept in the app's private storage, which is fine for a sideloaded personal app and not for one you distribute: use the Android Keystore for that.
  • With 100 or more readings, a sync no longer applies deletions made elsewhere.
  • The phone fetches the page without checking where a link points, so a link to a private-network address would be requested from the phone. That's fine for a personal app, and worth knowing before you distribute one.
  • Reading needs a signed-in account on a server that has the tool. A new article can't be read offline, only ones already read.
  • The prompt requires verbatim quotations. I haven't built a check that confirms it, so I read the ones that matter.

If you build something on this, I'd like to hear what breaks.