Immutable event-based state management for Kotlin Multiplatform
Revolver is a Kotlin Multiplatform state management library that enforces a single immutable state and unidirectional data flow. Clients send Events to a ViewModel, which processes them and emits States and Effects back via Kotlin Flows.
Client ──emit(Event)──► RevolverViewModel ──► EventHandler
│
┌─────────────────────┤
▼ ▼
StateFlow<State> SharedFlow<Effect>
│ │
└──────────► Clients ◄┘
| Type | Direction | Purpose |
|---|---|---|
RevolverEvent |
Client → ViewModel | User actions or lifecycle triggers |
RevolverState |
ViewModel → Client | Immutable snapshot of what to display |
RevolverEffect |
ViewModel → Client | One-shot side effect (navigation, toast, etc.) |
Add the GitHub Packages Maven repository to your settings.gradle.kts:
dependencyResolutionManagement {
repositories {
maven {
url = uri("https://maven.pkg.github.com/apegroup/revolver/")
credentials {
username = System.getenv("GH_USERNAME") ?: ""
password = System.getenv("GH_TOKEN") ?: ""
}
}
}
}Then declare the dependency in your commonMain source set:
implementation("com.umain:revolver:1.6.0")sealed class ExampleEvent : RevolverEvent {
object Refresh : ExampleEvent()
}
sealed class ExampleState : RevolverState {
object Loading : ExampleState()
data class Loaded(val result: String) : ExampleState()
data class Error(val message: String) : ExampleState()
}
sealed class ExampleEffect : RevolverEffect {
data class ShowToast(val message: String) : ExampleEffect()
}Prefer
objectfor states with no data anddata classfor states that carry data. The state flow skips values equal to the current one, so structural equality avoids redundant updates.
class ExampleViewModel : RevolverViewModel<ExampleEvent, ExampleState, ExampleEffect>(
initialState = ExampleState.Loading,
) {
init {
addEventHandler<ExampleEvent.Refresh>(::onRefresh)
addErrorHandler(RevolverDefaultErrorHandler(ExampleState.Error("Something went wrong")))
}
private suspend fun onRefresh(
event: ExampleEvent.Refresh,
emit: Emitter<ExampleState, ExampleEffect>,
) {
emit.state(ExampleState.Loading)
val data = fetchData()
emit.state(ExampleState.Loaded(data))
emit.effect(ExampleEffect.ShowToast("Loaded!"))
}
}@Composable
fun ExampleScreen(viewModel: ExampleViewModel) {
val state by viewModel.state.collectAsState()
val context = LocalContext.current
LaunchedEffect(Unit) {
viewModel.emit(ExampleEvent.Refresh)
viewModel.effect.collect { effect ->
when (effect) {
is ExampleEffect.ShowToast -> Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show()
}
}
}
when (val s = state) {
is ExampleState.Loading -> CircularProgressIndicator()
is ExampleState.Loaded -> Text(s.result)
is ExampleState.Error -> Text(s.message)
}
}For iOS, see iOS integration.
The docs/ directory is an Open Knowledge Format bundle: plain markdown with YAML frontmatter that people and AI agents can both read.
| Page | Covers |
|---|---|
| Getting started | Installation, platform support, first ViewModel |
| Core concepts | Event, State, Effect, Emitter, and runtime behaviour |
| Error handling | Built-in, per-type and reusable error handlers |
| Testing | Testing ViewModels with Turbine and kotlinx-coroutines-test |
| iOS integration | watch, DisposableHandle, SwiftUI and UIKit patterns |
| API reference | Every public type and function |
Bug reports, feature requests, and pull requests are welcome. This library is in active development — production use is at your own discretion.
Run the test suite with ./scripts/run_test_suite.sh, and publish to your local Maven repository with ./scripts/build_deploy_to_local_maven.sh.