Build

Putting Up Your First Screen in Ten Lines

By makemind · Jan 9, 2026

A waiting-room number board. One big number, one line of how many are waiting, two buttons.

What does this normally take? Make a project, write a widget tree, build it, install it on the panel. Want the number bigger? Go around those four steps again.

Here the screen is one file on the server. Connect, and the file arrives.

The screen

All of it.

{ "type": "page", "title": "Front Desk",
  "content": { "type": "center",
    "child": { "type": "linear", "direction": "vertical", "spacing": 16, "alignment": "center", "children": [
      { "type": "text", "text": "NOW SERVING", "style": { "fontSize": 18, "letterSpacing": 4, "color": "#6b7280" } },
      { "type": "text", "text": "{{now}}", "style": { "fontSize": 96, "fontWeight": "bold", "color": "#111827" } },
      { "type": "text", "text": "{{waiting}} waiting · issued {{issued}}", "style": { "fontSize": 20, "color": "#6b7280" } },
      { "type": "linear", "direction": "horizontal", "spacing": 12, "children": [
        { "type": "button", "label": "Call next", "onTap": { "type": "tool", "tool": "queue.next" } },
        { "type": "button", "label": "Take a ticket", "onTap": { "type": "tool", "tool": "queue.take" } } ] } ] } } }

The title says ten lines. It came in at nine lines, 848 bytes.

The number goes where {{now}} is. The buttons' onTap names a tool — in this piece, pressing them still does nothing. That is the next article.

Those nine lines live on the server

The server side is this much.

server.addResource(
  uri: 'ui://app',
  name: 'Front Desk screen',
  description: 'The screen definition, served as a file',
  mimeType: 'application/json',
  handler: (uri, params) async => ReadResourceResult(contents: [
    ResourceContentInfo(
      uri: 'ui://app',
      mimeType: 'application/json',
      text: File(_screenPath).readAsStringSync(),
    )
  ]),
);

It reads the file fresh every time. Reading once at boot and holding it in memory is faster, but then changing the screen means restarting the server — and at that moment the thing this sample claims, that editing the file gives you a different app, becomes false.

/// Read fresh on every request, deliberately. A screen cached at boot is a
/// screen you have to restart the server to change, and then the thing this
/// sample claims — edit the file, get a new app — stops being true.

Receive it and draw it

The client side is three lines.

final read = await client.readResource('ui://app');
final screen = jsonDecode(read.contents.first.text!) as Map<String, dynamic>;
await rt.initialize(screen);

rt is the runtime. Put JSON in, get a screen.

the server offers: ui://app
ui://app — 847 B, 9 lines, type "page", title "Front Desk"
nothing about this screen is compiled into the host; it arrived just now
opened at 41, 0 waiting
NOW SERVING 41 — 0 waiting · issued 41, Call next / Take a ticket
NOW SERVING 41 — 0 waiting · issued 41, Call next / Take a ticket

This content requires Developer or above

Sign in and upgrade your plan to continue reading.

View Plans
Twitter