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.
Files changed (176) hide show
  1. package/dist/index.js +143 -0
  2. package/package.json +27 -0
  3. package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
  4. package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
  5. package/skills/Android Platform/Configuration/SKILL.md +201 -0
  6. package/skills/Android Platform/Filesystem/SKILL.md +216 -0
  7. package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
  8. package/skills/Android Platform/Manifest/SKILL.md +226 -0
  9. package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
  10. package/skills/Android Platform/Resources/SKILL.md +234 -0
  11. package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
  12. package/skills/Android Platform/State Restoration/SKILL.md +210 -0
  13. package/skills/Architecture/Bounded Context/SKILL.md +207 -0
  14. package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
  15. package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
  16. package/skills/Architecture/Entity Design/SKILL.md +243 -0
  17. package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
  18. package/skills/Architecture/MVI/SKILL.md +224 -0
  19. package/skills/Architecture/MVVM/SKILL.md +198 -0
  20. package/skills/Architecture/Modularization/SKILL.md +194 -0
  21. package/skills/Architecture/Offline First/SKILL.md +249 -0
  22. package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
  23. package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
  24. package/skills/Architecture/State Management/SKILL.md +229 -0
  25. package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
  26. package/skills/Architecture/Use Case Design/SKILL.md +244 -0
  27. package/skills/Architecture/Value Object/SKILL.md +226 -0
  28. package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
  29. package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
  30. package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
  31. package/skills/Build System/Build Cache/SKILL.md +233 -0
  32. package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
  33. package/skills/Build System/Build Variant/SKILL.md +215 -0
  34. package/skills/Build System/Convention Plugin/SKILL.md +288 -0
  35. package/skills/Build System/Dependency Management/SKILL.md +261 -0
  36. package/skills/Build System/Gradle/SKILL.md +284 -0
  37. package/skills/Build System/Incremental Build/SKILL.md +199 -0
  38. package/skills/Build System/KAPT/SKILL.md +198 -0
  39. package/skills/Build System/KSP/SKILL.md +263 -0
  40. package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
  41. package/skills/Build System/Specialized/C++/SKILL.md +308 -0
  42. package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
  43. package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
  44. package/skills/Build System/Version Catalog/SKILL.md +304 -0
  45. package/skills/Concurrency/Background Processing/SKILL.md +185 -0
  46. package/skills/Concurrency/Channel/SKILL.md +207 -0
  47. package/skills/Concurrency/Coroutine/SKILL.md +200 -0
  48. package/skills/Concurrency/Flow/SKILL.md +179 -0
  49. package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
  50. package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
  51. package/skills/Concurrency/StateFlow/SKILL.md +175 -0
  52. package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
  53. package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
  54. package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
  55. package/skills/Core Language/DSL/SKILL.md +186 -0
  56. package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
  57. package/skills/Core Language/Immutability/SKILL.md +156 -0
  58. package/skills/Core Language/KMP/SKILL.md +182 -0
  59. package/skills/Core Language/Kotlin/SKILL.md +187 -0
  60. package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
  61. package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
  62. package/skills/Core Language/Serialization/SKILL.md +191 -0
  63. package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
  64. package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
  65. package/skills/Data Layer/DAO/SKILL.md +225 -0
  66. package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
  67. package/skills/Data Layer/DataStore/SKILL.md +264 -0
  68. package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
  69. package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
  70. package/skills/Data Layer/File Storage/SKILL.md +247 -0
  71. package/skills/Data Layer/Indexing/SKILL.md +184 -0
  72. package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
  73. package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
  74. package/skills/Data Layer/Migration/SKILL.md +243 -0
  75. package/skills/Data Layer/Paging/SKILL.md +264 -0
  76. package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
  77. package/skills/Data Layer/Room/SKILL.md +244 -0
  78. package/skills/Data Layer/SQLite/SKILL.md +255 -0
  79. package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
  80. package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
  81. package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
  82. package/skills/Dependency Injection/Koin/SKILL.md +282 -0
  83. package/skills/Developer Experience/Detekt/SKILL.md +272 -0
  84. package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
  85. package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
  86. package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
  87. package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
  88. package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
  89. package/skills/Media/Audio/SKILL.md +257 -0
  90. package/skills/Media/Camera/SKILL.md +229 -0
  91. package/skills/Media/CameraX/SKILL.md +295 -0
  92. package/skills/Media/ExoPlayer/SKILL.md +258 -0
  93. package/skills/Media/Video/SKILL.md +228 -0
  94. package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
  95. package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
  96. package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
  97. package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
  98. package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
  99. package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
  100. package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
  101. package/skills/Navigation/Navigation/SKILL.md +215 -0
  102. package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
  103. package/skills/Networking/API Contract/SKILL.md +220 -0
  104. package/skills/Networking/Authentication/SKILL.md +210 -0
  105. package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
  106. package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
  107. package/skills/Networking/Ktor/SKILL.md +219 -0
  108. package/skills/Networking/Multipart Upload/SKILL.md +213 -0
  109. package/skills/Networking/OkHttp/SKILL.md +193 -0
  110. package/skills/Networking/REST/SKILL.md +178 -0
  111. package/skills/Networking/Rate Limiting/SKILL.md +170 -0
  112. package/skills/Networking/Retrofit/SKILL.md +241 -0
  113. package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
  114. package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
  115. package/skills/Networking/WebSocket/SKILL.md +224 -0
  116. package/skills/Observability/Crash Reporting/SKILL.md +219 -0
  117. package/skills/Observability/Logging/SKILL.md +168 -0
  118. package/skills/Observability/Metrics/SKILL.md +227 -0
  119. package/skills/Observability/Structured Logging/SKILL.md +234 -0
  120. package/skills/Performance/ANR Prevention/SKILL.md +192 -0
  121. package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
  122. package/skills/Performance/App Startup/SKILL.md +183 -0
  123. package/skills/Performance/Baseline Profile/SKILL.md +205 -0
  124. package/skills/Performance/Battery Optimization/SKILL.md +192 -0
  125. package/skills/Performance/Benchmark/SKILL.md +182 -0
  126. package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
  127. package/skills/Performance/Compose Optimization/SKILL.md +187 -0
  128. package/skills/Performance/Heap Management/SKILL.md +184 -0
  129. package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
  130. package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
  131. package/skills/Performance/Rendering Performance/SKILL.md +205 -0
  132. package/skills/Performance/Startup Optimization/SKILL.md +219 -0
  133. package/skills/Security/Biometric/SKILL.md +224 -0
  134. package/skills/Security/Certificate Transparency/SKILL.md +158 -0
  135. package/skills/Security/Cryptography/SKILL.md +244 -0
  136. package/skills/Security/Encrypted Storage/SKILL.md +273 -0
  137. package/skills/Security/Frida Detection/SKILL.md +230 -0
  138. package/skills/Security/Hook Detection/SKILL.md +197 -0
  139. package/skills/Security/Keystore/SKILL.md +272 -0
  140. package/skills/Security/Network Security Config/SKILL.md +186 -0
  141. package/skills/Security/Obfuscation/SKILL.md +226 -0
  142. package/skills/Security/Proguard/SKILL.md +202 -0
  143. package/skills/Security/R8/SKILL.md +234 -0
  144. package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
  145. package/skills/Security/Root Detection/SKILL.md +220 -0
  146. package/skills/Security/Secure Networking/SKILL.md +220 -0
  147. package/skills/System Integration/AlarmManager/SKILL.md +182 -0
  148. package/skills/System Integration/App Widget/SKILL.md +182 -0
  149. package/skills/System Integration/Deep Link/SKILL.md +187 -0
  150. package/skills/System Integration/Foreground Service/SKILL.md +212 -0
  151. package/skills/System Integration/Notification/SKILL.md +237 -0
  152. package/skills/System Integration/WorkManager/SKILL.md +256 -0
  153. package/skills/System Integration/clipboard/SKILL.md +155 -0
  154. package/skills/System Integration/share-intent/SKILL.md +182 -0
  155. package/skills/Testing/Compose Testing/SKILL.md +296 -0
  156. package/skills/Testing/Espresso/SKILL.md +292 -0
  157. package/skills/Testing/Fake Data/SKILL.md +245 -0
  158. package/skills/Testing/Integration Testing/SKILL.md +288 -0
  159. package/skills/Testing/Mocking/SKILL.md +229 -0
  160. package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
  161. package/skills/Testing/UI Testing/SKILL.md +293 -0
  162. package/skills/Testing/Unit Testing/SKILL.md +309 -0
  163. package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
  164. package/skills/UI System/Compose/SKILL.md +296 -0
  165. package/skills/UI System/Compose Animation/SKILL.md +281 -0
  166. package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
  167. package/skills/UI System/Compose Navigation/SKILL.md +255 -0
  168. package/skills/UI System/Compose Performance/SKILL.md +274 -0
  169. package/skills/UI System/Design System/SKILL.md +217 -0
  170. package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
  171. package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
  172. package/skills/UI System/Loading Strategy/SKILL.md +254 -0
  173. package/skills/UI System/Material 3/SKILL.md +279 -0
  174. package/skills/UI System/RTL/SKILL.md +179 -0
  175. package/src/index.ts +182 -0
  176. package/tsconfig.json +19 -0
@@ -0,0 +1,214 @@
1
+ ---
2
+ name: process-death-recovery
3
+ description: >
4
+ Process death detection, prevention, and recovery strategies for Android.
5
+ Load this skill when designing state that must survive system-initiated
6
+ process kills, implementing session recovery, or testing background kill scenarios.
7
+ ---
8
+
9
+ # Process Death Recovery
10
+
11
+ ## Overview
12
+
13
+ Android can kill an app's process at any time when it's in the background — to free memory. When the user returns, Android recreates the Activity and restores the back stack, but all in-memory state (including ViewModel) is gone. A well-designed app recovers seamlessly without data loss.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Assume process death can happen **at any time** — design accordingly
20
+ - ViewModel does **not** survive process death — only configuration change
21
+ - SavedStateHandle survives process death — use it for critical UI state
22
+ - Persistent storage (Room, DataStore) always survives — use for important data
23
+ - Never show a blank or broken screen after process death recovery
24
+
25
+ ---
26
+
27
+ ## What Survives What
28
+
29
+ | Storage | Config Change | Process Death | App Kill (swipe) |
30
+ | ----------------- | ------------- | ------------- | ---------------- |
31
+ | ViewModel | ✅ | ❌ | ❌ |
32
+ | SavedStateHandle | ✅ | ✅ | ❌ |
33
+ | Room / DataStore | ✅ | ✅ | ✅ |
34
+ | SharedPreferences | ✅ | ✅ | ✅ |
35
+ | In-memory cache | ✅ | ❌ | ❌ |
36
+
37
+ ---
38
+
39
+ ## Recovery Strategy by State Type
40
+
41
+ ```kotlin
42
+ // ✅ User input — save in SavedStateHandle
43
+ @HiltViewModel
44
+ class FormViewModel @Inject constructor(
45
+ savedStateHandle: SavedStateHandle
46
+ ) : ViewModel() {
47
+ val name = savedStateHandle.getStateFlow("name", "")
48
+ val email = savedStateHandle.getStateFlow("email", "")
49
+
50
+ fun onNameChanged(value: String) { savedStateHandle["name"] = value }
51
+ fun onEmailChanged(value: String) { savedStateHandle["email"] = value }
52
+ }
53
+
54
+ // ✅ Loaded data — reload from Room/repository on init
55
+ @HiltViewModel
56
+ class UserListViewModel @Inject constructor(
57
+ private val repository: UserRepository
58
+ ) : ViewModel() {
59
+ val users: StateFlow<List<User>> = repository
60
+ .observeUsers() // Room Flow — always fresh after process death
61
+ .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
62
+ }
63
+
64
+ // ✅ Current screen/selection — save ID in SavedStateHandle, reload data
65
+ @HiltViewModel
66
+ class UserDetailViewModel @Inject constructor(
67
+ savedStateHandle: SavedStateHandle,
68
+ private val repository: UserRepository
69
+ ) : ViewModel() {
70
+ private val userId: String = checkNotNull(savedStateHandle["userId"])
71
+
72
+ val user: StateFlow<User?> = repository
73
+ .observeUser(userId) // reload data using saved ID
74
+ .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
75
+ }
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Detecting Process Death vs Fresh Start
81
+
82
+ ```kotlin
83
+ // ✅ Detect if Activity was restored after process death
84
+ class MainActivity : ComponentActivity() {
85
+ override fun onCreate(savedInstanceState: Bundle?) {
86
+ super.onCreate(savedInstanceState)
87
+
88
+ if (savedInstanceState != null) {
89
+ // Restored — either config change or process death recovery
90
+ // ViewModel + SavedStateHandle already restored
91
+ } else {
92
+ // Fresh start
93
+ }
94
+ }
95
+ }
96
+
97
+ // ✅ In ViewModel — check if state needs initialization
98
+ @HiltViewModel
99
+ class OnboardingViewModel @Inject constructor(
100
+ savedStateHandle: SavedStateHandle,
101
+ private val repository: OnboardingRepository
102
+ ) : ViewModel() {
103
+
104
+ private val currentStep = savedStateHandle.getStateFlow("step", 0)
105
+
106
+ init {
107
+ // Don't reset step — it may have been restored from SavedStateHandle
108
+ if (currentStep.value == 0) {
109
+ viewModelScope.launch { loadInitialState() }
110
+ }
111
+ }
112
+ }
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Incomplete Operations Recovery
118
+
119
+ ```kotlin
120
+ // ✅ For critical operations (payment, upload) — use WorkManager
121
+ // WorkManager survives process death and system reboots
122
+ class UploadWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
123
+ override suspend fun doWork(): Result {
124
+ val fileUri = inputData.getString("file_uri") ?: return Result.failure()
125
+ return try {
126
+ uploadFile(fileUri)
127
+ Result.success()
128
+ } catch (e: Exception) {
129
+ Result.retry()
130
+ }
131
+ }
132
+ }
133
+
134
+ // Enqueue — survives process death
135
+ WorkManager.getInstance(context)
136
+ .enqueueUniqueWork(
137
+ "upload_${fileId}",
138
+ ExistingWorkPolicy.KEEP,
139
+ OneTimeWorkRequestBuilder<UploadWorker>()
140
+ .setInputData(workDataOf("file_uri" to uri))
141
+ .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
142
+ .build()
143
+ )
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Session State Recovery
149
+
150
+ ```kotlin
151
+ // ✅ Persist session-critical state to DataStore immediately on change
152
+ class SessionRepository @Inject constructor(
153
+ private val dataStore: DataStore<Preferences>
154
+ ) {
155
+ suspend fun saveLastViewedUserId(userId: String) {
156
+ dataStore.edit { prefs ->
157
+ prefs[LAST_USER_ID] = userId
158
+ }
159
+ }
160
+
161
+ fun observeLastViewedUserId(): Flow<String?> =
162
+ dataStore.data.map { it[LAST_USER_ID] }
163
+
164
+ companion object {
165
+ val LAST_USER_ID = stringPreferencesKey("last_user_id")
166
+ }
167
+ }
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Testing Process Death
173
+
174
+ ```bash
175
+ # ✅ Simulate process death via ADB — put app in background first
176
+ adb shell am kill <package_name>
177
+
178
+ # Then return to app from Recents — should recover gracefully
179
+
180
+ # ✅ Android Studio — use "Terminate Application" button
181
+ # while app is in background (minimized)
182
+ ```
183
+
184
+ ```kotlin
185
+ // ✅ Write tests for SavedStateHandle restoration
186
+ @Test
187
+ fun `state is restored after process death`() {
188
+ val savedState = SavedStateHandle(mapOf("search_query" to "kotlin"))
189
+ val viewModel = SearchViewModel(savedState, fakeRepository)
190
+
191
+ assertThat(viewModel.query.value).isEqualTo("kotlin")
192
+ }
193
+ ```
194
+
195
+ ---
196
+
197
+ ## Anti-Patterns
198
+
199
+ - Assuming ViewModel survives process death — it doesn't
200
+ - Storing non-Parcelable objects in SavedStateHandle — crashes on restore
201
+ - Not testing process death — the most common untested scenario
202
+ - Saving too much in SavedStateHandle — it has a size limit (~500KB total)
203
+ - Relying on `onSaveInstanceState` in Activity instead of SavedStateHandle in ViewModel
204
+ - Starting critical operations (payments, uploads) without WorkManager fallback
205
+
206
+ ---
207
+
208
+ ## Related Skills
209
+
210
+ - `savedstatehandle` — key-value persistence across process death
211
+ - `state-restoration` — full state restoration strategy
212
+ - `workmanager` — durable background work across process death
213
+ - `datastore` — persisting session state
214
+ - `room` — persistent data that always survives
@@ -0,0 +1,234 @@
1
+ ---
2
+ name: resources
3
+ description: >
4
+ Android resource system — strings, colors, dimensions, drawables, styles, and qualifiers.
5
+ Load this skill when working with res/ directory, defining or accessing resources,
6
+ supporting multiple screen sizes, locales, or themes.
7
+ ---
8
+
9
+ # Resources
10
+
11
+ ## Overview
12
+
13
+ Android resources are external files and static values used by the app — strings, colors, dimensions, drawables, layouts, styles, and more. They live in `res/` and are accessed via generated `R` class. Resources support qualifiers that allow automatic selection based on device configuration.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Never hardcode strings, colors, or dimensions in code or layouts — always use resources
20
+ - Use qualifiers for configuration-specific resources — don't detect configuration in code
21
+ - All user-facing strings must be in `strings.xml` — enables localization
22
+ - Colors must be defined in `colors.xml` and referenced via theme attributes — not directly
23
+ - Dimensions must be in `dimens.xml` — enables density-independent scaling
24
+
25
+ ---
26
+
27
+ ## Directory Structure
28
+
29
+ ```
30
+ res/
31
+ ├── drawable/ ← vector drawables, shape drawables
32
+ ├── drawable-xxhdpi/ ← raster images (prefer vectors)
33
+ ├── layout/ ← XML layouts (if using View system)
34
+ ├── mipmap-*/ ← launcher icons only
35
+ ├── values/
36
+ │ ├── strings.xml
37
+ │ ├── colors.xml
38
+ │ ├── dimens.xml
39
+ │ ├── styles.xml
40
+ │ └── themes.xml
41
+ ├── values-night/ ← dark theme overrides
42
+ ├── values-fa/ ← Persian strings
43
+ ├── values-land/ ← landscape overrides
44
+ └── xml/ ← network_security_config, file_paths, etc.
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Strings
50
+
51
+ ```xml
52
+ <!-- ✅ strings.xml -->
53
+ <resources>
54
+ <!-- Simple string -->
55
+ <string name="app_name">MyApp</string>
56
+ <string name="action_save">Save</string>
57
+
58
+ <!-- ✅ Formatted string with argument -->
59
+ <string name="welcome_message">Welcome, %1$s!</string>
60
+
61
+ <!-- ✅ Plural strings -->
62
+ <plurals name="item_count">
63
+ <item quantity="one">%d item</item>
64
+ <item quantity="other">%d items</item>
65
+ </plurals>
66
+
67
+ <!-- ✅ String array -->
68
+ <string-array name="days_of_week">
69
+ <item>Monday</item>
70
+ <item>Tuesday</item>
71
+ </string-array>
72
+ </resources>
73
+ ```
74
+
75
+ ```kotlin
76
+ // ✅ Accessing strings in code
77
+ val welcome = getString(R.string.welcome_message, userName)
78
+ val itemCount = resources.getQuantityString(R.plurals.item_count, count, count)
79
+
80
+ // ✅ In Compose
81
+ Text(text = stringResource(R.string.welcome_message, userName))
82
+ Text(text = pluralStringResource(R.plurals.item_count, count, count))
83
+ ```
84
+
85
+ ---
86
+
87
+ ## Colors
88
+
89
+ ```xml
90
+ <!-- ✅ colors.xml — raw color palette -->
91
+ <resources>
92
+ <color name="purple_500">#FF6200EE</color>
93
+ <color name="purple_700">#FF3700B3</color>
94
+ <color name="teal_200">#FF03DAC5</color>
95
+ <color name="white">#FFFFFFFF</color>
96
+ <color name="black">#FF000000</color>
97
+ </resources>
98
+
99
+ <!-- ✅ themes.xml — semantic color attributes referencing palette -->
100
+ <style name="Theme.App" parent="Theme.Material3.DayNight">
101
+ <item name="colorPrimary">@color/purple_500</item>
102
+ <item name="colorOnPrimary">@color/white</item>
103
+ <item name="colorSurface">@color/white</item>
104
+ </style>
105
+
106
+ <!-- ✅ values-night/themes.xml — dark theme overrides -->
107
+ <style name="Theme.App" parent="Theme.Material3.DayNight">
108
+ <item name="colorPrimary">@color/purple_200</item>
109
+ <item name="colorSurface">@color/black</item>
110
+ </style>
111
+ ```
112
+
113
+ ```kotlin
114
+ // ✅ Always access color via theme attribute — not directly
115
+ val color = MaterialColors.getColor(view, com.google.android.material.R.attr.colorPrimary)
116
+
117
+ // ✅ In Compose — use MaterialTheme tokens
118
+ Surface(color = MaterialTheme.colorScheme.surface) { ... }
119
+
120
+ // ❌ Never hardcode or access raw color directly in code
121
+ view.setBackgroundColor(Color.parseColor("#FF6200EE"))
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Dimensions
127
+
128
+ ```xml
129
+ <!-- ✅ dimens.xml -->
130
+ <resources>
131
+ <dimen name="spacing_small">8dp</dimen>
132
+ <dimen name="spacing_medium">16dp</dimen>
133
+ <dimen name="spacing_large">24dp</dimen>
134
+ <dimen name="corner_radius">12dp</dimen>
135
+ <dimen name="text_size_body">14sp</dimen>
136
+ <dimen name="text_size_title">20sp</dimen>
137
+ </resources>
138
+ ```
139
+
140
+ ```kotlin
141
+ // ✅ Accessing dimensions
142
+ val spacing = resources.getDimensionPixelSize(R.dimen.spacing_medium)
143
+
144
+ // ✅ In Compose — use dp/sp directly or define as constants
145
+ val SpacingMedium = 16.dp
146
+ val TextSizeBody = 14.sp
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Drawables
152
+
153
+ ```xml
154
+ <!-- ✅ Use vector drawables — scale perfectly on all densities -->
155
+ <!-- res/drawable/ic_arrow_back.xml -->
156
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
157
+ android:width="24dp"
158
+ android:height="24dp"
159
+ android:viewportWidth="24"
160
+ android:viewportHeight="24"
161
+ android:tint="?attr/colorOnSurface">
162
+ <path android:fillColor="@android:color/white"
163
+ android:pathData="M20,11H7.83l5.59-5.59L12,4l-8,8 8,8 1.41-1.41L7.83,13H20v-2z"/>
164
+ </vector>
165
+ ```
166
+
167
+ ```
168
+ // ✅ Raster images — use only when vector isn't suitable
169
+ // Place in density buckets:
170
+ drawable-mdpi/ (1x)
171
+ drawable-hdpi/ (1.5x)
172
+ drawable-xhdpi/ (2x)
173
+ drawable-xxhdpi/ (3x)
174
+ drawable-xxxhdpi/ (4x)
175
+ ```
176
+
177
+ ---
178
+
179
+ ## Configuration Qualifiers
180
+
181
+ ```
182
+ values/ ← default
183
+ values-fa/ ← Persian locale
184
+ values-night/ ← dark mode
185
+ values-land/ ← landscape
186
+ values-sw600dp/ ← tablets (600dp+ smallest width)
187
+ values-v33/ ← API 33+
188
+
189
+ layout/ ← default layout
190
+ layout-land/ ← landscape layout
191
+ layout-sw600dp/ ← tablet layout
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Accessing Resources in Code
197
+
198
+ ```kotlin
199
+ // ✅ From Activity/Fragment
200
+ val text = getString(R.string.app_name)
201
+ val color = ContextCompat.getColor(this, R.color.purple_500)
202
+ val drawable = ContextCompat.getDrawable(this, R.drawable.ic_arrow_back)
203
+ val dimen = resources.getDimensionPixelSize(R.dimen.spacing_medium)
204
+
205
+ // ✅ From ViewModel (via injected Context — use ApplicationContext only)
206
+ class MyViewModel(private val appContext: Context) : ViewModel() {
207
+ fun getLabel(): String = appContext.getString(R.string.app_name)
208
+ }
209
+
210
+ // ✅ Compose
211
+ Text(stringResource(R.string.app_name))
212
+ Icon(painterResource(R.drawable.ic_arrow_back), contentDescription = null)
213
+ ```
214
+
215
+ ---
216
+
217
+ ## Anti-Patterns
218
+
219
+ - Hardcoded strings in XML or code — breaks localization
220
+ - Accessing colors directly (`R.color.x`) instead of via theme attributes — breaks dark mode
221
+ - Using `px` instead of `dp`/`sp` in dimensions — breaks across densities
222
+ - Placing launcher icons in `drawable/` instead of `mipmap/` — incorrect scaling
223
+ - Using raster images where vectors would work — increases APK size
224
+ - Storing sensitive data (keys, tokens) as string resources — visible in decompiled APK
225
+ - Duplicate string keys — silent override, hard to debug
226
+
227
+ ---
228
+
229
+ ## Related Skills
230
+
231
+ - `manifest` — referencing resources in manifest
232
+ - `material3` — Material 3 theme and color system
233
+ - `rtl` — RTL-specific resource qualifiers
234
+ - `design-system` — design tokens as Android resources
@@ -0,0 +1,217 @@
1
+ ---
2
+ name: savedstatehandle
3
+ description: >
4
+ SavedStateHandle usage in ViewModel for surviving process death and
5
+ configuration changes. Load this skill when persisting ViewModel state
6
+ across process kill, reading navigation arguments, or deciding what
7
+ state needs to be saved vs what can be reloaded.
8
+ ---
9
+
10
+ # SavedStateHandle
11
+
12
+ ## Overview
13
+
14
+ SavedStateHandle is a key-value store provided to ViewModel that survives both configuration changes and process death. Unlike ViewModel which only survives rotation, SavedStateHandle persists through system-initiated process kills and is restored when the user returns to the app.
15
+
16
+ ---
17
+
18
+ ## Core Principles
19
+
20
+ - Use SavedStateHandle for state that must survive **process death** — not just rotation
21
+ - Don't save everything — only save what can't be easily reloaded (user input, scroll position, selected IDs)
22
+ - Data stored in SavedStateHandle must be **Parcelable or primitive**
23
+ - Prefer `saveable { }` in Compose for local UI state — use SavedStateHandle for ViewModel state
24
+ - Navigation arguments are automatically available via SavedStateHandle
25
+
26
+ ---
27
+
28
+ ## What to Save vs What to Reload
29
+
30
+ | State Type | Strategy |
31
+ | ------------------------------------ | -------------------------------------- |
32
+ | User input (text fields, selections) | ✅ SavedStateHandle |
33
+ | Scroll position | ✅ SavedStateHandle or rememberSaveable |
34
+ | Selected item ID | ✅ SavedStateHandle |
35
+ | Loaded list from network/DB | ❌ Reload from repository |
36
+ | Loading/error state | ❌ Derive from data loading |
37
+ | Navigation arguments | ✅ Read from SavedStateHandle |
38
+
39
+ ---
40
+
41
+ ## Basic Usage
42
+
43
+ ```kotlin
44
+ // ✅ Inject via Hilt — automatic with @HiltViewModel
45
+ @HiltViewModel
46
+ class UserDetailViewModel @Inject constructor(
47
+ savedStateHandle: SavedStateHandle,
48
+ private val repository: UserRepository
49
+ ) : ViewModel() {
50
+
51
+ // ✅ Read navigation argument (set by NavController)
52
+ private val userId: String = checkNotNull(savedStateHandle["userId"])
53
+
54
+ // ✅ StateFlow backed by SavedStateHandle — survives process death
55
+ val searchQuery: StateFlow<String> = savedStateHandle
56
+ .getStateFlow("search_query", initialValue = "")
57
+
58
+ fun onSearchQueryChanged(query: String) {
59
+ savedStateHandle["search_query"] = query
60
+ }
61
+ }
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Reading Navigation Arguments
67
+
68
+ ```kotlin
69
+ // ✅ Compose Navigation — argument passed via route
70
+ // NavHost setup
71
+ composable("user/{userId}") { backStackEntry ->
72
+ val viewModel: UserDetailViewModel = hiltViewModel()
73
+ UserDetailScreen(viewModel)
74
+ }
75
+
76
+ // ✅ ViewModel reads it from SavedStateHandle
77
+ @HiltViewModel
78
+ class UserDetailViewModel @Inject constructor(
79
+ savedStateHandle: SavedStateHandle
80
+ ) : ViewModel() {
81
+ val userId: String = checkNotNull(savedStateHandle["userId"]) {
82
+ "userId argument is required"
83
+ }
84
+ }
85
+
86
+ // ✅ Type-safe navigation (Compose Navigation 2.8+)
87
+ @Serializable
88
+ data class UserDetailRoute(val userId: String)
89
+
90
+ @HiltViewModel
91
+ class UserDetailViewModel @Inject constructor(
92
+ savedStateHandle: SavedStateHandle
93
+ ) : ViewModel() {
94
+ private val route = savedStateHandle.toRoute<UserDetailRoute>()
95
+ val userId = route.userId
96
+ }
97
+ ```
98
+
99
+ ---
100
+
101
+ ## StateFlow from SavedStateHandle
102
+
103
+ ```kotlin
104
+ @HiltViewModel
105
+ class SearchViewModel @Inject constructor(
106
+ private val savedStateHandle: SavedStateHandle,
107
+ private val repository: SearchRepository
108
+ ) : ViewModel() {
109
+
110
+ // ✅ Backed by SavedStateHandle — survives process death
111
+ val query: StateFlow<String> = savedStateHandle
112
+ .getStateFlow(KEY_QUERY, initialValue = "")
113
+
114
+ // ✅ Derive results from the saved query
115
+ val results: StateFlow<List<Result>> = query
116
+ .debounce(300)
117
+ .flatMapLatest { q -> repository.search(q) }
118
+ .stateIn(
119
+ scope = viewModelScope,
120
+ started = SharingStarted.WhileSubscribed(5_000),
121
+ initialValue = emptyList()
122
+ )
123
+
124
+ fun onQueryChanged(newQuery: String) {
125
+ savedStateHandle[KEY_QUERY] = newQuery
126
+ }
127
+
128
+ companion object {
129
+ private const val KEY_QUERY = "search_query"
130
+ }
131
+ }
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Saving Complex Types
137
+
138
+ ```kotlin
139
+ // ✅ Parcelable types work directly
140
+ @Parcelize
141
+ data class FilterState(
142
+ val category: String,
143
+ val sortOrder: String,
144
+ val minPrice: Int
145
+ ) : Parcelable
146
+
147
+ @HiltViewModel
148
+ class ProductViewModel @Inject constructor(
149
+ savedStateHandle: SavedStateHandle
150
+ ) : ViewModel() {
151
+
152
+ var filterState: StateFlow<FilterState> = savedStateHandle
153
+ .getStateFlow("filter", FilterState("all", "asc", 0))
154
+
155
+ fun applyFilter(filter: FilterState) {
156
+ savedStateHandle["filter"] = filter
157
+ }
158
+ }
159
+
160
+ // ✅ Primitive types — no Parcelable needed
161
+ savedStateHandle["page"] = 1
162
+ savedStateHandle["isExpanded"] = true
163
+ savedStateHandle["selectedId"] = "user_123"
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Combining SavedStateHandle with Repository
169
+
170
+ ```kotlin
171
+ @HiltViewModel
172
+ class UserListViewModel @Inject constructor(
173
+ savedStateHandle: SavedStateHandle,
174
+ private val repository: UserRepository
175
+ ) : ViewModel() {
176
+
177
+ // Persisted: selected filter
178
+ private val selectedFilter = savedStateHandle
179
+ .getStateFlow("filter", "all")
180
+
181
+ // Not persisted: loaded data — reload from DB
182
+ val users: StateFlow<List<User>> = selectedFilter
183
+ .flatMapLatest { filter ->
184
+ repository.observeUsers(filter)
185
+ }
186
+ .stateIn(
187
+ scope = viewModelScope,
188
+ started = SharingStarted.WhileSubscribed(5_000),
189
+ initialValue = emptyList()
190
+ )
191
+
192
+ fun onFilterSelected(filter: String) {
193
+ savedStateHandle["filter"] = filter
194
+ }
195
+ }
196
+ ```
197
+
198
+ ---
199
+
200
+ ## Anti-Patterns
201
+
202
+ - Saving large objects (bitmaps, full lists) in SavedStateHandle — use DB or files instead
203
+ - Not using SavedStateHandle for user input — it's lost on process death
204
+ - Reading navigation args from `Intent` in Activity when ViewModel is available
205
+ - Using `onSaveInstanceState` in Activity/Fragment when SavedStateHandle covers it
206
+ - Saving non-Parcelable custom types — causes crash at restore time
207
+ - Duplicating state between SavedStateHandle and a separate StateFlow
208
+
209
+ ---
210
+
211
+ ## Related Skills
212
+
213
+ - `process-death-recovery` — full process death survival strategy
214
+ - `state-restoration` — broader state restoration patterns
215
+ - `navigation` — passing arguments via NavController
216
+ - `lifecycle` — when process death occurs
217
+ - `viewmodel` — ViewModel lifecycle and scope