android-sdd 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +143 -0
- package/package.json +27 -0
- package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
- package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
- package/skills/Android Platform/Configuration/SKILL.md +201 -0
- package/skills/Android Platform/Filesystem/SKILL.md +216 -0
- package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
- package/skills/Android Platform/Manifest/SKILL.md +226 -0
- package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
- package/skills/Android Platform/Resources/SKILL.md +234 -0
- package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
- package/skills/Android Platform/State Restoration/SKILL.md +210 -0
- package/skills/Architecture/Bounded Context/SKILL.md +207 -0
- package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
- package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
- package/skills/Architecture/Entity Design/SKILL.md +243 -0
- package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
- package/skills/Architecture/MVI/SKILL.md +224 -0
- package/skills/Architecture/MVVM/SKILL.md +198 -0
- package/skills/Architecture/Modularization/SKILL.md +194 -0
- package/skills/Architecture/Offline First/SKILL.md +249 -0
- package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
- package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
- package/skills/Architecture/State Management/SKILL.md +229 -0
- package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
- package/skills/Architecture/Use Case Design/SKILL.md +244 -0
- package/skills/Architecture/Value Object/SKILL.md +226 -0
- package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
- package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
- package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
- package/skills/Build System/Build Cache/SKILL.md +233 -0
- package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
- package/skills/Build System/Build Variant/SKILL.md +215 -0
- package/skills/Build System/Convention Plugin/SKILL.md +288 -0
- package/skills/Build System/Dependency Management/SKILL.md +261 -0
- package/skills/Build System/Gradle/SKILL.md +284 -0
- package/skills/Build System/Incremental Build/SKILL.md +199 -0
- package/skills/Build System/KAPT/SKILL.md +198 -0
- package/skills/Build System/KSP/SKILL.md +263 -0
- package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
- package/skills/Build System/Specialized/C++/SKILL.md +308 -0
- package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
- package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
- package/skills/Build System/Version Catalog/SKILL.md +304 -0
- package/skills/Concurrency/Background Processing/SKILL.md +185 -0
- package/skills/Concurrency/Channel/SKILL.md +207 -0
- package/skills/Concurrency/Coroutine/SKILL.md +200 -0
- package/skills/Concurrency/Flow/SKILL.md +179 -0
- package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
- package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
- package/skills/Concurrency/StateFlow/SKILL.md +175 -0
- package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
- package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
- package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
- package/skills/Core Language/DSL/SKILL.md +186 -0
- package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
- package/skills/Core Language/Immutability/SKILL.md +156 -0
- package/skills/Core Language/KMP/SKILL.md +182 -0
- package/skills/Core Language/Kotlin/SKILL.md +187 -0
- package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
- package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
- package/skills/Core Language/Serialization/SKILL.md +191 -0
- package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
- package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
- package/skills/Data Layer/DAO/SKILL.md +225 -0
- package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
- package/skills/Data Layer/DataStore/SKILL.md +264 -0
- package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
- package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
- package/skills/Data Layer/File Storage/SKILL.md +247 -0
- package/skills/Data Layer/Indexing/SKILL.md +184 -0
- package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
- package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
- package/skills/Data Layer/Migration/SKILL.md +243 -0
- package/skills/Data Layer/Paging/SKILL.md +264 -0
- package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
- package/skills/Data Layer/Room/SKILL.md +244 -0
- package/skills/Data Layer/SQLite/SKILL.md +255 -0
- package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
- package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
- package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
- package/skills/Dependency Injection/Koin/SKILL.md +282 -0
- package/skills/Developer Experience/Detekt/SKILL.md +272 -0
- package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
- package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
- package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
- package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
- package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
- package/skills/Media/Audio/SKILL.md +257 -0
- package/skills/Media/Camera/SKILL.md +229 -0
- package/skills/Media/CameraX/SKILL.md +295 -0
- package/skills/Media/ExoPlayer/SKILL.md +258 -0
- package/skills/Media/Video/SKILL.md +228 -0
- package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
- package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
- package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
- package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
- package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
- package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
- package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
- package/skills/Navigation/Navigation/SKILL.md +215 -0
- package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
- package/skills/Networking/API Contract/SKILL.md +220 -0
- package/skills/Networking/Authentication/SKILL.md +210 -0
- package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
- package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
- package/skills/Networking/Ktor/SKILL.md +219 -0
- package/skills/Networking/Multipart Upload/SKILL.md +213 -0
- package/skills/Networking/OkHttp/SKILL.md +193 -0
- package/skills/Networking/REST/SKILL.md +178 -0
- package/skills/Networking/Rate Limiting/SKILL.md +170 -0
- package/skills/Networking/Retrofit/SKILL.md +241 -0
- package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
- package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
- package/skills/Networking/WebSocket/SKILL.md +224 -0
- package/skills/Observability/Crash Reporting/SKILL.md +219 -0
- package/skills/Observability/Logging/SKILL.md +168 -0
- package/skills/Observability/Metrics/SKILL.md +227 -0
- package/skills/Observability/Structured Logging/SKILL.md +234 -0
- package/skills/Performance/ANR Prevention/SKILL.md +192 -0
- package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
- package/skills/Performance/App Startup/SKILL.md +183 -0
- package/skills/Performance/Baseline Profile/SKILL.md +205 -0
- package/skills/Performance/Battery Optimization/SKILL.md +192 -0
- package/skills/Performance/Benchmark/SKILL.md +182 -0
- package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
- package/skills/Performance/Compose Optimization/SKILL.md +187 -0
- package/skills/Performance/Heap Management/SKILL.md +184 -0
- package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
- package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
- package/skills/Performance/Rendering Performance/SKILL.md +205 -0
- package/skills/Performance/Startup Optimization/SKILL.md +219 -0
- package/skills/Security/Biometric/SKILL.md +224 -0
- package/skills/Security/Certificate Transparency/SKILL.md +158 -0
- package/skills/Security/Cryptography/SKILL.md +244 -0
- package/skills/Security/Encrypted Storage/SKILL.md +273 -0
- package/skills/Security/Frida Detection/SKILL.md +230 -0
- package/skills/Security/Hook Detection/SKILL.md +197 -0
- package/skills/Security/Keystore/SKILL.md +272 -0
- package/skills/Security/Network Security Config/SKILL.md +186 -0
- package/skills/Security/Obfuscation/SKILL.md +226 -0
- package/skills/Security/Proguard/SKILL.md +202 -0
- package/skills/Security/R8/SKILL.md +234 -0
- package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
- package/skills/Security/Root Detection/SKILL.md +220 -0
- package/skills/Security/Secure Networking/SKILL.md +220 -0
- package/skills/System Integration/AlarmManager/SKILL.md +182 -0
- package/skills/System Integration/App Widget/SKILL.md +182 -0
- package/skills/System Integration/Deep Link/SKILL.md +187 -0
- package/skills/System Integration/Foreground Service/SKILL.md +212 -0
- package/skills/System Integration/Notification/SKILL.md +237 -0
- package/skills/System Integration/WorkManager/SKILL.md +256 -0
- package/skills/System Integration/clipboard/SKILL.md +155 -0
- package/skills/System Integration/share-intent/SKILL.md +182 -0
- package/skills/Testing/Compose Testing/SKILL.md +296 -0
- package/skills/Testing/Espresso/SKILL.md +292 -0
- package/skills/Testing/Fake Data/SKILL.md +245 -0
- package/skills/Testing/Integration Testing/SKILL.md +288 -0
- package/skills/Testing/Mocking/SKILL.md +229 -0
- package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
- package/skills/Testing/UI Testing/SKILL.md +293 -0
- package/skills/Testing/Unit Testing/SKILL.md +309 -0
- package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
- package/skills/UI System/Compose/SKILL.md +296 -0
- package/skills/UI System/Compose Animation/SKILL.md +281 -0
- package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
- package/skills/UI System/Compose Navigation/SKILL.md +255 -0
- package/skills/UI System/Compose Performance/SKILL.md +274 -0
- package/skills/UI System/Design System/SKILL.md +217 -0
- package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
- package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
- package/skills/UI System/Loading Strategy/SKILL.md +254 -0
- package/skills/UI System/Material 3/SKILL.md +279 -0
- package/skills/UI System/RTL/SKILL.md +179 -0
- package/src/index.ts +182 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: compose-multiplatform
|
|
3
|
+
description: >
|
|
4
|
+
Compose Multiplatform (CMP) setup and patterns for sharing UI across
|
|
5
|
+
Android, iOS, Desktop, and Web. Load this skill when building shared
|
|
6
|
+
UI with CMP, handling platform-specific UI differences, or deciding
|
|
7
|
+
what UI code belongs in shared vs platform modules.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Compose Multiplatform
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Compose Multiplatform (CMP) by JetBrains extends Jetpack Compose to share UI code across Android, iOS, Desktop (JVM), and Web (Wasm). It builds on top of KMP for shared logic and adds a shared UI layer. The same composable functions render natively on each platform.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Share UI that is **truly identical** across platforms — don't force sharing where platforms differ
|
|
20
|
+
- Platform-specific UI (navigation bars, status bars, permissions UI) stays **platform-specific**
|
|
21
|
+
- Use `expect/actual` for platform-specific UI components
|
|
22
|
+
- Use `rememberX()` patterns for platform resources (images, fonts)
|
|
23
|
+
- Test shared UI on **all target platforms** — rendering differences exist
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Project Structure
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
project/
|
|
31
|
+
├── composeApp/
|
|
32
|
+
│ ├── src/
|
|
33
|
+
│ │ ├── commonMain/ ← shared UI + business logic
|
|
34
|
+
│ │ │ └── kotlin/
|
|
35
|
+
│ │ │ ├── App.kt ← root composable
|
|
36
|
+
│ │ │ ├── screens/
|
|
37
|
+
│ │ │ └── components/
|
|
38
|
+
│ │ ├── androidMain/ ← Android entry point + platform UI
|
|
39
|
+
│ │ │ └── kotlin/
|
|
40
|
+
│ │ │ └── MainActivity.kt
|
|
41
|
+
│ │ ├── iosMain/ ← iOS entry point
|
|
42
|
+
│ │ │ └── kotlin/
|
|
43
|
+
│ │ │ └── MainViewController.kt
|
|
44
|
+
│ │ └── desktopMain/ ← Desktop entry point
|
|
45
|
+
│ │ └── kotlin/
|
|
46
|
+
│ │ └── main.kt
|
|
47
|
+
├── shared/ ← shared business logic (no UI)
|
|
48
|
+
└── iosApp/ ← Xcode project
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Root Composable (commonMain)
|
|
54
|
+
|
|
55
|
+
```kotlin
|
|
56
|
+
// commonMain/App.kt
|
|
57
|
+
@Composable
|
|
58
|
+
fun App() {
|
|
59
|
+
AppTheme {
|
|
60
|
+
// Shared navigation and screens
|
|
61
|
+
AppNavHost()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Platform Entry Points
|
|
69
|
+
|
|
70
|
+
```kotlin
|
|
71
|
+
// androidMain/MainActivity.kt
|
|
72
|
+
class MainActivity : ComponentActivity() {
|
|
73
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
74
|
+
super.onCreate(savedInstanceState)
|
|
75
|
+
setContent { App() }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// iosMain/MainViewController.kt
|
|
80
|
+
fun MainViewController() = ComposeUIViewController { App() }
|
|
81
|
+
|
|
82
|
+
// desktopMain/main.kt
|
|
83
|
+
fun main() = application {
|
|
84
|
+
Window(title = "MyApp", onCloseRequest = ::exitApplication) {
|
|
85
|
+
App()
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Platform-Specific Components
|
|
93
|
+
|
|
94
|
+
```kotlin
|
|
95
|
+
// ✅ expect/actual for platform-specific UI
|
|
96
|
+
// commonMain
|
|
97
|
+
@Composable
|
|
98
|
+
expect fun PlatformStatusBar()
|
|
99
|
+
|
|
100
|
+
// androidMain
|
|
101
|
+
@Composable
|
|
102
|
+
actual fun PlatformStatusBar() {
|
|
103
|
+
// Android-specific status bar handling
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// iosMain
|
|
107
|
+
@Composable
|
|
108
|
+
actual fun PlatformStatusBar() {
|
|
109
|
+
// iOS safe area insets handling
|
|
110
|
+
Spacer(modifier = Modifier.windowInsetsTopHeight(WindowInsets.safeDrawing))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// desktopMain
|
|
114
|
+
@Composable
|
|
115
|
+
actual fun PlatformStatusBar() {
|
|
116
|
+
// No status bar on desktop
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Resources in CMP
|
|
123
|
+
|
|
124
|
+
```kotlin
|
|
125
|
+
// ✅ Use compose-resources for shared resources
|
|
126
|
+
// commonMain/composeResources/
|
|
127
|
+
// drawable/logo.png
|
|
128
|
+
// font/roboto.ttf
|
|
129
|
+
// values/strings.xml
|
|
130
|
+
|
|
131
|
+
// Access in shared composable
|
|
132
|
+
@Composable
|
|
133
|
+
fun Logo() {
|
|
134
|
+
Image(
|
|
135
|
+
painter = painterResource(Res.drawable.logo),
|
|
136
|
+
contentDescription = null
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
Text(stringResource(Res.string.app_name))
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```toml
|
|
144
|
+
# libs.versions.toml
|
|
145
|
+
[libraries]
|
|
146
|
+
compose-resources = { module = "org.jetbrains.compose.components:components-resources" }
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Navigation in CMP
|
|
152
|
+
|
|
153
|
+
```kotlin
|
|
154
|
+
// ✅ Use Navigation Compose (works in CMP)
|
|
155
|
+
// commonMain
|
|
156
|
+
@Composable
|
|
157
|
+
fun AppNavHost() {
|
|
158
|
+
val navController = rememberNavController()
|
|
159
|
+
NavHost(navController, startDestination = HomeRoute) {
|
|
160
|
+
composable<HomeRoute> {
|
|
161
|
+
HomeScreen(onNavigate = { navController.navigate(DetailRoute(it)) })
|
|
162
|
+
}
|
|
163
|
+
composable<DetailRoute> { DetailScreen() }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Platform Detection
|
|
171
|
+
|
|
172
|
+
```kotlin
|
|
173
|
+
// ✅ When platform-specific behavior needed in shared code
|
|
174
|
+
expect fun getPlatformName(): String
|
|
175
|
+
|
|
176
|
+
// androidMain
|
|
177
|
+
actual fun getPlatformName() = "Android"
|
|
178
|
+
|
|
179
|
+
// iosMain
|
|
180
|
+
actual fun getPlatformName() = "iOS"
|
|
181
|
+
|
|
182
|
+
// ✅ Use for conditional UI
|
|
183
|
+
@Composable
|
|
184
|
+
fun PlatformAwareLayout() {
|
|
185
|
+
if (getPlatformName() == "Android") {
|
|
186
|
+
AndroidSpecificLayout()
|
|
187
|
+
} else {
|
|
188
|
+
DefaultLayout()
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Gradle Setup
|
|
196
|
+
|
|
197
|
+
```kotlin
|
|
198
|
+
// composeApp/build.gradle.kts
|
|
199
|
+
plugins {
|
|
200
|
+
alias(libs.plugins.kotlin.multiplatform)
|
|
201
|
+
alias(libs.plugins.compose.multiplatform)
|
|
202
|
+
alias(libs.plugins.android.application)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
kotlin {
|
|
206
|
+
androidTarget()
|
|
207
|
+
iosX64(); iosArm64(); iosSimulatorArm64()
|
|
208
|
+
jvm("desktop")
|
|
209
|
+
|
|
210
|
+
sourceSets {
|
|
211
|
+
commonMain.dependencies {
|
|
212
|
+
implementation(compose.runtime)
|
|
213
|
+
implementation(compose.foundation)
|
|
214
|
+
implementation(compose.material3)
|
|
215
|
+
implementation(compose.ui)
|
|
216
|
+
implementation(compose.components.resources)
|
|
217
|
+
implementation(libs.navigation.compose)
|
|
218
|
+
}
|
|
219
|
+
androidMain.dependencies {
|
|
220
|
+
implementation(libs.androidx.activity.compose)
|
|
221
|
+
}
|
|
222
|
+
val desktopMain by getting {
|
|
223
|
+
dependencies {
|
|
224
|
+
implementation(compose.desktop.currentOs)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## What to Share vs Platform-Specific
|
|
234
|
+
|
|
235
|
+
| UI Element | Share | Platform-Specific |
|
|
236
|
+
|------------|-------|------------------|
|
|
237
|
+
| Business screens (list, form, detail) | ✅ | |
|
|
238
|
+
| Design system components | ✅ | |
|
|
239
|
+
| Navigation structure | ✅ | |
|
|
240
|
+
| Status bar / navigation bar | | ✅ |
|
|
241
|
+
| Permission request UI | | ✅ |
|
|
242
|
+
| Platform pickers (date, file) | | ✅ |
|
|
243
|
+
| App entry point | | ✅ |
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Anti-Patterns
|
|
248
|
+
|
|
249
|
+
- Importing `android.*` in `commonMain` — breaks iOS/Desktop compilation
|
|
250
|
+
- Sharing UI that looks wrong on a platform — prefer platform-specific when UX differs
|
|
251
|
+
- Using Android-specific Compose APIs in shared code (e.g., `LocalContext`) — use expect/actual
|
|
252
|
+
- Not testing on iOS — rendering differences are common
|
|
253
|
+
- Placing platform resources in `commonMain/res/` — use `composeResources/` instead
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Related Skills
|
|
258
|
+
- `kmp` — Kotlin Multiplatform shared logic
|
|
259
|
+
- `compose` — Compose fundamentals
|
|
260
|
+
- `compose-navigation` — Navigation in CMP
|
|
261
|
+
- `material3` — Material 3 in CMP
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: compose-navigation
|
|
3
|
+
description: >
|
|
4
|
+
Jetpack Compose Navigation setup, patterns, and best practices.
|
|
5
|
+
Load this skill when setting up NavHost, defining routes, navigating between
|
|
6
|
+
screens, passing arguments, handling deep links, or managing the back stack
|
|
7
|
+
in a Compose app.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Compose Navigation
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Jetpack Compose Navigation provides a type-safe way to navigate between composable screens. Navigation is managed by a `NavController` and defined in a `NavHost`. Arguments, deep links, and back stack management are all handled through the navigation graph.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- `NavController` must be created at the **top level** — never inside nested composables
|
|
20
|
+
- Pass `NavController` down **only one level** — deeper screens receive lambdas, not NavController
|
|
21
|
+
- Use **type-safe routes** (Navigation 2.8+) — avoid string-based routes
|
|
22
|
+
- Navigation logic belongs in the **screen-level composable** — not in ViewModel
|
|
23
|
+
- **Never navigate from inside a ViewModel** — emit an event, handle navigation in UI
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
```toml
|
|
30
|
+
# libs.versions.toml
|
|
31
|
+
[versions]
|
|
32
|
+
navigation-compose = "2.8.0"
|
|
33
|
+
|
|
34
|
+
[libraries]
|
|
35
|
+
navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation-compose" }
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```kotlin
|
|
39
|
+
// build.gradle.kts
|
|
40
|
+
dependencies {
|
|
41
|
+
implementation(libs.navigation.compose)
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Type-Safe Routes (Navigation 2.8+)
|
|
48
|
+
|
|
49
|
+
```kotlin
|
|
50
|
+
// ✅ Define routes as serializable objects/data classes
|
|
51
|
+
@Serializable
|
|
52
|
+
object HomeRoute
|
|
53
|
+
|
|
54
|
+
@Serializable
|
|
55
|
+
object UserListRoute
|
|
56
|
+
|
|
57
|
+
@Serializable
|
|
58
|
+
data class UserDetailRoute(val userId: String)
|
|
59
|
+
|
|
60
|
+
@Serializable
|
|
61
|
+
data class EditUserRoute(val userId: String, val isNew: Boolean = false)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## NavHost Setup
|
|
67
|
+
|
|
68
|
+
```kotlin
|
|
69
|
+
// ✅ NavHost at top level — in Activity or root composable
|
|
70
|
+
@Composable
|
|
71
|
+
fun AppNavHost(
|
|
72
|
+
navController: NavHostController = rememberNavController(),
|
|
73
|
+
startDestination: Any = HomeRoute
|
|
74
|
+
) {
|
|
75
|
+
NavHost(
|
|
76
|
+
navController = navController,
|
|
77
|
+
startDestination = startDestination
|
|
78
|
+
) {
|
|
79
|
+
composable<HomeRoute> {
|
|
80
|
+
HomeScreen(
|
|
81
|
+
onNavigateToUsers = { navController.navigate(UserListRoute) }
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
composable<UserListRoute> {
|
|
86
|
+
UserListScreen(
|
|
87
|
+
onUserClick = { userId ->
|
|
88
|
+
navController.navigate(UserDetailRoute(userId))
|
|
89
|
+
},
|
|
90
|
+
onBack = { navController.navigateUp() }
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
composable<UserDetailRoute> { backStackEntry ->
|
|
95
|
+
val route = backStackEntry.toRoute<UserDetailRoute>()
|
|
96
|
+
UserDetailScreen(
|
|
97
|
+
userId = route.userId,
|
|
98
|
+
onBack = { navController.navigateUp() },
|
|
99
|
+
onEdit = { navController.navigate(EditUserRoute(route.userId)) }
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Reading Arguments in ViewModel
|
|
109
|
+
|
|
110
|
+
```kotlin
|
|
111
|
+
// ✅ ViewModel reads route args from SavedStateHandle
|
|
112
|
+
@HiltViewModel
|
|
113
|
+
class UserDetailViewModel @Inject constructor(
|
|
114
|
+
savedStateHandle: SavedStateHandle,
|
|
115
|
+
private val repository: UserRepository
|
|
116
|
+
) : ViewModel() {
|
|
117
|
+
|
|
118
|
+
private val route = savedStateHandle.toRoute<UserDetailRoute>()
|
|
119
|
+
val userId = route.userId
|
|
120
|
+
|
|
121
|
+
val user: StateFlow<User?> = repository
|
|
122
|
+
.observeUser(userId)
|
|
123
|
+
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Navigation Patterns
|
|
130
|
+
|
|
131
|
+
```kotlin
|
|
132
|
+
// ✅ Navigate and clear back stack (login → home)
|
|
133
|
+
navController.navigate(HomeRoute) {
|
|
134
|
+
popUpTo(navController.graph.startDestinationId) { inclusive = true }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ✅ Avoid duplicate destinations
|
|
138
|
+
navController.navigate(UserDetailRoute(userId)) {
|
|
139
|
+
launchSingleTop = true
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ✅ Navigate up (back)
|
|
143
|
+
navController.navigateUp()
|
|
144
|
+
|
|
145
|
+
// ✅ Pop to specific destination
|
|
146
|
+
navController.popBackStack(UserListRoute, inclusive = false)
|
|
147
|
+
|
|
148
|
+
// ✅ Navigate with result (pass data back)
|
|
149
|
+
// Sender
|
|
150
|
+
navController.previousBackStackEntry
|
|
151
|
+
?.savedStateHandle
|
|
152
|
+
?.set("selected_user_id", userId)
|
|
153
|
+
navController.navigateUp()
|
|
154
|
+
|
|
155
|
+
// Receiver
|
|
156
|
+
val selectedUserId = navController.currentBackStackEntry
|
|
157
|
+
?.savedStateHandle
|
|
158
|
+
?.getStateFlow<String?>("selected_user_id", null)
|
|
159
|
+
?.collectAsStateWithLifecycle()
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Passing NavController Down — Lambda Pattern
|
|
165
|
+
|
|
166
|
+
```kotlin
|
|
167
|
+
// ✅ Only one level deep — screen composables receive lambdas
|
|
168
|
+
@Composable
|
|
169
|
+
fun AppNavHost(navController: NavHostController) {
|
|
170
|
+
NavHost(navController, startDestination = HomeRoute) {
|
|
171
|
+
composable<HomeRoute> {
|
|
172
|
+
// ✅ Pass lambda, not navController
|
|
173
|
+
HomeScreen(
|
|
174
|
+
onNavigateToUsers = { navController.navigate(UserListRoute) },
|
|
175
|
+
onNavigateToSettings = { navController.navigate(SettingsRoute) }
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ✅ Screen receives lambdas — decoupled from navigation
|
|
182
|
+
@Composable
|
|
183
|
+
fun HomeScreen(
|
|
184
|
+
onNavigateToUsers: () -> Unit,
|
|
185
|
+
onNavigateToSettings: () -> Unit,
|
|
186
|
+
viewModel: HomeViewModel = hiltViewModel()
|
|
187
|
+
) {
|
|
188
|
+
// ...
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ❌ Don't pass NavController into screen composables
|
|
192
|
+
@Composable
|
|
193
|
+
fun HomeScreen(navController: NavController) { // tightly coupled
|
|
194
|
+
Button(onClick = { navController.navigate(UserListRoute) }) { ... }
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Deep Links
|
|
201
|
+
|
|
202
|
+
```kotlin
|
|
203
|
+
// ✅ Define deep links in NavHost
|
|
204
|
+
composable<UserDetailRoute>(
|
|
205
|
+
deepLinks = listOf(
|
|
206
|
+
navDeepLink<UserDetailRoute>(
|
|
207
|
+
basePath = "https://example.com/users"
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
) { ... }
|
|
211
|
+
|
|
212
|
+
// AndroidManifest.xml — declare intent filter
|
|
213
|
+
// (see deep-link skill for full manifest setup)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Navigation with Hilt
|
|
219
|
+
|
|
220
|
+
```kotlin
|
|
221
|
+
// ✅ hiltViewModel() — scoped to NavBackStackEntry
|
|
222
|
+
composable<UserDetailRoute> {
|
|
223
|
+
val viewModel: UserDetailViewModel = hiltViewModel()
|
|
224
|
+
UserDetailScreen(viewModel = viewModel)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ✅ Shared ViewModel across multiple screens
|
|
228
|
+
composable<CheckoutRoute> { backStackEntry ->
|
|
229
|
+
val parentEntry = remember(backStackEntry) {
|
|
230
|
+
navController.getBackStackEntry(CheckoutFlowRoute)
|
|
231
|
+
}
|
|
232
|
+
val sharedViewModel: CheckoutViewModel = hiltViewModel(parentEntry)
|
|
233
|
+
CheckoutScreen(viewModel = sharedViewModel)
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Anti-Patterns
|
|
240
|
+
|
|
241
|
+
- Creating `NavController` inside a non-top-level composable — causes recreation
|
|
242
|
+
- Passing `NavController` more than one level deep — tight coupling
|
|
243
|
+
- Navigating from ViewModel — emit event, navigate from UI
|
|
244
|
+
- Using string-based routes in new code — use type-safe routes
|
|
245
|
+
- Not using `launchSingleTop` for bottom nav tabs — duplicates destinations
|
|
246
|
+
- Not handling `navigateUp()` return value — can silently fail at root
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Related Skills
|
|
251
|
+
- `navigation` — general navigation patterns and principles
|
|
252
|
+
- `deep-navigation` — nested graphs and deep link handling
|
|
253
|
+
- `nested-navigation` — nested NavHost and graphs
|
|
254
|
+
- `hilt` — ViewModel injection with hiltViewModel()
|
|
255
|
+
- `savedstatehandle` — reading route arguments in ViewModel
|