Skip to content

Compose

petterp edited this page Aug 30, 2026 · 2 revisions

Compose

floatingx-compose lets the window content be a composition: compose { control -> … }. The composition runs on the window's own FxComposeOwner, so viewModel() and rememberSaveable state is not cleared when the host Activity is destroyed. The module is minSdk 23, see Getting Started.

Usage

FloatingX.install("compose") {
    compose { control ->
        val vm: CounterViewModel = viewModel()                        // the window's own ViewModelStore
        var count by rememberSaveable { mutableIntStateOf(0) }        // survives container detach
        val state by control.stateFlow().collectAsState()             // FxState
        val pos by control.positionFlow().collectAsState()            // screen coords of the content's top-left
        Surface(shape = CircleShape, modifier = Modifier.size(110.dp)) {
            Column(Modifier.clickable { count++; vm.clicks++ }) {
                Text("count $count")
                Text("${pos.x.toInt()},${pos.y.toInt()} $state")
            }
        }
    }
    anchor(FxGravity.CENTER_START, dy = -100f)
    appHost(app)
}.show()

compose {} only sets the content; every other option (anchor / adsorption / gestures / animation / persistence) works exactly as it does for View content, see Configuration.

FxComposeOwner: state survives page changes

Every window owns an FxComposeOwner, which is at once a LifecycleOwner, ViewModelStoreOwner and SavedStateRegistryOwner. It belongs to the control, not to the host Activity, and follows the window:

Window action owner's Lifecycle.State
attach (container attached to the host) STARTED
show RESUMED
detach (page change, blacklist detach, rotation recreate) CREATED
cancel() DESTROYED (terminal)

Only cancel() reaches DESTROYED, so page changes, rotation and blacklist detach never restart the composition (#210/#239).

  • viewModel() lands in the owner's ViewModelStore; it lives from install to cancel, independent of any page.
  • rememberSaveable goes through the window's own bridging registry: saved when the composition is disposed (container detach) and restored on recomposition.

Does rememberSaveable inside compose {} survive process death? No. It is saved in-process only: when the composition is disposed (container detach) the state goes into the window's own bridging registry and is restored on recomposition. Kill the process and it is gone (the window itself is not recreated either). Persist it yourself if you need more.

stateFlow / positionFlow

public fun FxControl.stateFlow(): StateFlow<FxState>
public fun FxControl.positionFlow(): StateFlow<FxPoint>
  • stateFlow() — the window state (INSTALLED / ATTACHED / SHOWN / CANCELLED).
  • positionFlow() — the screen coordinates of the content's top-left (the same as control.position; updated every frame while dragging and when moveTo or adsorption finishes). Note this differs from the x/y in the FxListener.onDrag/onDragEnd callbacks, which are relative to the container.
  • Both must be called on the main thread, and the flows are only updated there; repeated calls on one control return the same flow.
  • After cancel() core clears its listeners, so the flow stops on its last value.

Agnostic to the host

compose {} does not care where the window is attached — app-level and system windows are written exactly the same way, only the host lines change:

FloatingX.install("compose-sys") {
    compose { control -> /* exactly the same content as above */ }
    anchor(FxGravity.CENTER_START, dy = 60f)
    systemHost(app) {
        permission(FxPermissionStrategy.auto())
        fallback(AppHost.builder(app).build())
    }
}.show()

When a denied permission downgrades it to App Host the container is swapped, but the owner belongs to the control, so the composition state is still kept. The permission strategies live in System Host.


Back to Home · Related: Getting Started Configuration App Host System Host

Clone this wiki locally