
Opinionated application state management framework with support for multiple targets, focusing on the MVI pattern. Features include handling user inputs, updating state, and integration with various UI toolkits.
Opinionated Application State Management framework for Kotlin Multiplatform
object TodosContract {
data class State(
val loading: Boolean = false,
val todos: List<String> = emptyList(),
)
sealed interface Inputs {
data object FetchSavedTodos : Inputs
data class AddTodo(val text: String) : Inputs
data class RemoveTodo(val text: String) : Inputs
}
}
class TodosInputHandler : InputHandler<Inputs, Events, State> {
override suspend fun InputHandlerScope<Inputs, Events, State>.handleInput(
input: TodosContract.Inputs
) = when (input) {
is FetchSavedTodos -> {
updateState { copy(loading = true) }
val todos = todosApi.fetchTodos()
updateState { copy(loading = false, todos = todos) }
}
is AddTodo -> updateState { copy(todos = todos + input.text) }
is RemoveTodo -> updateState { copy(todos = todos - input.text) }
}
}
@Composable
fun App() {
val coroutineScope = rememberCoroutineScope()
val vm = remember(coroutineScope) { TodosViewModel(coroutineScope) }
val vmState by vm.observeStates().collectAsState()
LaunchedEffect(vm) { vm.send(TodosContract.FetchSavedTodos) }
TodosList(vmState, postInput = { vm.trySend(it) })
}This snippet omits some details for brevity. See Getting Started for a complete walkthrough.
Ballast was intentionally designed to not be tied to any particular platform or UI toolkit. It works in any Kotlin target that supports Coroutines and Flows. The following platforms are officially supported and tested:
| Platform | Supported |
|---|---|
| JVM | ✅ |
| Android | ✅ |
| iOS | ✅ |
| JS | ✅ |
| WASM JS | ✅ |
repositories {
mavenCentral()
}
// for plain JVM or Android projects
dependencies {
implementation("io.github.copper-leaf:ballast-core:{{ballastVersion}}")
testImplementation("io.github.copper-leaf:ballast-test:{{ballastVersion}}")
}
// for multiplatform projects
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.github.copper-leaf:ballast-core:{{ballastVersion}}")
}
}
val commonTest by getting {
dependencies {
implementation("io.github.copper-leaf:ballast-test:{{ballastVersion}}")
}
}
}
}Other modules can be added as needed. See docs/README.md for the full list.
Ballast ships an llms.txt file — a plain-Markdown context file for AI coding assistants. It covers core concepts, APIs, module list, and common pitfalls.
To use it, copy the file into your project's agent rules location:
| Agent | Location |
|---|---|
| Claude Code |
CLAUDE.md or .claude/rules/ballast.md
|
| Cursor | .cursor/rules/ballast.mdc |
| GitHub Copilot | .github/copilot-instructions.md |
| Windsurf | .windsurfrules |
| Other | Wherever your agent reads Markdown context |
Full documentation is in the docs/ directory:
Join us on the Kotlin Slack in the #ballast channel for
support or to show off what you're building with Ballast.
Ballast is licensed under the BSD 3-Clause License, see LICENSE.md.
Ballast was built upon years of experience building UI applications and observing the direction UI programming has gone. The MVI model has proven itself robust across a wide range of applications, programming languages, and API surfaces. Ballast is not the only MVI library in Kotlin, but it is unique in being a highly opinionated and highly structured MVI library, which brings certain advantages. The feature comparison is a detailed breakdown of similarities and differences among the many libraries that were consulted as a large inspiration for Ballast. But by far, these 3 links were the most helpful in shaping how Ballast works and looks, and studying these resources may give you a deeper understanding of why Ballast was built the way that it was.
Opinionated Application State Management framework for Kotlin Multiplatform
object TodosContract {
data class State(
val loading: Boolean = false,
val todos: List<String> = emptyList(),
)
sealed interface Inputs {
data object FetchSavedTodos : Inputs
data class AddTodo(val text: String) : Inputs
data class RemoveTodo(val text: String) : Inputs
}
}
class TodosInputHandler : InputHandler<Inputs, Events, State> {
override suspend fun InputHandlerScope<Inputs, Events, State>.handleInput(
input: TodosContract.Inputs
) = when (input) {
is FetchSavedTodos -> {
updateState { copy(loading = true) }
val todos = todosApi.fetchTodos()
updateState { copy(loading = false, todos = todos) }
}
is AddTodo -> updateState { copy(todos = todos + input.text) }
is RemoveTodo -> updateState { copy(todos = todos - input.text) }
}
}
@Composable
fun App() {
val coroutineScope = rememberCoroutineScope()
val vm = remember(coroutineScope) { TodosViewModel(coroutineScope) }
val vmState by vm.observeStates().collectAsState()
LaunchedEffect(vm) { vm.send(TodosContract.FetchSavedTodos) }
TodosList(vmState, postInput = { vm.trySend(it) })
}This snippet omits some details for brevity. See Getting Started for a complete walkthrough.
Ballast was intentionally designed to not be tied to any particular platform or UI toolkit. It works in any Kotlin target that supports Coroutines and Flows. The following platforms are officially supported and tested:
| Platform | Supported |
|---|---|
| JVM | ✅ |
| Android | ✅ |
| iOS | ✅ |
| JS | ✅ |
| WASM JS | ✅ |
repositories {
mavenCentral()
}
// for plain JVM or Android projects
dependencies {
implementation("io.github.copper-leaf:ballast-core:{{ballastVersion}}")
testImplementation("io.github.copper-leaf:ballast-test:{{ballastVersion}}")
}
// for multiplatform projects
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.github.copper-leaf:ballast-core:{{ballastVersion}}")
}
}
val commonTest by getting {
dependencies {
implementation("io.github.copper-leaf:ballast-test:{{ballastVersion}}")
}
}
}
}Other modules can be added as needed. See docs/README.md for the full list.
Ballast ships an llms.txt file — a plain-Markdown context file for AI coding assistants. It covers core concepts, APIs, module list, and common pitfalls.
To use it, copy the file into your project's agent rules location:
| Agent | Location |
|---|---|
| Claude Code |
CLAUDE.md or .claude/rules/ballast.md
|
| Cursor | .cursor/rules/ballast.mdc |
| GitHub Copilot | .github/copilot-instructions.md |
| Windsurf | .windsurfrules |
| Other | Wherever your agent reads Markdown context |
Full documentation is in the docs/ directory:
Join us on the Kotlin Slack in the #ballast channel for
support or to show off what you're building with Ballast.
Ballast is licensed under the BSD 3-Clause License, see LICENSE.md.
Ballast was built upon years of experience building UI applications and observing the direction UI programming has gone. The MVI model has proven itself robust across a wide range of applications, programming languages, and API surfaces. Ballast is not the only MVI library in Kotlin, but it is unique in being a highly opinionated and highly structured MVI library, which brings certain advantages. The feature comparison is a detailed breakdown of similarities and differences among the many libraries that were consulted as a large inspiration for Ballast. But by far, these 3 links were the most helpful in shaping how Ballast works and looks, and studying these resources may give you a deeper understanding of why Ballast was built the way that it was.