agentsclimarketplace

Kmp mvi setup

Skill iammohdzaki/kmp-skills/skills/create-project/kmp-mvi-setup

Instead of copying and pasting the same prompts into every new project, you install this repo globally into your AI assistant. The AI learns exactly how to structure an AGP 9.0+ project, how to scaffold MVI architecture, how to resolve real library versions, and how to audit your Compose UI for Material 3 compliance.

Install
npx -y skills add iammohdzaki/kmp-skills --skill kmp-mvi-setup

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Interactive skill for scaffolding a complete Kotlin Multiplatform (KMP) project using MVI architecture. Covers State/Event/Effect pattern, Compose Multiplatform, DI, type-safe navigation, and image loading. Asks user for preferences before generating any code.

SKILL.md

16.8 KB, as published. Nobody here has run it

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:


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 selectsReference file to readWhen to skip
Any (always)references/mvi-patterns.mdNever — always read this
Koinreferences/di-options.mdUser chose Manual DI
Ktorreferences/network-ktor.mdUser chose None
kotlinx.serializationreferences/serialization.mdUser chose None
Coil / Landscapistreferences/image-loading.mdUser chose None
Navigationreferences/navigation.mdUser chose No
multiplatform-settingsreferences/storage-datastore.mdUser chose None
SQLDelightreferences/storage-sqldelight.mdUser chose None
Roomreferences/storage-room.mdUser chose None
Both settings + SQLDelightBoth 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/<module-name>.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 for full file contents.

<AppName>/
├── shared/                                ← KMP library: ALL shared code + shared UI
│   ├── build.gradle.kts                   ← com.android.kotlin.multiplatform.library + kotlin.multiplatform
│   └── src/
│       ├── commonMain/kotlin/<package>/
│       │   ├── 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/<package>/
│       │   └── di/
│       │       └── AndroidModule.kt
│       └── jvmMain/kotlin/<package>/      ← only if Desktop selected
│           └── di/
│               └── DesktopModule.kt
│
├── androidApp/                            ← Thin Android shell (entry point only)
│   ├── build.gradle.kts                   ← com.android.application only
│   └── src/main/kotlin/<package>/
│       ├── 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 for the complete pattern.

The Three Contracts

Every feature in the app follows this contract:

// State — immutable snapshot of what the UI renders
data class FeatureUiState(
    val isLoading: Boolean = false,
    val data: List<ItemModel> = 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)

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

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

    private val _effect = MutableSharedFlow<Effect>(extraBufferCapacity = 16)
    val effect: SharedFlow<Effect> = _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 for full setup per framework.

If Koin selected

// 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 for full setup.

If navigation-compose selected

// 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<HomeRoute> {
            HomeScreen(onNavigateToDetail = { id ->
                navController.navigate(DetailRoute(id))
            })
        }
        composable<DetailRoute> { backStackEntry ->
            val route: DetailRoute = backStackEntry.toRoute()
            DetailScreen(id = route.id)
        }
    }
}

Image Loading Setup

See references/image-loading.md for full setup.


Version Catalog

⚠️ Never hardcode versions. Always follow the 5-step protocol in kmp-versions to fetch live compatible versions before filling in libs.versions.toml. The table below shows which sources to check per library:

LibraryFetch version from
Kotlin / KGPhttps://kotlinlang.org/docs/multiplatform/multiplatform-compatibility-guide.html
AGPhttps://developer.android.com/build/releases/gradle-plugin
Compose Multiplatformhttps://plugins.gradle.org/plugin/org.jetbrains.compose
kotlinx-coroutineshttps://github.com/Kotlin/kotlinx.coroutines/releases
kotlinx-serializationhttps://github.com/Kotlin/kotlinx.serialization/releases
Ktorhttps://github.com/ktorio/ktor/releases
Koinhttps://github.com/InsertKoinIO/koin/releases
Lifecycle / ViewModelhttps://developer.android.com/jetpack/androidx/releases/lifecycle
Navigation Composehttps://developer.android.com/jetpack/androidx/releases/navigation
Coil 3https://github.com/coil-kt/coil/releases
Landscapisthttps://github.com/skydoves/landscapist/releases

Always-included catalog entries

[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.

ModuleReference file with entries
kotlinx.serializationreferences/serialization.md[versions] + [libraries] + [plugins]
Ktorreferences/network-ktor.md[versions] + [libraries]
Koinreferences/di-options.md[versions] + [libraries]
Navigation Composereferences/navigation.md[versions] + [libraries]
Coil + Landscapistreferences/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

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.