Skip to content
Flow-UILive

Architecture

How Flow-UI turns a JSON page into native SwiftUI: decode through an open registry, contain failures, render widgets, and dispatch actions as data.

Flow-UI is a rendering engine, not a networking stack. The host supplies bytes. The framework decodes a page, looks up each widget in a registry the app owns, and paints native SwiftUI. Unknown types and malformed payloads are contained so one bad widget never blanks a screen.

The pipeline#

  1. Load. PageStore calls the host PageLoader for a page id. The store owns fetch, refresh, pagination and in-flight cancellation. A slower response does not overwrite a newer one.
  2. Decode. FlowCore reads the envelope: PageModel, then SectionModel, then AnyWidget. Layout, actions and tracking ride alongside the payload. Decoding is handwritten Decodable with decodeIfPresent defaults.
  3. Resolve. Each widget type string is looked up in WidgetRegistry. The registry is the only AnyView erasure point. Registering a widget is one generic call in the host app; there is no framework enum to extend.
  4. Render. FlowPageView draws nav, sticky header and footer bars, sections (vertical, carousel, grid), sheets and toasts. Chrome from WidgetLayout is applied in one fixed order: padding, background or gradient, corner clip, border, margin.
  5. Act. Taps carry ActionData. ActionDispatcher is a chain of responsibility. Built-ins cover toast, dismiss, refresh, bottom sheet and api mutations. Deeplinks are host handlers.
The host seamswift
let registry = WidgetRegistry()
FlowWidgets.register(on: registry)
registry.register(OrderCardWidget.self)
 
let store = PageStore(pageID: "home", loader: MyLoader(), registry: registry)
FlowPageView(store: store)

What the engine does not own#

Flow-UI ships no HTTP client, no image library and no design system. PageLoader and FlowImageLoader are host types. ThemeProvider maps tokens the backend already uses. That is why the package plugs into an existing app instead of replacing it.

Resilience is the default#

LossyArray keeps a section alive when one child fails. DecodeDiagnostics records the type and key path. Debug builds show a labelled placeholder; release builds skip the widget. Stable ids are derived from the coding path so a refresh diffs instead of rebuilding.