Skip to content

Orbit

A Compose-driven architecture for Kotlin Multiplatform.

A presenter is a @Composable that returns state. A UI is a @Composable that renders that state and sends events back. A screen is the key that pairs the two. That is the whole model.

@Composable
override fun present(): CounterState {
    var count by rememberRetained { mutableIntStateOf(0) }
    return CounterState(count) { event ->
        when (event) {
            CounterEvent.Increment -> count++
        }
    }
}

Because presenters are Composables rather than ViewModels, they are ordinary Kotlin with no Android dependencies — the same presenter runs on Android, Desktop and iOS, and can be tested without an emulator.

Install

dependencies {
    implementation("io.github.avelon1a:orbit-foundation:0.0.11")
    implementation("io.github.avelon1a:orbit-overlay:0.0.11")
    testImplementation("io.github.avelon1a:orbit-test:0.0.11")
}
[versions]
orbit = "0.0.11"

[libraries]
orbit-foundation = { module = "io.github.avelon1a:orbit-foundation", version.ref = "orbit" }
orbit-overlay = { module = "io.github.avelon1a:orbit-overlay", version.ref = "orbit" }
orbit-test = { module = "io.github.avelon1a:orbit-test", version.ref = "orbit" }

Modules

Artifact Contents Compose dependency
orbit-runtime Screen, Presenter, Navigator, PopResult, rememberRetained compose-runtime only
orbit-foundation Orbit registry, Ui, OrbitContent, back stack, nav decorations compose-ui, foundation, animation
orbit-overlay Overlay, OverlayHost, ContentWithOverlays compose-ui, foundation
orbit-test Presenter.test { }, FakeNavigator, FakeOverlayHost test only

orbit-foundation brings orbit-runtime with it. Feature modules that contain presenters but no UI should depend on orbit-runtime alone — it never pulls in compose-ui, which keeps presenter modules light and fast to compile.

What is different here

No Parcelable. Screen is a plain commonMain interface. Persistence runs through kotlinx-serialization on every platform, so there is no @Parcelize, no expect/actual annotation shim, and no Android-only supertype leaking into shared code.

No annotation processor. Presenter and UI factories are registered by hand on Orbit.Builder. Nothing is generated, so nothing is hidden.

Overlays return values. overlayHost.show(overlay) suspends and returns the user's choice, which removes the usual showDialog / onDismiss state juggling.

State that outlives configuration changes. rememberRetained keeps values across rotation and across navigating away and back, without you writing a ViewModel.

Where to go next

License

Apache 2.0.