Skip to content
Flow-UILive

Actions

Declarative interactions for server driven UI on iOS. What happens on tap is part of the backend response, not compiled into the app.

An action is a JSON object with a type and whatever else that type needs. Widgets declare them per event; the dispatcher routes them to whichever handler understands the type.

json
{
  "actions": {
    "tap": { "type": "open_bottom_sheet", "sheet": {} },
    "long_press": { "type": "toast", "message": "Held" },
    "change": { "type": "api", "endpoint": "cart/update" }
  }
}

tap and long_press are wired by the renderer automatically. Named events like change are fired by widgets themselves, for example when a stepper's count moves.

Built in actions#

TypeBehavior
toastShows a floating message. { "message": "Saved", "duration": 2.5 }
dismissCloses the active sheet, or asks the host to dismiss the screen.
refresh_pageReloads the current page through the loader.
open_bottom_sheetPresents the inline sheet under the sheet key.
apiSends the whole action object to your loader and applies returned mutations.

The handler chain#

Handlers are consulted newest first, and the first one to return true consumes the action. That gives you three powers with one mechanism: add new types, override built in behavior, and keep navigation in your own router.

DeeplinkHandler.swiftswift
struct DeeplinkHandler: ActionHandler {
    struct Payload: Decodable { let url: String }
 
    func handle(_ action: ActionData, context: ActionContext) async -> Bool {
        guard action.type == "deeplink",
              let payload = try? action.payload(Payload.self) else { return false }
        await router.open(payload.url)
        return true
    }
}
 
let dispatcher = ActionDispatcher()
dispatcher.register(DeeplinkHandler())
FlowPageView(store: store, dispatcher: dispatcher)

Deeplinks are deliberately not built in. Navigation belongs to your app; Flow-UI just delivers the payload.

The api action and mutations#

The api action is how a tap changes the page without a reload. Flow-UI sends the full action object to your loader, and your backend answers with mutations:

Action responsejson
{
  "mutations": [
    { "kind": "replace_widget", "id": "product_1", "widget": {} },
    { "kind": "remove_widget", "id": "product_2" }
  ],
  "toast": { "message": "Added to favourites" }
}

Five mutation kinds exist: replace_widget, remove_widget, append_sections, prepend_sections and replace_page. Applied against stable widget ids, they make optimistic looking updates trivial: favourite a card and the backend sends back the favourited version of just that card.

replace_widget and remove_widget reach a widget wherever it lives: the page header, a section header, a section body, or the page footer. A sticky footer button is as replaceable as a card in the middle of the list.