Skip to content
Flow-UILive
On this page

API driven UI and remote UI, explained

API driven UI and remote UI are names for the same idea as server driven UI: the API returns page structure, the iOS app paints SwiftUI.

Ayush MishraPublished 4 min read

API driven UI and remote UI are names for the same idea as server driven UI. The API returns page structure. The iOS app paints SwiftUI (or it paints a WebView, if you picked the wrong renderer). This post spends the three phrases once each, then stays with the envelope so you are not maintaining three definitions.

API driven UI#

API driven UI means the contract is an HTTP (or RPC) response that includes structure, not only business records. A GET /orders that returns { "orders": [...] } is not API driven UI. A GET /pages/home that returns sections and widgets is.

The temptation is to overload your domain API with layout. That couples merchandising to order JSON. Flow-UI's posture is a page document: an envelope with an id, sections, widgets, optional header and footer. Domain APIs stay domain APIs. The page document can reference domain ids inside widget data.

Your host still implements PageLoader. The framework never calls the API.

RESTPageLoader.swiftswift
struct RESTPageLoader: PageLoader {
    func loadPage(_ request: PageRequest) async throws -> Data {
        try await URLSession.shared.data(from: url(for: request)).0
    }
}

Refresh, pagination, and api actions reuse that seam. PageRequest.Kind includes initial, refresh, nextPage(postback:), and action. The postback is opaque. Echo it. Do not parse it in the client unless you enjoy owning two cursor formats.

Remote UI#

Remote UI is the oldest label. In the 2010s it often meant HTML shipped into a UIWebView or WKWebView. In 2020s SDUI talks it sometimes means native widgets from JSON. If a candidate says remote UI in an interview, ask what the renderer is before you nod.

Flow-UI is not remote HTML. Widgets are SwiftUI views registered in process. Unknown types skip or placeholder. Diagnostics record a key path. A WebView does not give you that without a second pipeline.

swift
let registry = WidgetRegistry()
FlowWidgets.register(on: registry)

Server driven UI, one more time#

Server driven UI is the phrase this site standardises on. Same split: server describes, client renders native. See backend driven vs server driven if you arrived from the BDU spelling.

One mention, then the envelope#

After those three names, stop cycling synonyms in the same paragraph. Point at the document.

A page has:

  • id
  • optional nav
  • optional header / footer bars of widgets
  • sections of widgets
  • optional pagination and refresh

Widgets have type, id, data, layout, actions, optional tracking. That is the contract the envelope API documents.

What the API must not return#

Executable code. A script field. A binary blob you dlopen. Those leave the "data" story.

It also should not return a second copy of your design system as CSS unless you intentionally chose a WebView.

Additive properties are fine. Old clients ignore keys they do not decode. New required keys without a default break old binaries. Version the contract by adding, not by renaming in place.

A page document is not a dump of tables#

Teams sometimes return { "user": ..., "orders": ..., "layout_hints": ... } and call it API driven UI. The client still decides the widget tree. That is a richer domain API. It is not a page.

The envelope inverts that: the tree is in the JSON. Domain ids live inside widget data so a title block can say order_id without the page becoming your entire database.

If you are migrating, run both: keep GET /orders for the domain, add GET /pages/orders_home for composition. Do not stuff layout into the domain resource "for now". For now becomes the contract.

Versioning the API without a special header#

Flow-UI does not require a max_schema_version header. Some companies add one as a host convention so the backend can omit unknown types for old apps. That is allowed. It is not in the package. Additive JSON (new optional keys, new widget types) is the default that old binaries already survive via skip/placeholder.

Rename-in-place of a required key is how you break 20% of sessions. Add title_v2 if you must. Leave title alone.

Host concerns people dump into the acronym#

Caching: PageLoader returns cached Data when offline. The renderer cannot tell.

Auth: attach tokens in the loader.

Experiments: the backend composes a different page document. Feature flags in the binary still exist for code you already shipped. They are not a substitute for rearranging widgets.

The Flow-UI mark: two nodes joined through a filled centre

Questions this article should close#

Is GraphQL automatically API driven UI?#

No. GraphQL can return domain graphs. Unless the query returns an envelope of sections and widgets, the client still hardcodes the tree.

Is gRPC remote UI?#

Only if the payload is a page. Binary transport does not make it SDUI.

Should the page route live on the same host as the domain API?#

Host policy. PageLoader does not care. Many teams use the same API gateway with a /pages/ prefix so auth and observability stay one path.

API driven UI, remote UI, server driven UI: one split. JSON page in, native SwiftUI out. Name the renderer every time.

More on the blog.

Read the source

Flow-UI is MIT licensed. The schema, renderer and starter widgets live on GitHub.

GitHub

Get started with Flow-UI

Install the Swift package, register a widget, and render a page from JSON.

Get started