Orbit Multiplatform for Kotlin MVI State Management
A simple MVI framework for Kotlin Multiplatform and Android
At a glance
- What is it?
- Orbit Multiplatform is a Kotlin MVI framework for sharing state-driven business logic across Android, iOS and desktop. Its main trade-off is that adopters accept Orbit containers, intents, reductions and a specific ViewModel integration model rather than using only generic Kotlin flows.
- Who is it for?
- Orbit Multiplatform fits Kotlin teams that want one MVI state-management layer across Android, iOS and desktop, especially when Compose Multiplatform or shared ViewModel logic matters. It is less suitable for a small screen that needs only a local StateFlow or a project that does not want Orbit's container and intent model.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 53 days ago.
- What is it written in?
- Mainly Kotlin, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What Orbit Multiplatform Solves
Orbit addresses the problem of keeping business logic consistent when a Kotlin application presents interfaces on more than one platform. The README positions it as a simple, type-safe MVI framework for Kotlin Multiplatform, with shared logic across Android, iOS and desktop. The intended audience is developers building MVVM-based applications, particularly teams that want the state-management mechanism itself to remain common code while platform-specific views handle rendering.
The framework gives application code two distinct channels. State represents the current screen or business state and is intended for rendering. Side effects represent one-off actions such as displaying a toast, navigation or another event that should not become persistent UI state. This separation makes the calculator example concrete: its state contains `total`, while `CalculatorSideEffect.Toast` carries a transient message.
Orbit also provides integrations for Android and common ViewModel behavior, Compose and Compose Multiplatform, testing utilities and Espresso idling-resource support. That makes it broader than a single reducer function, but it also means adoption involves learning several modules rather than adding one isolated helper. The project is not archived. Its last push was on 2026-08-31, and the listed releases include 12.0.1 on 2026-08-28. That supports recent project activity, although it does not by itself establish compatibility with a particular application.
Containers, Intents and Reductions
The documented architecture is built around `OrbitContainerHost`, `OrbitContainer` and `container`. A host exposes a container, and intents are functions that can read the current state, reduce it and post side effects. The README's calculator implementation uses `orbitContainer<CalculatorState, CalculatorSideEffect>(CalculatorState())`, so the initial state and side-effect type are established when the container is created.
The data flow is unidirectional. The interface calls `viewModel.add(1234)`, which enters an intent. The intent posts a `CalculatorSideEffect.Toast`, then reduces the existing state by copying it with a new total. The Activity or Fragment observes the state through `observe`, while the Compose example collects state and side effects through separate APIs. This structure keeps rendering separate from business-state transitions, which is the central MVI convention in the project.
The type parameters make the intended channels visible at the contract boundary. `CalculatorState` describes the persistent state, and `CalculatorSideEffect` describes transient effects. The example does not show a separate event type for UI input. Input is represented by methods such as `add(number: Int)`, while output is split into observable state and side effects. Orbit's documentation also describes lifecycle-safe flows and state saving, but the README does not provide the implementation details or configuration needed to evaluate those behaviors for a particular screen.
Adding Orbit to a Kotlin Project
The README directs new users to the Orbit documentation and gives Gradle dependencies for the main integrations. The following block is the documented dependency setup. The `<latest-version>` placeholder is retained because the README does not specify a fixed version in its dependency examples.
implementation("org.orbit-mvi:orbit-core:<latest-version>")
implementation("org.orbit-mvi:orbit-viewmodel:<latest-version>")
implementation("org.orbit-mvi:orbit-compose:<latest-version>")
testImplementation("org.orbit-mvi:orbit-test:<latest-version>")Use `orbit-core` when the application needs the core state container and unidirectional data flow. Add `orbit-viewmodel` for the documented Android and common ViewModel integration. Add `orbit-compose` when using Jetpack Compose or Compose Multiplatform. The testing dependency provides utilities for verifying state and event flows. The repository includes separate top-level modules for these areas, including `orbit-core/`, `orbit-viewmodel/`, `orbit-compose/` and `orbit-test/`.
The project uses SemVer, according to the README, and points readers to repository tags for available versions. Before copying dependencies into a build, check the relevant tag and the project documentation because the README cannot establish artifact compatibility for an unspecified Kotlin, Android or Compose configuration.
Building the Calculator Contract and Container
A first implementation starts with the state and side-effect types. The following example is taken from the README's calculator contract. The state has a default total of zero, and the sealed side-effect class contains the toast message shown by the example.
data class CalculatorState(
val total: Int = 0
)
sealed class CalculatorSideEffect {
data class Toast(val text: String) : CalculatorSideEffect()
}Next, the class implements `OrbitContainerHost` and exposes an Orbit container. In the documented Android example, it also extends `ViewModel`, although the README explicitly says that using an Android `ViewModel` is not required. The `add` method is an intent: it posts a side effect and then updates state through `reduce`.
class CalculatorViewModel: OrbitContainerHost<CalculatorState, CalculatorState, CalculatorSideEffect>, ViewModel() {
override val container = orbitContainer<CalculatorState, CalculatorSideEffect>(CalculatorState())
fun add(number: Int) = intent {
postSideEffect(CalculatorSideEffect.Toast("Adding $number to ${state.total}!"))
reduce {
state.copy(total = state.total + number)
}
}
}The example's generic declaration is `OrbitContainerHost<CalculatorState, CalculatorState, CalculatorSideEffect>`, while the `orbitContainer` factory shown after it uses the state and side-effect types. Preserve the type relationships from the current documentation when adapting the example, because the README excerpt does not explain every generic parameter.
Collecting State in Android and Compose
An Android Activity or Fragment sends an intent and subscribes to the two output channels. The README uses a Koin-style `by viewModel<CalculatorViewModel>()` example, while noting that a different dependency injection system may be used. The exact Activity code contains ellipses where layout creation and button lookup are omitted, so the documented wiring is the part to follow.
addButton.setOnClickListener { viewModel.add(1234) }
viewModel.observe(state = ::render, sideEffect = ::handleSideEffect)The callbacks are application code. `render` receives `CalculatorState`, and `handleSideEffect` receives `CalculatorSideEffect`. The latter uses a `when` expression to display the toast. The Activity example therefore keeps a persistent state callback separate from a transient effect callback.
For Compose Multiplatform, the README shows a screen that collects state as a value and collects side effects through `collectSideEffect`. The following is the documented connection pattern. The final UI rendering is intentionally represented by an ellipsis in the README.
@Composable
fun CalculatorScreen(viewModel: CalculatorViewModel) {
val state = viewModel.collectAsState().value
viewModel.collectSideEffect { handleSideEffect(it) }
}This is useful when the screen model should be shared across platform views. It also requires the chosen module and platform setup to match the project. The README mentions lifecycle-safe flows and saved state, but it does not document restart behavior, process-death details or cancellation semantics in the available documentation.
Testing, Alternatives and Adoption Limits
Orbit includes a dedicated testing area. The README lists unit tests and Espresso idling-resource support, and the `orbit-test` dependency is described as providing utilities for verifying state and event flows. That is relevant for teams that need assertions around emitted state or side effects, although the README does not show a complete test or explain matcher names. The repository includes `test-common/`, and the documentation has a separate Test page.
The closest documented alternative is not a named competitor but a simpler architecture using ordinary Kotlin state flows and application-defined reducers. The difference is architectural: Orbit supplies a container abstraction, intent methods, reductions, side-effect channels, ViewModel integration and Compose helpers in one framework. A lightweight ViewModel with a `StateFlow` can cover screens that only need state exposure and do not benefit from Orbit's conventions. Orbit is the wrong tool when the application needs a small local state holder and adopting several modules would add more structure than the screen requires.
There is also a coordination cost. State and side effects have distinct types, and consumers must implement matching handling paths. A missing collector or an unhandled side-effect subtype can leave UI behavior incomplete, although the README does not document compiler exhaustiveness guarantees for every platform API. The framework is aimed at shared MVI logic, not every Kotlin UI problem. Its multiplatform value is strongest when business logic is genuinely common across Android, iOS and desktop.
License, Releases and Upgrade Work
Orbit Multiplatform is licensed under Apache License 2.0, and the repository includes `LICENSE.md` and `NOTICE.md`. The license identifies the project's licensing terms, but it does not answer application-specific legal questions or remove the need to review dependencies separately.
The repository's recent release list includes 12.0.1 on 2026-08-28, 12.0.0 on 2026-07-13 and a 12.0.0-3-SNAPSHOT entry on 2026-07-12. The README states that Orbit follows SemVer and directs users to repository tags for available versions. That gives adopters a version-selection mechanism, but the README does not describe migration steps between releases. Check `CHANGELOG.md`, the selected tag and the module documentation before upgrading an existing application.
The top-level modules and the separate documentation areas indicate a broad framework rather than a single artifact. An upgrade may therefore require reviewing core container APIs, ViewModel integration, Compose integration and test utilities separately. The repository's last push was on 2026-08-31 and it is not archived. The practical maintenance signal is release and changelog review, not an assumption that every module changes at the same rate.
Editorial conclusion
Orbit Multiplatform fits Kotlin teams that want one MVI state-management layer across Android, iOS and desktop, especially when Compose Multiplatform or shared ViewModel logic matters. It is less suitable for a small screen that needs only a local StateFlow or a project that does not want Orbit's container and intent model. Verify first that the chosen artifact version, ViewModel integration and lifecycle behavior match the application's architecture. The repository lists release 12.0.1 on 2026-08-28, with the last push on 2026-08-31, but migration effort should be assessed from the changelog for the version being adopted.
Frequently asked questions
What is Orbit Multiplatform used for?
Orbit Multiplatform is a Kotlin MVI framework for sharing business logic across Android, iOS and desktop. It uses state, intents, reductions and side effects to support unidirectional data flow.
How do I add Orbit to a Kotlin Multiplatform project?
The README documents Gradle dependencies for `orbit-core`, `orbit-viewmodel` and `orbit-compose`, plus `orbit-test` for test utilities. Its examples use the `<latest-version>` placeholder, so the repository tags and documentation should determine the version to enter.
Does Orbit Multiplatform require an Android ViewModel?
No. The README uses an Android `ViewModel` for its most common example, but explicitly states that there is no requirement to do so. Orbit provides separate Android and common ViewModel integration documentation.
How does Orbit handle state and one-time UI effects?
The documented pattern observes state separately from side effects. In the calculator example, `reduce` updates the persistent `CalculatorState`, while `postSideEffect` sends a `CalculatorSideEffect` such as a toast.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/orbit-mvi-orbit-mvi)Was this useful?