Loading repository data…
Loading repository data…
Fuusio / repository
Kide is a Kotlin Multiplatform (KMP) library for building applications with a strict MVI (Model–View–Intent) presentation layer and (optional) Clean Architecture layering. It targets Android, iOS. and JVM (desktop), and integrates with Compose Multiplatform, Navigation 3, Koin, Decompose, and Voyager through small optional modules.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
The First AI-Agent Native MVI Architecture for Kotlin Multiplatform (KMP).
Kide is a modern, strict MVI (Model–View–Intent) and Clean Architecture library built specifically for the era of AI-assisted development. Targeting Android, iOS, and JVM (desktop), it integrates seamlessly with Compose Multiplatform, Navigation 3, Koin, Decompose, and Voyager.
The library takes its name and logo from the Finnish word for "crystal": kide, reflecting its transparent, predictable, and indestructible architecture.
For a comprehensive overview of core concepts, architecture diagrams, and practical instructions for development, debugging, and testing, check out the Kide Developer Guide.
FlightRecorder. Your AI agent can connect directly to your running app, dispatch intents, read state traces, find bugs, and instantly generate regression tests.ScreenNavKey-based design keeps each destination self-contained — a type-safe key ties a screen to its processor, so features register their own destinations independently, arguments and back stack survive process death, and navigation stays decoupled and testable without a central navigation graph.Kide's AI-first debugging approach (FlightRecorder, interceptors, and MCP agent ports) is something none of the other current MVI libraries offer. As AI coding assistants become mandatory tools for development teams, an architecture built specifically to be understood and debugged by AI is a massive strategic advantage.
Classic MVI debug tooling renders a GUI for human eyes. Kide goes further by targeting the entity that increasingly does your debugging: your AI coding agent.
Using the kide-devtools module, Kide keeps a queryable, causally ordered trace of a processor's life. An embedded MCP server exposes your running app as agent tools:
// In your debug builds:
val recorder = FlightRecorder<SearchIntent, SearchViewState, SearchSideEffect>()
val processor = SearchProcessor(useCase, interceptors = listOf(recorder))
KideDebug.attach("search", processor, recorder)
KideMcpServer.start(context)
What can your agent do?
Connect your agent (like Claude or Antigravity) to http://localhost:8765/mcp.
You can now simply ask: "Why is isLoading stuck on the search screen?"
Your agent will read the trace, correlate it with your source code, reproduce the bug by dispatching live intents into your emulator, and generate a Kotest replay scaffold for you automatically.
Kide is highly decoupled. Use only what you need:
| Module | Contents | Depends on |
|---|---|---|
kide | Core MVI engine: PresentationProcessor, actions, interceptors, KideLog | kotlinx-coroutines |
kide-navigation | Navigation 3 based navigation: ScreenNavKey, AppNavigation, back-stack persistence | kide, Compose, Navigation 3 |
kide-clean-architecture | Clean Architecture building blocks: use cases, repositories, services, features | kotlinx-coroutines |
kide-koin | Koin dependency-injection helpers | Koin |
kide-test | Fluent testing DSL for PresentationProcessor | kide, Turbine, kotlinx-coroutines-test |
kide-clean-architecture-test | Testing DSL for UseCaseProcessor | kide-clean-architecture, Turbine, kotlinx-coroutines-test |
kide-devtools | Debug tooling: FlightRecorder, MCP agent port, console event streaming | kide, kotlinx-serialization |
kide-decompose | InstanceKeeperHost for hosting processors in Decompose | kide, Essenty |
kide-voyager | ScreenModelHost for hosting processors in Voyager | kide, Voyager |
app | Sample Android application exercising the full stack | all of the above |
Kide is published to Maven Central under the group org.fuusio.kide. The latest
release is 1.1.0.
Make sure mavenCentral() is in your repositories (in settings.gradle.kts):
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
Add only the modules you need. In a Kotlin Multiplatform project, put the shared modules
in commonMain:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("org.fuusio.kide:kide:1.1.0") // Core MVI engine
implementation("org.fuusio.kide:kide-navigation:1.1.0") // Navigation 3
implementation("org.fuusio.kide:kide-clean-architecture:1.1.0") // Clean Architecture
implementation("org.fuusio.kide:kide-koin:1.1.0") // Koin DI helpers
implementation("org.fuusio.kide:kide-decompose:1.1.0") // Decompose host
implementation("org.fuusio.kide:kide-voyager:1.1.0") // Voyager host
}
commonTest.dependencies {
implementation("org.fuusio.kide:kide-test:1.1.0") // Presentation testing DSL
implementation("org.fuusio.kide:kide-clean-architecture-test:1.1.0") // Use-case testing DSL
}
}
}
For debug builds only, add the agent-native debug tooling:
// e.g. an androidMain / debug source set
implementation("org.fuusio.kide:kide-devtools:1.1.0")
For a single-platform (e.g. Android-only) project, declare them in the regular
dependencies { } block instead:
dependencies {
implementation("org.fuusio.kide:kide:1.1.0")
testImplementation("org.fuusio.kide:kide-test:1.1.0")
}
If you use a libs.versions.toml catalog, declare a shared version and the artifacts:
[versions]
kide = "1.1.0"
[libraries]
kide = { module = "org.fuusio.kide:kide", version.ref = "kide" }
kide-navigation = { module = "org.fuusio.kide:kide-navigation", version.ref = "kide" }
kide-clean-architecture = { module = "org.fuusio.kide:kide-clean-architecture", version.ref = "kide" }
kide-koin = { module = "org.fuusio.kide:kide-koin", version.ref = "kide" }
kide-decompose = { module = "org.fuusio.kide:kide-decompose", version.ref = "kide" }
kide-voyager = { module = "org.fuusio.kide:kide-voyager", version.ref = "kide" }
kide-test = { module = "org.fuusio.kide:kide-test", version.ref = "kide" }
kide-clean-architecture-test = { module = "org.fuusio.kide:kide-clean-architecture-test", version.ref = "kide" }
kide-devtools = { module = "org.fuusio.kide:kide-devtools", version.ref = "kide" }
Then reference them from your build script:
commonMain.dependencies {
implementation(libs.kide)
implementation(libs.kide.navigation)
}
commonTest.dependencies {
implementation(libs.kide.test)
}
Kide's presentation layer is a unidirectional data flow built around one class,
PresentationProcessor<I, S, E>:
flowchart TD
UI[Compose UI] -- dispatch(ViewIntent) --> PP[PresentationProcessor]
PP -- map() --> Action[Action]
Action -- reduce --> VS[ViewState]
Action -- async work --> VS
Action -- sideEffect --> SE[SideEffect]
VS -- collectAsState --> UI
SE -- collect --> UI
ViewIntent describes a user interaction or UI event.Action (or null for a no-op).ViewState (the single source of truth the UI renders),
or emit one-time SideEffects (navigation, toasts, …), or run asynchronous work.AsyncAction runs in its own coroutine; long-running work
never stalls the intent loop. Executions can be coalesced with a cancellation key:
dispatching an action with the same key cancels the previous, still-running one.KideInterceptor.onError) and to the
processor's overridable onError, and the loop continues with the next intent.kotlinx.serialization. No custom saver abstractions required!KideLog), or custom monitoring.UseCaseProcessors that can emit domain-state changes directly to your processors.data class SearchViewState(
val query: String = "",
val results: List<Project> = emptyList(),
val isLoading: Boolean = false,
) : ViewState
sealed interface SearchIntent : ViewIntent {
data class UpdateQuery(val query: String) : SearchIntent
data object TriggerSearch : SearchIntent
}
sealed interface SearchSideEffect : SideEffect {
data class ShowToast(val message: String) : SearchSideEffect
}
class SearchProcessor(
private val searchUseCase: SearchGitHubProjectsUseCase,
) : PresentationProcessor<SearchIntent, SearchViewState, SearchSideEffect>(SearchViewState()) {
override suspend fun map(intent: SearchIntent): Action<SearchViewState, SearchSideEffect>? =
when (intent) {
is SearchIntent.UpdateQuery -> reduce { copy(query = intent.query) }
SearchIntent.TriggerSearch ->
if (state.query.isBlank()) {
sideEffect { SearchSideEffect.ShowToast("Query cannot be empty") }
} else {
composite(
reduce { copy(isLoading = true) },
async(cancellationKey = "search") {
val result = searchUseCase.execute(state.query)
reduce { copy(results = result.getOrDefault(emptyList()), isLoading = false) }
},
)
}
}
}
Action builders available in map():
| Builder | Action | Runs |
|---|---|---|
reduce { … } | ReducerAction | Inline, synchronous state reduction |
sideEffect { … } | SideEffectAction | Inline, constructs a side eff |