|
| 1 | +# Kotlin Multiplatform |
| 2 | + |
| 3 | +compose-nav-graph supports **Kotlin Multiplatform** out of the box. The annotations are a KMP library that lives in |
| 4 | +your `commonMain` source set (published for `android`, `jvm`, `iosArm64` / `iosSimulatorArm64` / `iosX64`, `js`, and |
| 5 | +`wasmJs`), and the Gradle plugin detects your module's shape and wires the right KSP pass for you. There is no |
| 6 | +KMP specific configuration: you apply the same two plugins as on a plain Android module. |
| 7 | + |
| 8 | +```kotlin |
| 9 | +// shared/build.gradle.kts |
| 10 | +plugins { |
| 11 | + id("com.google.devtools.ksp") version "<matching your Kotlin version>" |
| 12 | + id("com.github.skydoves.navgraph") version "0.1.0" |
| 13 | +} |
| 14 | +``` |
| 15 | + |
| 16 | +The plugin adds `compose-nav-graph-annotations` to `commonMain` automatically, so `@NavDestination`, `@NavEdge`, |
| 17 | +`@NavPreview`, and `@NavGraphRoot` are usable from shared code right away. |
| 18 | + |
| 19 | +## How module detection works |
| 20 | + |
| 21 | +The plugin recognizes three module shapes and picks the extraction pass accordingly: |
| 22 | + |
| 23 | +| Module shape | Extraction pass | Thumbnails | |
| 24 | +|---|---|---| |
| 25 | +| Plain Android | the debug variant's KSP pass | Yes | |
| 26 | +| KMP **with** an Android target | the Android compilation's KSP pass (`kspAndroidMain` on the new `androidLibrary {}` DSL, the legacy per variant pass otherwise) | Yes | |
| 27 | +| KMP **without** an Android target (iOS/JS/wasm only) | the common metadata KSP pass | Structure only | |
| 28 | + |
| 29 | +Detection happens automatically, including the new `com.android.kotlin.multiplatform.library` (`androidLibrary {}`) |
| 30 | +DSL, where the module namespace is also resolved for you. |
| 31 | + |
| 32 | +## Thumbnails for shared Compose Multiplatform screens |
| 33 | + |
| 34 | +Rendering needs Android, but a KMP shared module has **no Android resources of its own**. The plugin solves this by |
| 35 | +reusing the **consuming Android app's** linked resources: when it finds an Android app that depends on your shared |
| 36 | +module, the renderer runs against that app's fully merged Compose Multiplatform resources (the app's own, the shared |
| 37 | +module's, and every transitive dependency's). Your `composeResources` images, fonts, and strings all show up in the |
| 38 | +thumbnails. |
| 39 | + |
| 40 | +Two render backends cooperate (see [`renderBackend`](gradle-plugin/configuration.md#renderbackend)): |
| 41 | + |
| 42 | +- **Layoutlib** (device free, fast): the same engine Android Studio uses for `@Preview`. Most screens render here. |
| 43 | +- **Robolectric** (full Android runtime): complex Compose Multiplatform screens that Layoutlib can't draw fall back |
| 44 | + here automatically when `renderBackend` is `AUTO` (the default). |
| 45 | + |
| 46 | +!!! tip "Keep previews render friendly" |
| 47 | + |
| 48 | + The renderer instantiates your `@NavPreview` composables without a running app, so previews should stay |
| 49 | + **stateless**: render plain UI with mock data, and avoid view models, real network or media players, and other |
| 50 | + runtime only machinery inside preview bodies. This is the same rule that makes Android Studio previews reliable. |
| 51 | + |
| 52 | +## Structure only graphs |
| 53 | + |
| 54 | +A KMP module with no Android target (an iOS/JS/wasm only project) still gets a full navigation graph: nodes, typed |
| 55 | +arguments, start destination, and transitions, extracted from the common metadata pass. Only the thumbnails are |
| 56 | +skipped, and the IDE plugin and exports render thumbnail-less nodes gracefully. You can also opt into the same |
| 57 | +behavior anywhere with `navgraph { renderThumbnails.set(false) }` when you only care about the graph's shape. |
| 58 | + |
| 59 | +## Multi module aggregation |
| 60 | + |
| 61 | +Cross module aggregation works the same as on Android: each module (shared KMP or Android) emits its own |
| 62 | +`nav-graph.json`, and the app module merges its own graph with every dependency's into one picture. See |
| 63 | +[Configuration](gradle-plugin/configuration.md#aggregate) for the `aggregate` switch. |
| 64 | + |
| 65 | +## A real world example |
| 66 | + |
| 67 | +[`samples/sample-kotlinconf/`](https://github.com/skydoves/compose-nav-graph/tree/main/samples/sample-kotlinconf) |
| 68 | +applies the plugin to JetBrains' **KotlinConf app**, a production grade Compose Multiplatform project (Android, iOS, |
| 69 | +desktop, and web targets, multi module). The shared `:app:shared` module declares the two plugins, screens are |
| 70 | +annotated in `commonMain`, and the result is the complete conference app flow: **26 screens, 36 transitions, and a |
| 71 | +rendered thumbnail for every screen**, including screens built entirely from Compose Multiplatform resources. |
| 72 | + |
| 73 | +The committed export of that graph is in |
| 74 | +[`nav-results/`](https://github.com/skydoves/compose-nav-graph/tree/main/nav-results), if you want to see the output |
| 75 | +without building anything. |
0 commit comments