Starting a Kotlin Multiplatform project has a strange cost curve. The first `Hello, world` is cheap — the IDE's built-in wizard gives you a shared module and platform targets in a minute. The next two weeks are where the real setup lives: choosing an architecture, wiring a KMP-ready database, getting type-safe navigation working, setting up DI without service-locator soup, structuring Gradle so the build doesn't rot, and writing enough tests to trust any of it.

This article walks through one opinionated answer to that setup — the architecture decisions themselves, and why they're baked the way they are. The vehicle is [KMP Project Wizard](https://plugins.jetbrains.com/plugin/31786-kmp-project-wizard), an open-source IntelliJ IDEA / Android Studio plugin I built that generates the whole thing from `File → New Project`, but every decision below stands on its own — you can apply them by hand to any KMP codebase.

![The KMP Project Wizard's New Project dialog in IntelliJ IDEA, showing package, app, feature and platform fields.](kmp-wizard-new-project.png)

## The reference-app principle: templates that can't go stale by construction

The most important decision isn't in the generated code at all. The templates aren't hand-authored files that drift out of date — they're generated from [kmp-ledger](https://github.com/aoreshkov/kmp-ledger), a real, working, MIT-licensed double-entry ledger app that builds, tests, and runs on all three platforms. At plugin build time, concrete names (`app.oreshkov.ledger`, `Posting`, `Ledger`) are replaced with placeholders; at generation time, your names are substituted back in — package, app name, feature name, entity fields, everywhere including directory names.

The consequence: if the reference app compiles and its tests pass, so does your generated project. "Template rot" — the classic starter-kit disease — becomes a build failure in *my* pipeline instead of a runtime surprise in *your* project.

## Architecture: Clean layers with a feature API/impl split

The generated project is modular from the first commit:

- **Domain / Data / UI layers** per feature, with the dependency arrow always pointing inward — UI and Data both depend on Domain; Domain depends on nothing.
- **A feature API/impl split**: each feature exposes a small `api` module (public contracts) and keeps its `impl` private. Other features depend on `api` only, so feature boundaries survive growth and builds stay parallelizable.
- **Binary-compatibility validation**: the Kotlin BCV plugin's `apiDump` snapshots every module's public ABI, and `apiCheck` fails the build when a public contract changes accidentally. (A detail I got wrong the first time: ABI dumps are compiler-output snapshots — symbol names, declaration order, and Compose lambda keys derive from *your* names, so shipping the reference app's dumps with names substituted can never match. The wizard instead runs `apiDump` after your project's first Gradle sync, so the baseline is generated from your code.)

## The stack, and why each piece

- **Compose Multiplatform** for shared UI across Android, iOS, and Desktop — one rendering model, platform entry points kept thin.
- **Navigation 3** for type-safe routing: destinations are serializable types, not string routes, so a refactor that breaks navigation breaks the *compile*.
- **Room 3** for persistence — the KMP-ready generation of Room, meaning the same DAO and entity definitions run on all targets, with SQLite under the hood.
- **Koin + KSP annotations** for DI: annotation-driven definitions with compile-time processing, no reflective magic on the hot path, and no hand-maintained module lists.
- **Coroutines & Flow** end to end — repositories expose `Flow`, use cases suspend, UI collects with lifecycle awareness.

## Gradle that scales: convention plugins + version catalog

All build logic lives in convention plugins (`build-logic/`), applied by module type — a feature `impl` module's build file is a few lines naming its type and dependencies. Versions are centralized in `gradle/libs.versions.toml`. Adding a fifteenth module costs the same as adding the second, and a dependency bump is a one-line change.

## Tests as part of the template

Every generated project ships a passing suite: unit tests for domain and data (including an in-memory Room setup) and Compose UI tests. Not because sample tests are valuable in themselves, but because a testing *pattern* you can copy beats a blank `src/test` directory every time you add a feature.

![The generated project's JVM test suite in IntelliJ — 137 tests passed, all green.](kmp-wizard-tests-green.png)

## What the one dialog actually asks

Package name, app name, one feature name, one entity with a test value, and target platforms (Android / iOS / Desktop). From that it derives every identifier — PascalCase types, camelCase members, snake_case resources — and renders the whole tree with your names, then triggers Gradle sync and generates the ABI baselines.

The plugin's core is free (all three platforms, the full architecture and tests). A paid Pro tier adds two things to the generated project: Claude Code agent config (`CLAUDE.md`, `.claude/` skills) and GitHub Actions CI (`.github/`) — flat files, no runtime services, and the same content you can inspect in kmp-ledger.

## Links

- Plugin: https://plugins.jetbrains.com/plugin/31786-kmp-project-wizard
- Plugin source (MIT): https://github.com/aoreshkov/kmp-wizard
- Reference app the templates come from: https://github.com/aoreshkov/kmp-ledger

If you try it, I'd genuinely value feedback — the issue tracker is open, and the architecture opinions above are exactly the kind of thing worth arguing about.

---

*Originally published on [dev.to](https://dev.to/aoreshkov/generate-a-production-grade-kotlin-multiplatform-app-compose-room-3-navigation-3-koin-in-one-n6m).*
