In a multi-platform mobile product β€” iOS and Android β€” Clean Architecture and native UI still leave a practical problem: how do you change home-screen composition without rewriting SwiftUI and Android views every time, and without letting the two platforms drift apart?

That is what server-driven UI (SDUI) is for in this shape: the server owns screen composition; clients keep native presentation. A render document describes what appears and in what order; each platform maps those sections with its own toolkit β€” SwiftUI on iOS, Compose on Android. I put this into practice in HornsApp and Muvin: a shared screen contract (Kotlin Multiplatform), with layout updates delivered from the server.

A concrete scenario

Think of a home screen as an ordered list of section types, for example:

Brand A might show carousel β†’ list β†’ CTA. Brand B might show list β†’ promo β†’ carousel. Same section types, different composition. Same native components underneath. When marketing or product wants a new order, the change can ship with the document β€” not a full client release for every experiment.

Without a server-driven approach, that difference often becomes duplicated view code β€” or flavor forks and #if brand branches that are hard to keep honest across platforms.

What you are trying to protect

Three goals show up again and again in this scenario:

1. One contract for two platforms. Android and iOS should agree on what a β€œscreen document” is: which section types exist, what payload each one carries, which navigation targets are allowed. One definition of the home screen, two native apps.

2. White-label without rewriting UI. New brand β‰ˆ new configuration from the backend, not a new app fork. Shared components stay shared; only composition changes.

3. Layout changes without redeploying look-and-feel. Moving a section or swapping order should be a server/config change when possible. Fonts, colors, motion, and accessibility stay in native UI (SwiftUI / Compose).

If your team is living through β€œsmall layout tickets that touch every client,” you are already in this use case.

How to split the system

A clear way to draw the boundaries:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Server / remote render document          β”‚
β”‚  Which sections exist, and in what order  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Shared layer (e.g. Kotlin Multiplatform) β”‚
β”‚  Screen definition, navigation targets    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Native presentation                      β”‚
β”‚  Android Compose Β· SwiftUI          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Navigation (Router)                      β”‚
β”‚  Tab switches, pushed screens, links      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Read it top to bottom as a lesson: who owns structure, who owns the contract, who owns presentation, who owns navigation between screens. Each layer should have one job. The design is about what can cross a boundary β€” and what should not.

Structure vs presentation

Teach this distinction early β€” it is what keeps SDUI from becoming β€œremote Views”:

The render document defines section types and order. Different brands reuse the same types with different composition.

On the UI side, map each section type to a native component. Views should render a typed model β€” they should not parse raw JSON and invent layout policy themselves. That keeps presentation testable and keeps the document boring (in a good way).

Why a shared layer helps

A shared module (Kotlin Multiplatform is one option) can own:

Both apps then ask the same question from the same place: what should this screen contain? Parsing and validation live here so you do not maintain two incompatible models. This layer is not UI β€” that is the whole point.

SwiftUI’s job on iOS

On iOS, SwiftUI owns the experience: layout polish, accessibility, gestures, platform conventions. Translate the shared contract into a small set of presentation models the view layer understands β€” the same mapping idea Android uses for its native components.

Rule of thumb: if it is visual behavior, it is native. If it is composition of the screen, it is server-driven data.

Navigation as its own concern

The document can say β€œopen Upcoming” or β€œopen this URL.” That is a destination, not a navigation implementation. A router turns destinations into actions: switch tab, push detail, open in-app browser.

Views report intent; they do not own the full navigation graph. As the home screen grows, that separation keeps the flow readable on both platforms.

Checklist you can take to your team

What you gain (and what you trade)

You pay a bit of upfront structure: contracts, mappers, a router, and a delivery path. You gain a screen model that scales across platforms and brands β€” layout experiments and white-label composition can move with the server while clients stay native and shippable.

If that sounds like a problem your team already has β€” multi-platform home screens, white-label variants, layout tickets that never stay in sync β€” this SDUI design is meant to be reusable. Start with the shared contract and native maps; wire live delivery when product needs it.

Next in the series: how the mapping layer turns shared models into SwiftUI views, how navigation stays aligned with Android, and how one SwiftUI base supports more than one brand.

I build native iOS and Android apps and write about designs that survive real shipping. If this is a problem your team is facing, let's connect on LinkedIn.