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,209 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deep-navigation
|
|
3
|
+
description: >
|
|
4
|
+
Deep link handling in Android with Jetpack Compose Navigation.
|
|
5
|
+
Load this skill when implementing URI-based deep links, handling
|
|
6
|
+
incoming intents from notifications or external apps, defining
|
|
7
|
+
deep link patterns, or testing deep link navigation.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Deep Navigation
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Deep links allow the app to navigate directly to a specific screen from an external source — a notification, a web URL, another app, or an NFC tag. Navigation Compose supports deep links via `deepLinks` parameter on composable destinations.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Define deep link URIs in a central constants object — never inline strings
|
|
20
|
+
- Always verify deep links in the manifest with `android:autoVerify="true"` for App Links
|
|
21
|
+
- Handle deep link arguments the same way as regular nav arguments — via `toRoute()`
|
|
22
|
+
- Deep links must be **tested with adb** before release
|
|
23
|
+
- Provide fallback navigation when a deep link destination requires authentication
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Manifest Setup
|
|
28
|
+
|
|
29
|
+
```xml
|
|
30
|
+
<!-- AndroidManifest.xml -->
|
|
31
|
+
<activity
|
|
32
|
+
android:name=".MainActivity"
|
|
33
|
+
android:exported="true">
|
|
34
|
+
|
|
35
|
+
<intent-filter android:autoVerify="true">
|
|
36
|
+
<action android:name="android.intent.action.VIEW" />
|
|
37
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
38
|
+
<category android:name="android.intent.category.BROWSABLE" />
|
|
39
|
+
<data android:scheme="https"
|
|
40
|
+
android:host="example.com"
|
|
41
|
+
android:pathPrefix="/users" />
|
|
42
|
+
</intent-filter>
|
|
43
|
+
|
|
44
|
+
<!-- Custom scheme for internal deep links -->
|
|
45
|
+
<intent-filter>
|
|
46
|
+
<action android:name="android.intent.action.VIEW" />
|
|
47
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
48
|
+
<category android:name="android.intent.category.BROWSABLE" />
|
|
49
|
+
<data android:scheme="myapp" />
|
|
50
|
+
</intent-filter>
|
|
51
|
+
|
|
52
|
+
</activity>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Route with Deep Link
|
|
58
|
+
|
|
59
|
+
```kotlin
|
|
60
|
+
// ✅ Define deep link URIs centrally
|
|
61
|
+
object DeepLinks {
|
|
62
|
+
const val BASE = "https://example.com"
|
|
63
|
+
const val USER_DETAIL = "$BASE/users/{userId}"
|
|
64
|
+
const val ORDER_DETAIL = "$BASE/orders/{orderId}"
|
|
65
|
+
|
|
66
|
+
// Custom scheme
|
|
67
|
+
const val NOTIFICATION_BASE = "myapp://notification"
|
|
68
|
+
const val NOTIFICATION_DETAIL = "$NOTIFICATION_BASE/{notificationId}"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ✅ Attach deep links to composable destination
|
|
72
|
+
composable<UserDetailRoute>(
|
|
73
|
+
deepLinks = listOf(
|
|
74
|
+
navDeepLink<UserDetailRoute>(
|
|
75
|
+
basePath = "https://example.com/users"
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
) { backStackEntry ->
|
|
79
|
+
val route: UserDetailRoute = backStackEntry.toRoute()
|
|
80
|
+
UserDetailScreen(userId = route.userId)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ✅ Legacy string-based deep link (if not using type-safe routes)
|
|
84
|
+
composable(
|
|
85
|
+
route = "user/{userId}",
|
|
86
|
+
deepLinks = listOf(
|
|
87
|
+
navDeepLink {
|
|
88
|
+
uriPattern = DeepLinks.USER_DETAIL
|
|
89
|
+
}
|
|
90
|
+
),
|
|
91
|
+
arguments = listOf(navArgument("userId") { type = NavType.StringType })
|
|
92
|
+
) { backStackEntry ->
|
|
93
|
+
val userId = backStackEntry.arguments?.getString("userId") ?: return@composable
|
|
94
|
+
UserDetailScreen(userId = userId)
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Handling Incoming Intent in Activity
|
|
101
|
+
|
|
102
|
+
```kotlin
|
|
103
|
+
// ✅ Pass intent to NavHost for deep link handling
|
|
104
|
+
class MainActivity : ComponentActivity() {
|
|
105
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
106
|
+
super.onCreate(savedInstanceState)
|
|
107
|
+
setContent {
|
|
108
|
+
AppTheme {
|
|
109
|
+
val navController = rememberNavController()
|
|
110
|
+
AppNavHost(navController = navController)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Navigation Compose handles intent automatically when
|
|
117
|
+
// NavHost is set up — no manual intent parsing needed
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Notification Deep Links
|
|
123
|
+
|
|
124
|
+
```kotlin
|
|
125
|
+
// ✅ Build PendingIntent with deep link URI
|
|
126
|
+
fun buildNotificationIntent(context: Context, userId: String): PendingIntent {
|
|
127
|
+
val deepLinkUri = Uri.parse("https://example.com/users/$userId")
|
|
128
|
+
|
|
129
|
+
val intent = Intent(Intent.ACTION_VIEW, deepLinkUri).apply {
|
|
130
|
+
setPackage(context.packageName)
|
|
131
|
+
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return TaskStackBuilder.create(context).run {
|
|
135
|
+
addNextIntentWithParentStack(intent)
|
|
136
|
+
getPendingIntent(
|
|
137
|
+
userId.hashCode(),
|
|
138
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
139
|
+
)!!
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Auth Guard for Deep Links
|
|
147
|
+
|
|
148
|
+
```kotlin
|
|
149
|
+
// ✅ Redirect unauthenticated deep link to login
|
|
150
|
+
@Composable
|
|
151
|
+
fun AppNavHost(navController: NavHostController) {
|
|
152
|
+
val authState by authViewModel.state.collectAsStateWithLifecycle()
|
|
153
|
+
|
|
154
|
+
NavHost(navController = navController, startDestination = HomeRoute) {
|
|
155
|
+
|
|
156
|
+
composable<UserDetailRoute> { backStackEntry ->
|
|
157
|
+
if (authState !is AuthState.Authenticated) {
|
|
158
|
+
LaunchedEffect(Unit) {
|
|
159
|
+
navController.navigate(LoginRoute) {
|
|
160
|
+
// store original destination for post-login redirect
|
|
161
|
+
navController.currentBackStackEntry
|
|
162
|
+
?.savedStateHandle
|
|
163
|
+
?.set("pending_route", "user_detail")
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
val route: UserDetailRoute = backStackEntry.toRoute()
|
|
168
|
+
UserDetailScreen(userId = route.userId)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Testing Deep Links with ADB
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
# Test HTTPS deep link
|
|
181
|
+
adb shell am start \
|
|
182
|
+
-W -a android.intent.action.VIEW \
|
|
183
|
+
-d "https://example.com/users/123" \
|
|
184
|
+
com.example.app
|
|
185
|
+
|
|
186
|
+
# Test custom scheme
|
|
187
|
+
adb shell am start \
|
|
188
|
+
-W -a android.intent.action.VIEW \
|
|
189
|
+
-d "myapp://notification/456" \
|
|
190
|
+
com.example.app
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Anti-Patterns
|
|
196
|
+
|
|
197
|
+
- Inline URI strings in `navDeepLink {}` — define in a constants object
|
|
198
|
+
- Not adding `autoVerify="true"` for HTTPS App Links — won't be verified by Google
|
|
199
|
+
- Parsing the intent URI manually in `onCreate` — Navigation Compose handles this
|
|
200
|
+
- Not handling the unauthenticated deep link case — sends user to a broken state
|
|
201
|
+
- Using deep links for in-app navigation between features — use regular routes
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Related Skills
|
|
206
|
+
- `navigation` — core navigation setup
|
|
207
|
+
- `nested-navigation` — deep links into nested graphs
|
|
208
|
+
- `notification` — building notifications with deep link intents
|
|
209
|
+
- `manifest` — intent filter configuration
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: navigation
|
|
3
|
+
description: >
|
|
4
|
+
Navigation setup and patterns in Jetpack Compose using Navigation Compose.
|
|
5
|
+
Load this skill when setting up the NavHost, defining routes, passing
|
|
6
|
+
arguments between screens, handling back stack, or structuring navigation
|
|
7
|
+
in a single-activity architecture.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Navigation
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Navigation Compose is the standard navigation library for Jetpack Compose. It manages the back stack, screen transitions, and argument passing within a single-activity architecture. Routes are defined as type-safe objects using Kotlin serialization.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- One `NavHost` per app — never nest multiple NavHosts at the top level
|
|
20
|
+
- Routes must be **type-safe** using `@Serializable` data objects/classes — never string literals
|
|
21
|
+
- Navigation logic belongs in the **ViewModel or event callbacks** — never inside composables directly
|
|
22
|
+
- Pass only **IDs** between screens — never full objects
|
|
23
|
+
- Always use `launchSingleTop = true` for bottom navigation items
|
|
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
|
|
48
|
+
|
|
49
|
+
```kotlin
|
|
50
|
+
// ✅ Define routes as serializable objects/classes
|
|
51
|
+
@Serializable
|
|
52
|
+
data object HomeRoute
|
|
53
|
+
|
|
54
|
+
@Serializable
|
|
55
|
+
data object SettingsRoute
|
|
56
|
+
|
|
57
|
+
@Serializable
|
|
58
|
+
data class UserDetailRoute(val userId: String)
|
|
59
|
+
|
|
60
|
+
@Serializable
|
|
61
|
+
data class EditUserRoute(val userId: String, val mode: String = "edit")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## NavHost Setup
|
|
67
|
+
|
|
68
|
+
```kotlin
|
|
69
|
+
// ✅ NavHost at the app root
|
|
70
|
+
@Composable
|
|
71
|
+
fun AppNavHost(
|
|
72
|
+
navController: NavHostController = rememberNavController()
|
|
73
|
+
) {
|
|
74
|
+
NavHost(
|
|
75
|
+
navController = navController,
|
|
76
|
+
startDestination = HomeRoute
|
|
77
|
+
) {
|
|
78
|
+
composable<HomeRoute> {
|
|
79
|
+
HomeScreen(
|
|
80
|
+
onNavigateToUser = { userId ->
|
|
81
|
+
navController.navigate(UserDetailRoute(userId))
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
composable<UserDetailRoute> { backStackEntry ->
|
|
87
|
+
val route: UserDetailRoute = backStackEntry.toRoute()
|
|
88
|
+
UserDetailScreen(
|
|
89
|
+
userId = route.userId,
|
|
90
|
+
onNavigateBack = { navController.popBackStack() },
|
|
91
|
+
onNavigateToEdit = {
|
|
92
|
+
navController.navigate(EditUserRoute(route.userId))
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
composable<EditUserRoute> { backStackEntry ->
|
|
98
|
+
val route: EditUserRoute = backStackEntry.toRoute()
|
|
99
|
+
EditUserScreen(
|
|
100
|
+
userId = route.userId,
|
|
101
|
+
onNavigateBack = { navController.popBackStack() }
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
composable<SettingsRoute> {
|
|
106
|
+
SettingsScreen(
|
|
107
|
+
onNavigateBack = { navController.popBackStack() }
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Bottom Navigation
|
|
117
|
+
|
|
118
|
+
```kotlin
|
|
119
|
+
// ✅ Bottom navigation with launchSingleTop
|
|
120
|
+
@Composable
|
|
121
|
+
fun AppScaffold(navController: NavHostController) {
|
|
122
|
+
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
|
123
|
+
val currentDestination = navBackStackEntry?.destination
|
|
124
|
+
|
|
125
|
+
Scaffold(
|
|
126
|
+
bottomBar = {
|
|
127
|
+
NavigationBar {
|
|
128
|
+
bottomNavItems.forEach { item ->
|
|
129
|
+
NavigationBarItem(
|
|
130
|
+
icon = { Icon(item.icon, contentDescription = null) },
|
|
131
|
+
label = { Text(item.label) },
|
|
132
|
+
selected = currentDestination?.hierarchy?.any {
|
|
133
|
+
it.hasRoute(item.route::class)
|
|
134
|
+
} == true,
|
|
135
|
+
onClick = {
|
|
136
|
+
navController.navigate(item.route) {
|
|
137
|
+
popUpTo(navController.graph.findStartDestination().id) {
|
|
138
|
+
saveState = true
|
|
139
|
+
}
|
|
140
|
+
launchSingleTop = true // ✅ avoid duplicates
|
|
141
|
+
restoreState = true // ✅ restore tab state
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
) { padding ->
|
|
149
|
+
AppNavHost(
|
|
150
|
+
navController = navController,
|
|
151
|
+
modifier = Modifier.padding(padding)
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Passing Results Back
|
|
160
|
+
|
|
161
|
+
```kotlin
|
|
162
|
+
// ✅ Pass result back via SavedStateHandle
|
|
163
|
+
// In destination screen
|
|
164
|
+
navController.previousBackStackEntry
|
|
165
|
+
?.savedStateHandle
|
|
166
|
+
?.set("selected_item_id", selectedItemId)
|
|
167
|
+
navController.popBackStack()
|
|
168
|
+
|
|
169
|
+
// ✅ Observe in source screen's ViewModel
|
|
170
|
+
class SourceViewModel(
|
|
171
|
+
savedStateHandle: SavedStateHandle
|
|
172
|
+
) : ViewModel() {
|
|
173
|
+
val selectedItemId = savedStateHandle
|
|
174
|
+
.getStateFlow<String?>("selected_item_id", null)
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Navigation Extensions
|
|
181
|
+
|
|
182
|
+
```kotlin
|
|
183
|
+
// ✅ Extension to reduce boilerplate
|
|
184
|
+
fun NavController.navigateSingleTop(route: Any) {
|
|
185
|
+
navigate(route) {
|
|
186
|
+
launchSingleTop = true
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
fun NavController.navigateAndClearStack(route: Any) {
|
|
191
|
+
navigate(route) {
|
|
192
|
+
popUpTo(0) { inclusive = true }
|
|
193
|
+
launchSingleTop = true
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Anti-Patterns
|
|
201
|
+
|
|
202
|
+
- Using raw string routes — loses type safety and refactoring support
|
|
203
|
+
- Passing full objects (Parcelable/Serializable) as nav arguments — pass IDs only
|
|
204
|
+
- Calling `navController.navigate()` directly inside a composable body — use callbacks or events
|
|
205
|
+
- Nesting `NavHost` inside another `NavHost` at the top level — use nested graphs instead
|
|
206
|
+
- Not using `launchSingleTop` for bottom nav — creates duplicate back stack entries
|
|
207
|
+
- Holding `NavController` reference in ViewModel — pass navigation events as lambdas
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Related Skills
|
|
212
|
+
- `compose-navigation` — advanced NavHost patterns and animations
|
|
213
|
+
- `deep-navigation` — deep links and external navigation
|
|
214
|
+
- `nested-navigation` — nested graphs and feature module navigation
|
|
215
|
+
- `savedstatehandle` — passing data between screens
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nested-navigation
|
|
3
|
+
description: >
|
|
4
|
+
Nested navigation graphs in Jetpack Compose Navigation.
|
|
5
|
+
Load this skill when organizing routes into feature-scoped graphs,
|
|
6
|
+
implementing tab-based navigation with independent back stacks,
|
|
7
|
+
isolating feature module navigation, or structuring auth vs main flows.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Nested Navigation
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Nested navigation graphs group related destinations into a sub-graph with its own start destination. This enables feature isolation, independent back stacks per tab, and clean separation between flows (e.g. auth flow vs main flow).
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Each feature or flow gets its own nested graph — never dump all routes into one flat NavHost
|
|
20
|
+
- Nested graphs have their own `startDestination` — the entry point is the only public route
|
|
21
|
+
- Tab-based navigation uses `saveState`/`restoreState` to preserve independent back stacks
|
|
22
|
+
- Auth flow and main flow must be separate nested graphs — not mixed in the same graph
|
|
23
|
+
- Never navigate directly to an internal route of another feature's graph
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Basic Nested Graph
|
|
28
|
+
|
|
29
|
+
```kotlin
|
|
30
|
+
// ✅ Define feature graph routes
|
|
31
|
+
@Serializable data object UserGraph
|
|
32
|
+
@Serializable data object UserListRoute
|
|
33
|
+
@Serializable data class UserDetailRoute(val userId: String)
|
|
34
|
+
@Serializable data class UserEditRoute(val userId: String)
|
|
35
|
+
|
|
36
|
+
// ✅ Register nested graph in NavHost
|
|
37
|
+
NavHost(navController = navController, startDestination = UserGraph) {
|
|
38
|
+
|
|
39
|
+
navigation<UserGraph>(startDestination = UserListRoute) {
|
|
40
|
+
|
|
41
|
+
composable<UserListRoute> {
|
|
42
|
+
UserListScreen(
|
|
43
|
+
onNavigateToDetail = { userId ->
|
|
44
|
+
navController.navigate(UserDetailRoute(userId))
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
composable<UserDetailRoute> { backStackEntry ->
|
|
50
|
+
val route: UserDetailRoute = backStackEntry.toRoute()
|
|
51
|
+
UserDetailScreen(
|
|
52
|
+
userId = route.userId,
|
|
53
|
+
onNavigateToEdit = {
|
|
54
|
+
navController.navigate(UserEditRoute(route.userId))
|
|
55
|
+
},
|
|
56
|
+
onNavigateBack = { navController.popBackStack() }
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
composable<UserEditRoute> { backStackEntry ->
|
|
61
|
+
val route: UserEditRoute = backStackEntry.toRoute()
|
|
62
|
+
UserEditScreen(
|
|
63
|
+
userId = route.userId,
|
|
64
|
+
onNavigateBack = { navController.popBackStack() }
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Auth vs Main Flow
|
|
74
|
+
|
|
75
|
+
```kotlin
|
|
76
|
+
// ✅ Separate auth and main flows as nested graphs
|
|
77
|
+
@Serializable data object AuthGraph
|
|
78
|
+
@Serializable data object LoginRoute
|
|
79
|
+
@Serializable data object RegisterRoute
|
|
80
|
+
|
|
81
|
+
@Serializable data object MainGraph
|
|
82
|
+
@Serializable data object HomeRoute
|
|
83
|
+
@Serializable data object SettingsRoute
|
|
84
|
+
|
|
85
|
+
@Composable
|
|
86
|
+
fun AppNavHost(
|
|
87
|
+
navController: NavHostController,
|
|
88
|
+
isAuthenticated: Boolean
|
|
89
|
+
) {
|
|
90
|
+
NavHost(
|
|
91
|
+
navController = navController,
|
|
92
|
+
startDestination = if (isAuthenticated) MainGraph else AuthGraph
|
|
93
|
+
) {
|
|
94
|
+
|
|
95
|
+
navigation<AuthGraph>(startDestination = LoginRoute) {
|
|
96
|
+
composable<LoginRoute> {
|
|
97
|
+
LoginScreen(
|
|
98
|
+
onLoginSuccess = {
|
|
99
|
+
navController.navigate(MainGraph) {
|
|
100
|
+
popUpTo(AuthGraph) { inclusive = true } // ✅ clear auth stack
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
onNavigateToRegister = {
|
|
104
|
+
navController.navigate(RegisterRoute)
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
composable<RegisterRoute> {
|
|
109
|
+
RegisterScreen(
|
|
110
|
+
onRegisterSuccess = {
|
|
111
|
+
navController.navigate(MainGraph) {
|
|
112
|
+
popUpTo(AuthGraph) { inclusive = true }
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
onNavigateBack = { navController.popBackStack() }
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
navigation<MainGraph>(startDestination = HomeRoute) {
|
|
121
|
+
composable<HomeRoute> { HomeScreen() }
|
|
122
|
+
composable<SettingsRoute> { SettingsScreen() }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Tab Navigation with Independent Back Stacks
|
|
131
|
+
|
|
132
|
+
```kotlin
|
|
133
|
+
// ✅ Each tab is a nested graph with its own back stack
|
|
134
|
+
@Serializable data object HomeGraph
|
|
135
|
+
@Serializable data object HomeRootRoute
|
|
136
|
+
|
|
137
|
+
@Serializable data object SearchGraph
|
|
138
|
+
@Serializable data object SearchRootRoute
|
|
139
|
+
|
|
140
|
+
@Serializable data object ProfileGraph
|
|
141
|
+
@Serializable data object ProfileRootRoute
|
|
142
|
+
|
|
143
|
+
@Composable
|
|
144
|
+
fun MainScreen() {
|
|
145
|
+
val navController = rememberNavController()
|
|
146
|
+
|
|
147
|
+
Scaffold(
|
|
148
|
+
bottomBar = {
|
|
149
|
+
BottomNavBar(navController = navController)
|
|
150
|
+
}
|
|
151
|
+
) { padding ->
|
|
152
|
+
NavHost(
|
|
153
|
+
navController = navController,
|
|
154
|
+
startDestination = HomeGraph,
|
|
155
|
+
modifier = Modifier.padding(padding)
|
|
156
|
+
) {
|
|
157
|
+
navigation<HomeGraph>(startDestination = HomeRootRoute) {
|
|
158
|
+
composable<HomeRootRoute> { HomeScreen() }
|
|
159
|
+
// more home destinations...
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
navigation<SearchGraph>(startDestination = SearchRootRoute) {
|
|
163
|
+
composable<SearchRootRoute> { SearchScreen() }
|
|
164
|
+
// more search destinations...
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
navigation<ProfileGraph>(startDestination = ProfileRootRoute) {
|
|
168
|
+
composable<ProfileRootRoute> { ProfileScreen() }
|
|
169
|
+
// more profile destinations...
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ✅ Bottom nav switching with state save/restore
|
|
176
|
+
fun NavController.switchTab(route: Any) {
|
|
177
|
+
navigate(route) {
|
|
178
|
+
popUpTo(graph.findStartDestination().id) {
|
|
179
|
+
saveState = true // ✅ save current tab's back stack
|
|
180
|
+
}
|
|
181
|
+
launchSingleTop = true
|
|
182
|
+
restoreState = true // ✅ restore destination tab's back stack
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Popping to Graph Start
|
|
190
|
+
|
|
191
|
+
```kotlin
|
|
192
|
+
// ✅ Pop back to the start of a nested graph
|
|
193
|
+
navController.popBackStack(route = UserListRoute, inclusive = false)
|
|
194
|
+
|
|
195
|
+
// ✅ Pop the entire graph off the stack
|
|
196
|
+
navController.popBackStack(route = UserGraph, inclusive = true)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Anti-Patterns
|
|
202
|
+
|
|
203
|
+
- Flat NavHost with all routes at the same level — hard to maintain and reason about
|
|
204
|
+
- Navigating directly to an internal route of another feature graph from outside it
|
|
205
|
+
- Not using `saveState`/`restoreState` for tab navigation — back stack lost on tab switch
|
|
206
|
+
- Not clearing the auth graph when navigating to main — user can back-navigate to login
|
|
207
|
+
- Sharing a single `NavController` across nested `NavHost` composables
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Related Skills
|
|
212
|
+
- `navigation` — core navigation setup and route definitions
|
|
213
|
+
- `deep-navigation` — deep links into nested graphs
|
|
214
|
+
- `compose-navigation` — transitions and animations between graphs
|