Skip to content

ComposableView: custom Android views and Compose functions - #39

Merged
colemancda merged 9 commits into
masterfrom
feature/composable-registry
Jul 23, 2026
Merged

ComposableView: custom Android views and Compose functions#39
colemancda merged 9 commits into
masterfrom
feature/composable-registry

Conversation

@colemancda

Copy link
Copy Markdown
Member

The library's UIViewRepresentable-equivalent. Because a @Composable (or an Android View) can never be authored in Swift, the seam is a named registry: Kotlin registers name → composable factories once at startup, and Swift references one by name.

API

SwiftComposableView(_ name:props:content:) emits a node that references a registered factory, forwarding typed props and an optional slot of SwiftUI child content:

ComposableView("RatingBar", props: ["rating": .double(stars), "max": 5])
ComposableView("DashedBorder", props: ["color": .color(.blue)]) {
    Text("These SwiftUI views sit inside a Compose-drawn border")
}

PropValue gained literal conveniences, so props read as a plain dictionary (["url": "…", "zoom": 1.5, "on": true]).

KotlinComposableRegistry.register(name) { props, children -> … }. A factory that wraps a legacy Android View (maps, WebView, an ad SDK) uses Compose's own AndroidView inside; props is a typed accessor (props.float("rating")), so app authors never touch the JSON layer. Unregistered names render a visible diagnostic.

The interesting bug

Child content initially rendered nothing. Root cause (found by bisecting the render path on-device): a nested call to a public composable from a dynamically-invoked factory slot has its restart group skipped by the Compose runtime — the subtree silently fails to compose. Fixed by routing the registry slot through an internal RenderChild entry point; the public Render now delegates to it. (Also: the registry now stores a plain composable function type rather than a fun interface.)

Verification

  • swift test — 2 new emission tests (typed props + child forwarding), 60 total passing
  • Emulator (new Custom Views screen): a native android.widget.RatingBar that reactively updates as the Swift @State changes (3.5 → 4.5 via ±½ buttons); a Compose DashedBorder drawing around two SwiftUI Texts in its slot; and the red unregistered-name diagnostic

Deferred

Two-way callbacks from a custom composable back into Swift state (props flow Swift→Kotlin only for now); desktop registration of Android-view factories (they're Android-only by nature).

@colemancda
colemancda merged commit 7eab81c into master Jul 23, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant