# Kmp Mvi Setup

> >

- **Type:** Skill
- **Install:** `agentstack add skill-iammohdzaki-kmp-skills-kmp-mvi-setup`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [iammohdzaki](https://agentstack.voostack.com/s/iammohdzaki)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [iammohdzaki](https://github.com/iammohdzaki)
- **Source:** https://github.com/iammohdzaki/kmp-skills/tree/main/skills/create-project/kmp-mvi-setup

## Install

```sh
agentstack add skill-iammohdzaki-kmp-skills-kmp-mvi-setup
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# KMP + MVI Project Setup Skill

This skill guides you through creating a **production-ready Kotlin Multiplatform (KMP) project**
using **MVI (Model-View-Intent)** architecture with Compose Multiplatform.

> **IMPORTANT**: Before writing any code or project structure, you MUST ask the user
> all Pre-flight Questions below and wait for answers. Never assume defaults silently.

> **PREREQUISITE SKILLS**: Read these skills first before generating any files:
> - [`kmp-versions`](../kmp-versions/SKILL.md) — always fetch live compatible versions from official sources
> - [`kmp-project-structure`](../kmp-project-structure/SKILL.md) — use the new multi-module layout (`shared/` + `androidApp/`)

---

## Pre-flight Questions

Ask the user these questions **in a single message** before generating anything.
Wait for all answers before reading any reference files or writing any code.

```
Before I scaffold your KMP project, I need a few details:

1. ℹ️ App name & package
   What is your app name and base package?
   e.g. App Name: "Task Manager", Package: "com.example.taskmanager"

2. 📱 Target platforms
   - Android only
   - Android + Desktop (JVM)
   - Android + Desktop + iOS  (requires macOS for iOS build)

3. 🔧 Dependency Injection
   - Koin  (recommended — KMP-native, simple DSL, no code gen)
   - Manual DI  (constructor injection, no library)
   - Other — please specify

4. 🌐 Networking
   Do you need HTTP API calls?
   - Ktor  (recommended — KMP-native, multiplatform engines)
   - None  (no networking needed)
   - Other — please specify

5. 📦 Serialization
   Do you need JSON or data encoding?
   - kotlinx.serialization  (recommended — works on all targets)
   - None  (no serialization needed)
   - Other — please specify

6. 🖼️ Image loading
   Do you need to load images from URLs?
   - Coil 3 + Landscapist  (recommended — KMP-ready, composable wrappers)
   - Coil 3 standalone  (ImageRequest API directly)
   - None  (skip for now)
   - Other — please specify

7. 🦭 Navigation
   Do you want type-safe navigation-compose scaffolded?
   - Yes  (recommended — @Serializable routes, NavHost, NavController via Effect)
   - No  (I’ll add it later)

8. 🗄️ Local Storage / Database
   Do you need persistent local data storage?
   - multiplatform-settings  (small data: prefs, flags, settings — KMP-native)
   - SQLDelight  (recommended for structured data — full KMP: Android + iOS + Desktop)
   - Room  (structured data — Android/JVM only, no iOS)
   - Both multiplatform-settings + SQLDelight  (prefs + structured data)
   - None  (no local storage needed)
```

---

## Module Loader

After the user answers the pre-flight questions, read **only** the reference files that match
their choices. Do not load references for modules the user did not select.

| User selects | Reference file to read | When to skip |
|---|---|---|
| Any (always) | `references/mvi-patterns.md` | Never — always read this |
| Koin | `references/di-options.md` | User chose Manual DI |
| Ktor | `references/network-ktor.md` | User chose None |
| kotlinx.serialization | `references/serialization.md` | User chose None |
| Coil / Landscapist | `references/image-loading.md` | User chose None |
| Navigation | `references/navigation.md` | User chose No |
| multiplatform-settings | `references/storage-datastore.md` | User chose None |
| SQLDelight | `references/storage-sqldelight.md` | User chose None |
| Room | `references/storage-room.md` | User chose None |
| Both settings + SQLDelight | Both `storage-datastore.md` + `storage-sqldelight.md` | — |

> **Rule**: Only include libraries, Gradle entries, and code from the selected reference files.
> Never add a dependency the user did not ask for.

### 🤖 AI Self-Extension Rule

If the user requests a library **not covered by any reference file** in this skill
(e.g. WorkManager, Firebase, Apollo GraphQL, Coil-SVG, Bluetooth, etc.), the AI should:

1. **Research the library** — check its official docs and KMP compatibility.
2. **Generate the setup inline** following the same patterns used in existing reference files:
   - Version catalog entries with `FETCH_FROM_OFFICIAL_SOURCE` placeholders
   - Source set dependency declarations
   - Repository / factory pattern in `commonMain`
   - `expect`/`actual` for platform-specific code if needed
   - Koin DI wiring (if Koin is selected)
   - MVI ViewModel integration snippet
3. **Save a new reference file** at `references/.md` so it can be reused in future projects.
4. **Add the new module to this Module Loader table** in the SKILL.md.

This keeps the skill growing organically without requiring manual updates for every possible library.

---

## After Collecting Answers

Use the answers to generate:

1. **Full project directory structure** (see `kmp-project-structure` skill)
2. `settings.gradle.kts`
3. Root `build.gradle.kts`
4. `shared/build.gradle.kts` — only selected dependencies
5. `androidApp/build.gradle.kts`
6. `desktopApp/build.gradle.kts` (if Desktop selected)
7. `gradle/libs.versions.toml` — only selected libraries, fetched versions (see below)
8. MVI base files: `UiState.kt`, `UiEvent.kt`, `UiEffect.kt`, `BaseViewModel.kt`
9. DI module files — based on chosen DI framework
10. `AppNavigation.kt` — if navigation selected
11. Network/serialization setup — if selected
12. Entry points: `MainActivity.kt`, `main.kt` (Desktop), `App.kt` (shared)

---

## Project Structure to Generate

> Use the **new multi-module layout** (required for AGP 9.0+). See [kmp-project-structure](../kmp-project-structure/SKILL.md) for full file contents.

```
/
├── shared/                                ← KMP library: ALL shared code + shared UI
│   ├── build.gradle.kts                   ← com.android.kotlin.multiplatform.library + kotlin.multiplatform
│   └── src/
│       ├── commonMain/kotlin//
│       │   ├── App.kt                     ← Root @Composable (shared)
│       │   ├── mvi/
│       │   │   ├── UiState.kt
│       │   │   ├── UiEvent.kt
│       │   │   ├── UiEffect.kt
│       │   │   └── BaseViewModel.kt
│       │   ├── di/
│       │   │   └── AppModule.kt           ← Shared DI module
│       │   └── navigation/
│       │       └── AppNavigation.kt       ← NavHost + routes (if selected)
│       ├── androidMain/kotlin//
│       │   └── di/
│       │       └── AndroidModule.kt
│       └── jvmMain/kotlin//      ← only if Desktop selected
│           └── di/
│               └── DesktopModule.kt
│
├── androidApp/                            ← Thin Android shell (entry point only)
│   ├── build.gradle.kts                   ← com.android.application only
│   └── src/main/kotlin//
│       ├── MainActivity.kt                ← setContent { App() }
│       └── MyApplication.kt              ← DI init (if Koin)
│
├── desktopApp/                            ← only if Desktop selected
│   ├── build.gradle.kts                   ← kotlin("jvm")
│   └── src/main/kotlin/
│       └── main.kt                        ← application { Window { App() } }
│
├── iosApp/                                ← Xcode project (if iOS selected)
├── gradle/
│   └── libs.versions.toml
├── build.gradle.kts
├── settings.gradle.kts
├── gradle.properties
└── .gitignore
```

---

## MVI Architecture

See [references/mvi-patterns.md](references/mvi-patterns.md) for the complete pattern.

### The Three Contracts

Every feature in the app follows this contract:

```kotlin
// State — immutable snapshot of what the UI renders
data class FeatureUiState(
    val isLoading: Boolean = false,
    val data: List = emptyList(),
    val error: String? = null
)

// Event — all user actions / one-directional: UI → ViewModel
sealed interface FeatureUiEvent {
    data class LoadData(val id: String) : FeatureUiEvent
    data object RetryClicked : FeatureUiEvent
    data object DismissError : FeatureUiEvent
}

// Effect — one-shot side effects, not stored in State
sealed interface FeatureUiEffect {
    data class NavigateTo(val route: String) : FeatureUiEffect
    data class ShowSnackbar(val message: String) : FeatureUiEffect
}
```

### BaseViewModel (commonMain)

```kotlin
// commonMain — shared ViewModel base, no Android dependency
abstract class BaseViewModel {
    private val initialState: State by lazy { createInitialState() }

    protected val _state: MutableStateFlow = MutableStateFlow(initialState)
    val state: StateFlow = _state.asStateFlow()

    private val _effect = MutableSharedFlow(extraBufferCapacity = 16)
    val effect: SharedFlow = _effect.asSharedFlow()

    protected val viewModelScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)

    abstract fun createInitialState(): State
    abstract fun onEvent(event: Event)

    protected fun setState(reduce: State.() -> State) {
        _state.update { it.reduce() }
    }

    protected fun sendEffect(effect: Effect) {
        viewModelScope.launch { _effect.emit(effect) }
    }

    open fun onCleared() {
        viewModelScope.cancel()
    }
}
```

---

## DI Setup

See [references/di-options.md](references/di-options.md) for full setup per framework.

### If Koin selected

```kotlin
// commonMain — sharedModule
val sharedModule = module {
    // Add your shared dependencies here
}

// androidMain — Android Application.onCreate()
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@MyApplication)
            modules(sharedModule, androidModule)
        }
    }
}

// jvmMain — main()
fun main() = application {
    startKoin { modules(sharedModule, desktopModule) }
    Window(onCloseRequest = ::exitApplication, title = "AppName") {
        App()
    }
}
```

---

## Navigation Setup

See [references/navigation.md](references/navigation.md) for full setup.

### If navigation-compose selected

```kotlin
// commonMain — type-safe routes using @Serializable
@Serializable object HomeRoute
@Serializable data class DetailRoute(val id: String)

// NavHost setup
@Composable
fun AppNavigation(navController: NavHostController = rememberNavController()) {
    NavHost(navController = navController, startDestination = HomeRoute) {
        composable {
            HomeScreen(onNavigateToDetail = { id ->
                navController.navigate(DetailRoute(id))
            })
        }
        composable { backStackEntry ->
            val route: DetailRoute = backStackEntry.toRoute()
            DetailScreen(id = route.id)
        }
    }
}
```

---

## Image Loading Setup

See [references/image-loading.md](references/image-loading.md) for full setup.

---

## Version Catalog

> ⚠️ **Never hardcode versions.** Always follow the **5-step protocol** in
> [`kmp-versions`](../kmp-versions/SKILL.md) to fetch live compatible versions before filling
> in `libs.versions.toml`. The table below shows which sources to check per library:

| Library | Fetch version from |
|---|---|
| Kotlin / KGP | https://kotlinlang.org/docs/multiplatform/multiplatform-compatibility-guide.html |
| AGP | https://developer.android.com/build/releases/gradle-plugin |
| Compose Multiplatform | https://plugins.gradle.org/plugin/org.jetbrains.compose |
| kotlinx-coroutines | https://github.com/Kotlin/kotlinx.coroutines/releases |
| kotlinx-serialization | https://github.com/Kotlin/kotlinx.serialization/releases |
| Ktor | https://github.com/ktorio/ktor/releases |
| Koin | https://github.com/InsertKoinIO/koin/releases |
| Lifecycle / ViewModel | https://developer.android.com/jetpack/androidx/releases/lifecycle |
| Navigation Compose | https://developer.android.com/jetpack/androidx/releases/navigation |
| Coil 3 | https://github.com/coil-kt/coil/releases |
| Landscapist | https://github.com/skydoves/landscapist/releases |

### Always-included catalog entries

```toml
[versions]
# ── Fetch from official sources (see kmp-versions skill) ──────────────────────
kotlin               = "FETCH"
agp                  = "FETCH"
composeMultiplatform = "FETCH"
coroutines           = "FETCH"
lifecycle            = "FETCH"

[libraries]
# Compose Multiplatform — explicit (compose.* accessors deprecated in CMP 1.10)
compose-runtime              = { module = "org.jetbrains.compose.runtime:runtime",                version.ref = "composeMultiplatform" }
compose-foundation           = { module = "org.jetbrains.compose.foundation:foundation",           version.ref = "composeMultiplatform" }
compose-ui                   = { module = "org.jetbrains.compose.ui:ui",                           version.ref = "composeMultiplatform" }
compose-material3            = { module = "org.jetbrains.compose.material3:material3",             version.ref = "composeMultiplatform" }
# material-icons-core is NOT transitive since CMP 1.8.2 — always declare explicitly
compose-material-icons-core  = { module = "org.jetbrains.compose.material:material-icons-core",   version.ref = "composeMultiplatform" }
compose-ui-tooling-preview   = { module = "org.jetbrains.compose.ui:ui-tooling-preview",          version.ref = "composeMultiplatform" }

# Kotlin
kotlinx-coroutines-core      = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core",        version.ref = "coroutines" }
lifecycle-viewmodel          = { module = "androidx.lifecycle:lifecycle-viewmodel",               version.ref = "lifecycle" }
kotlin-test                  = { module = "org.jetbrains.kotlin:kotlin-test",                     version.ref = "kotlin" }

# Android entry point
androidx-activity-compose    = { module = "androidx.activity:activity-compose",                  version = "FETCH" }

[plugins]
kotlinMultiplatform   = { id = "org.jetbrains.kotlin.multiplatform",            version.ref = "kotlin" }
androidApplication    = { id = "com.android.application",                       version.ref = "agp" }
# AGP 9+ new plugin for the shared module
androidKmpLibrary     = { id = "com.android.kotlin.multiplatform.library",      version.ref = "agp" }
composeMultiplatform  = { id = "org.jetbrains.compose",                         version.ref = "composeMultiplatform" }
composeCompiler       = { id = "org.jetbrains.kotlin.plugin.compose",           version.ref = "kotlin" }
```

### Add-on catalog entries (include only if selected)

> Each module's reference file contains its exact `[versions]` and `[libraries]` entries.
> Copy them into `libs.versions.toml` if the user selected that module.

| Module | Reference file with entries |
|---|---|
| kotlinx.serialization | `references/serialization.md` — `[versions]` + `[libraries]` + `[plugins]` |
| Ktor | `references/network-ktor.md` — `[versions]` + `[libraries]` |
| Koin | `references/di-options.md` — `[versions]` + `[libraries]` |
| Navigation Compose | `references/navigation.md` — `[versions]` + `[libraries]` |
| Coil + Landscapist | `references/image-loading.md` — `[versions]` + `[libraries]` |

---

## Rules & Constraints

- **Never** write any code until all pre-flight questions are answered.
- **Never** include a library the user did not select.
- All business logic lives in `commonMain` — never in `androidMain` or `jvmMain`.
- `androidMain` and `jvmMain` contain only: entry points, platform-specific DI modules, and platform `actual` implementations.
- Use `interface` + DI for platform differences — avoid `expect class` (Beta warning).
- All shared ViewModels extend `BaseViewModel` defined in `commonMain`.
- Use `data class` for `UiState`, `sealed interface` for `UiEvent` and `UiEffect`.
- Navigation routes must use `@Serializable` data classes/objects (Kotlin 2.x type-safe API).
- Never hardcode strings — use a `Strings` object or resource file.

---

## Official Docs

- [KMP Overview](https://www.kotlinlang.org/docs/multiplatform/)
- [Compose Multiplatform](https://www.jetbrains.com/compose-multiplatform/)
- [Koin for KMP](https://insert-koin.io/docs/reference/koin-mp/kmp/)
- [Navigation Compose](https://developer.android.com/jetpack/compose/navigation)
- [Coil 3](https://coil-kt.github.io/coil/)
- [Landscapist](https://github.com/skydoves/landscapist)

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [iammohdzaki](https://github.com/iammohdzaki)
- **Source:** [iammohdzaki/kmp-skills](https://github.com/iammohdzaki/kmp-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-iammohdzaki-kmp-skills-kmp-mvi-setup
- Seller: https://agentstack.voostack.com/s/iammohdzaki
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
