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,264 @@
1
+ ---
2
+ name: datastore
3
+ description: >
4
+ Jetpack DataStore (Preferences) setup and usage for Android.
5
+ Load this skill when storing simple key-value settings, user preferences,
6
+ feature flags, or any small structured data that doesn't need a full database.
7
+ ---
8
+
9
+ # DataStore (Preferences)
10
+
11
+ ## Overview
12
+ Jetpack DataStore is the modern replacement for SharedPreferences. It stores key-value pairs (Preferences DataStore) or typed objects (Proto DataStore) asynchronously using coroutines and Flow. It is safe to call from the main thread and handles concurrent access correctly.
13
+
14
+ ---
15
+
16
+ ## Core Principles
17
+
18
+ - Use DataStore for **small, simple data** — user settings, session state, feature flags
19
+ - Use Room for **structured, queryable data** — DataStore is not a database
20
+ - **Never block** to read DataStore — always collect as Flow
21
+ - Create **one DataStore instance** per file — never multiple instances for the same file
22
+ - Inject DataStore via **Hilt** — never create it in a ViewModel or Repository directly
23
+
24
+ ---
25
+
26
+ ## Setup
27
+
28
+ ```toml
29
+ # libs.versions.toml
30
+ [versions]
31
+ datastore = "1.1.1"
32
+
33
+ [libraries]
34
+ datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" }
35
+ ```
36
+
37
+ ```kotlin
38
+ // build.gradle.kts
39
+ dependencies {
40
+ implementation(libs.datastore.preferences)
41
+ }
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Creating DataStore
47
+
48
+ ```kotlin
49
+ // ✅ Create via extension — one instance per file
50
+ val Context.userPreferencesDataStore: DataStore<Preferences> by preferencesDataStore(
51
+ name = "user_preferences"
52
+ )
53
+
54
+ // ✅ Provide via Hilt — singleton
55
+ @Module
56
+ @InstallIn(SingletonComponent::class)
57
+ object DataStoreModule {
58
+
59
+ @Provides
60
+ @Singleton
61
+ fun provideUserPreferencesDataStore(
62
+ @ApplicationContext context: Context
63
+ ): DataStore<Preferences> = context.userPreferencesDataStore
64
+ }
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Defining Keys
70
+
71
+ ```kotlin
72
+ // ✅ Define all keys in a companion object or object
73
+ object UserPreferencesKeys {
74
+ val IS_DARK_MODE = booleanPreferencesKey("is_dark_mode")
75
+ val LANGUAGE = stringPreferencesKey("language")
76
+ val FONT_SIZE = intPreferencesKey("font_size")
77
+ val LAST_SYNC = longPreferencesKey("last_sync_timestamp")
78
+ val ONBOARDING_COMPLETE = booleanPreferencesKey("onboarding_complete")
79
+ }
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Repository Pattern
85
+
86
+ ```kotlin
87
+ // ✅ Wrap DataStore in a Repository — don't access it directly from ViewModel
88
+ class UserPreferencesRepository @Inject constructor(
89
+ private val dataStore: DataStore<Preferences>
90
+ ) {
91
+
92
+ // ✅ Read — expose as Flow
93
+ val isDarkMode: Flow<Boolean> = dataStore.data
94
+ .catch { e ->
95
+ if (e is IOException) emit(emptyPreferences())
96
+ else throw e
97
+ }
98
+ .map { prefs -> prefs[UserPreferencesKeys.IS_DARK_MODE] ?: false }
99
+
100
+ val language: Flow<String> = dataStore.data
101
+ .catch { e ->
102
+ if (e is IOException) emit(emptyPreferences())
103
+ else throw e
104
+ }
105
+ .map { prefs -> prefs[UserPreferencesKeys.LANGUAGE] ?: "en" }
106
+
107
+ // ✅ Read all at once — single object
108
+ val userPreferences: Flow<UserPreferences> = dataStore.data
109
+ .catch { e ->
110
+ if (e is IOException) emit(emptyPreferences())
111
+ else throw e
112
+ }
113
+ .map { prefs ->
114
+ UserPreferences(
115
+ isDarkMode = prefs[UserPreferencesKeys.IS_DARK_MODE] ?: false,
116
+ language = prefs[UserPreferencesKeys.LANGUAGE] ?: "en",
117
+ fontSize = prefs[UserPreferencesKeys.FONT_SIZE] ?: 14
118
+ )
119
+ }
120
+
121
+ // ✅ Write — suspend function
122
+ suspend fun setDarkMode(enabled: Boolean) {
123
+ dataStore.edit { prefs ->
124
+ prefs[UserPreferencesKeys.IS_DARK_MODE] = enabled
125
+ }
126
+ }
127
+
128
+ suspend fun setLanguage(language: String) {
129
+ dataStore.edit { prefs ->
130
+ prefs[UserPreferencesKeys.LANGUAGE] = language
131
+ }
132
+ }
133
+
134
+ // ✅ Clear all preferences
135
+ suspend fun clearAll() {
136
+ dataStore.edit { it.clear() }
137
+ }
138
+ }
139
+
140
+ // ✅ Domain model for preferences
141
+ data class UserPreferences(
142
+ val isDarkMode: Boolean,
143
+ val language: String,
144
+ val fontSize: Int
145
+ )
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Usage in ViewModel
151
+
152
+ ```kotlin
153
+ @HiltViewModel
154
+ class SettingsViewModel @Inject constructor(
155
+ private val preferencesRepository: UserPreferencesRepository
156
+ ) : ViewModel() {
157
+
158
+ val preferences: StateFlow<UserPreferences> = preferencesRepository.userPreferences
159
+ .stateIn(
160
+ scope = viewModelScope,
161
+ started = SharingStarted.WhileSubscribed(5_000),
162
+ initialValue = UserPreferences(isDarkMode = false, language = "en", fontSize = 14)
163
+ )
164
+
165
+ fun onDarkModeToggled(enabled: Boolean) {
166
+ viewModelScope.launch {
167
+ preferencesRepository.setDarkMode(enabled)
168
+ }
169
+ }
170
+
171
+ fun onLanguageSelected(language: String) {
172
+ viewModelScope.launch {
173
+ preferencesRepository.setLanguage(language)
174
+ }
175
+ }
176
+ }
177
+ ```
178
+
179
+ ---
180
+
181
+ ## Atomic Updates
182
+
183
+ ```kotlin
184
+ // ✅ update multiple keys atomically in one edit block
185
+ suspend fun saveUserSession(userId: String, token: String, expiresAt: Long) {
186
+ dataStore.edit { prefs ->
187
+ prefs[USER_ID_KEY] = userId
188
+ prefs[TOKEN_KEY] = token
189
+ prefs[EXPIRES_AT_KEY] = expiresAt
190
+ }
191
+ }
192
+
193
+ // ✅ Conditional update
194
+ suspend fun incrementLaunchCount() {
195
+ dataStore.edit { prefs ->
196
+ val current = prefs[LAUNCH_COUNT_KEY] ?: 0
197
+ prefs[LAUNCH_COUNT_KEY] = current + 1
198
+ }
199
+ }
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Error Handling
205
+
206
+ ```kotlin
207
+ // ✅ Always handle IOException — disk read/write can fail
208
+ val preferences: Flow<AppPreferences> = dataStore.data
209
+ .catch { exception ->
210
+ if (exception is IOException) {
211
+ // Emit defaults on read error
212
+ emit(emptyPreferences())
213
+ } else {
214
+ throw exception // re-throw unexpected errors
215
+ }
216
+ }
217
+ .map { prefs -> prefs.toAppPreferences() }
218
+ ```
219
+
220
+ ---
221
+
222
+ ## DataStore vs SharedPreferences
223
+
224
+ | | DataStore | SharedPreferences |
225
+ |--|-----------|------------------|
226
+ | Thread safety | ✅ Coroutines | ❌ Partial |
227
+ | Main thread safe | ✅ Yes | ❌ No (apply is async, commit blocks) |
228
+ | Strongly typed | ✅ Yes | ❌ No |
229
+ | Error handling | ✅ Flow catch | ❌ Silent |
230
+ | Transactions | ✅ edit {} | ❌ No |
231
+ | Migration | ✅ SharedPreferencesMigration | — |
232
+
233
+ ---
234
+
235
+ ## Migrating from SharedPreferences
236
+
237
+ ```kotlin
238
+ // ✅ Migrate existing SharedPreferences data
239
+ val dataStore = context.createDataStore(
240
+ name = "user_preferences",
241
+ migrations = listOf(
242
+ SharedPreferencesMigration(context, "legacy_prefs")
243
+ )
244
+ )
245
+ ```
246
+
247
+ ---
248
+
249
+ ## Anti-Patterns
250
+
251
+ - Multiple DataStore instances for same file — data corruption risk
252
+ - Calling `dataStore.data.first()` in a loop — use Flow collection instead
253
+ - Storing large objects or lists in DataStore — use Room instead
254
+ - Not catching `IOException` — unhandled disk errors crash the app
255
+ - Accessing DataStore directly in ViewModel — wrap in Repository
256
+ - Using DataStore for data that needs querying/filtering — use Room
257
+
258
+ ---
259
+
260
+ ## Related Skills
261
+ - `proto-datastore` — typed object storage with Protobuf
262
+ - `repository-pattern` — wrapping DataStore in a Repository
263
+ - `hilt` — providing DataStore as a singleton
264
+ - `key-value-store-strategy` — choosing the right storage for the use case
@@ -0,0 +1,215 @@
1
+ ---
2
+ name: database-versioning-strategy
3
+ description: >
4
+ Strategy for managing Room database version increments across releases.
5
+ Load this skill when planning schema changes, deciding when to increment
6
+ version, managing version history, or coordinating schema changes across
7
+ multiple developers/branches.
8
+ ---
9
+
10
+ # Database Versioning Strategy
11
+
12
+ ## Overview
13
+ Database versioning is the discipline of managing schema changes in a controlled, predictable way. Every schema change must be planned, versioned, documented, and tested before release. A broken migration path crashes the app on existing installs.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - **One version increment per release** — batch all schema changes for a release into one migration
20
+ - **Never reuse a version number** — once a version is released, it's permanent
21
+ - **Never modify a released migration** — write a new one instead
22
+ - Export and commit the schema file — it's the source of truth for migration correctness
23
+ - Plan schema changes before implementation — migrations are hard to undo
24
+
25
+ ---
26
+
27
+ ## Version Numbering
28
+
29
+ ```kotlin
30
+ // ✅ Increment by 1 for each release with schema changes
31
+ @Database(version = 5, ...) // was 4 in previous release
32
+
33
+ // ✅ Never skip version numbers
34
+ // Bad: 1 → 3 (skipped 2) — users on v2 have no migration path to v3
35
+ // Good: 1 → 2 → 3
36
+
37
+ // ✅ During development — batch changes
38
+ // Don't increment version for every local change during a sprint
39
+ // Increment once when the feature/release is ready
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Schema Export and Version Control
45
+
46
+ ```kotlin
47
+ // ✅ Always export schema
48
+ @Database(
49
+ entities = [...],
50
+ version = 5,
51
+ exportSchema = true // generates schemas/com.example.AppDatabase/5.json
52
+ )
53
+
54
+ // build.gradle.kts
55
+ ksp {
56
+ arg("room.schemaLocation", "$projectDir/schemas")
57
+ }
58
+ ```
59
+
60
+ ```
61
+ // ✅ Commit schema files to version control
62
+ schemas/
63
+ com.example.AppDatabase/
64
+ 1.json ← version 1 schema
65
+ 2.json ← version 2 schema
66
+ 3.json ← version 3 schema
67
+ 4.json ← version 4 schema
68
+ 5.json ← version 5 schema (current)
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Change Planning Checklist
74
+
75
+ Before making any schema change:
76
+
77
+ ```
78
+ ☐ What tables/columns are affected?
79
+ ☐ Is existing data preserved? How?
80
+ ☐ Is the migration reversible? (Usually not — plan carefully)
81
+ ☐ Are there indices that need updating?
82
+ ☐ Are there related DAOs that need query updates?
83
+ ☐ Are there DTOs/mappers that need updating?
84
+ ☐ Is a data migration needed (not just schema migration)?
85
+ ☐ Is this the only schema change for this release?
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Multi-Developer Coordination
91
+
92
+ ```
93
+ // ✅ Strategy when multiple developers change schema simultaneously
94
+
95
+ // Developer A: adds column to users table
96
+ // Developer B: adds new orders table
97
+
98
+ // On merge:
99
+ // 1. Agree on a single version number for the combined change
100
+ // 2. Write ONE migration that includes BOTH changes
101
+ // 3. Never have two separate migrations with the same version increment
102
+
103
+ // ✅ Use a migration registry file to coordinate
104
+ // migrations/MIGRATION_LOG.md
105
+ // Version 5 (Release 2.3.0):
106
+ // - Added phone_number to users (Developer A)
107
+ // - Added orders table (Developer B)
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Data Migration vs Schema Migration
113
+
114
+ ```kotlin
115
+ // Schema migration — changes structure
116
+ val MIGRATION_4_5 = object : Migration(4, 5) {
117
+ override fun migrate(database: SupportSQLiteDatabase) {
118
+ database.execSQL("ALTER TABLE users ADD COLUMN tier TEXT NOT NULL DEFAULT 'free'")
119
+ }
120
+ }
121
+
122
+ // ✅ Data migration — backfill or transform data after schema change
123
+ // Do NOT do complex data transformations in Migration.migrate()
124
+ // Use a one-time WorkManager job instead
125
+
126
+ class BackfillUserTierWorker(
127
+ context: Context,
128
+ params: WorkerParameters
129
+ ) : CoroutineWorker(context, params) {
130
+
131
+ override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
132
+ try {
133
+ val users = userDao.getAllOnce()
134
+ users.forEach { user ->
135
+ val tier = computeTierFromHistory(user)
136
+ userDao.updateTier(user.id, tier)
137
+ }
138
+ Result.success()
139
+ } catch (e: Exception) {
140
+ Result.retry()
141
+ }
142
+ }
143
+ }
144
+
145
+ // Schedule after migration is complete
146
+ fun scheduleBackfillIfNeeded(prefs: SharedPreferences) {
147
+ if (!prefs.getBoolean("backfill_tier_done", false)) {
148
+ WorkManager.getInstance(context).enqueueUniqueWork(
149
+ "backfill_user_tier",
150
+ ExistingWorkPolicy.KEEP,
151
+ OneTimeWorkRequestBuilder<BackfillUserTierWorker>().build()
152
+ )
153
+ }
154
+ }
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Emergency: Breaking Schema in Development
160
+
161
+ ```kotlin
162
+ // ✅ During development only — if migration is too complex and data doesn't matter
163
+ Room.databaseBuilder(context, AppDatabase::class.java, "app_database")
164
+ .fallbackToDestructiveMigration() // ❌ NEVER in production release
165
+ .build()
166
+
167
+ // ✅ Better approach in development — clear app data manually
168
+ // adb shell pm clear com.example.app
169
+
170
+ // ✅ Or use a different DB name per build type
171
+ val dbName = if (BuildConfig.DEBUG) "app_database_debug" else "app_database"
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Version History Documentation
177
+
178
+ ```markdown
179
+ <!-- schemas/CHANGELOG.md — maintain alongside schema files -->
180
+
181
+ ## Version 5 (Release 2.3.0 — 2024-11-15)
182
+ - Added `phone_number TEXT` to `users` table
183
+ - Added `orders` table with foreign key to `users`
184
+ - Added index on `orders.user_id`
185
+
186
+ ## Version 4 (Release 2.2.0 — 2024-09-01)
187
+ - Added `status TEXT NOT NULL DEFAULT 'active'` to `users`
188
+
189
+ ## Version 3 (Release 2.0.0 — 2024-06-10)
190
+ - Renamed column `name` → `full_name` in `users`
191
+
192
+ ## Version 2 (Release 1.5.0 — 2024-03-20)
193
+ - Added `created_at INTEGER` to `users`
194
+
195
+ ## Version 1 (Initial release — 2024-01-10)
196
+ - Initial schema: `users` table
197
+ ```
198
+
199
+ ---
200
+
201
+ ## Anti-Patterns
202
+
203
+ - Incrementing version during development for every small change — creates unnecessary migrations
204
+ - Not committing schema JSON files — migration tests fail, history is lost
205
+ - Writing migration without updating the version in `@Database` — causes crash
206
+ - Two migrations with overlapping version ranges — Room can't resolve path
207
+ - Complex business logic inside `Migration.migrate()` — use WorkManager instead
208
+ - Releasing without testing migration from the previous production version
209
+
210
+ ---
211
+
212
+ ## Related Skills
213
+ - `migration` — writing individual migration objects
214
+ - `room` — Room database configuration
215
+ - `workmanager` — data migration jobs post-schema-change
@@ -0,0 +1,212 @@
1
+ ---
2
+ name: encrypted-database
3
+ description: >
4
+ Encrypted SQLite database using SQLCipher and Room for Android.
5
+ Load this skill when storing sensitive user data that requires encryption
6
+ at rest, implementing encrypted Room database, or managing encryption keys.
7
+ ---
8
+
9
+ # Encrypted Database
10
+
11
+ ## Overview
12
+
13
+ SQLCipher provides transparent AES-256 encryption for SQLite databases. When integrated with Room, the entire database file is encrypted at rest. This is required for apps handling sensitive data — health records, financial data, private messages, or enterprise security requirements.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Use SQLCipher when the database contains **sensitive user data**
20
+ - Encryption key must be stored in **Android Keystore** — never hardcoded
21
+ - Key derivation from user password uses PBKDF2 — never use raw password as key
22
+ - Encrypted database has a **performance overhead** — benchmark before requiring it
23
+ - Test encryption on real devices — emulators may behave differently
24
+
25
+ ---
26
+
27
+ ## Setup
28
+
29
+ ```toml
30
+ [versions]
31
+ sqlcipher = "4.5.4"
32
+ security-crypto = "1.1.0-alpha06"
33
+
34
+ [libraries]
35
+ sqlcipher-android = { module = "net.zetetic:sqlcipher-android", version.ref = "sqlcipher" }
36
+ sqlite-ktx = { module = "androidx.sqlite:sqlite-ktx", version = "2.4.0" }
37
+ security-crypto = { module = "androidx.security:security-crypto", version.ref = "security-crypto" }
38
+ ```
39
+
40
+ ```kotlin
41
+ dependencies {
42
+ implementation(libs.sqlcipher.android)
43
+ implementation(libs.sqlite.ktx)
44
+ implementation(libs.security.crypto)
45
+ }
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Key Management with Android Keystore
51
+
52
+ ```kotlin
53
+ // ✅ Generate or retrieve encryption key from Keystore
54
+ object DatabaseKeyManager {
55
+
56
+ private const val KEY_ALIAS = "db_encryption_key"
57
+ private const val PREFS_NAME = "db_key_prefs"
58
+ private const val PREFS_KEY = "encrypted_db_passphrase"
59
+
60
+ fun getOrCreateKey(context: Context): ByteArray {
61
+ val masterKey = MasterKey.Builder(context)
62
+ .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
63
+ .build()
64
+
65
+ val prefs = EncryptedSharedPreferences.create(
66
+ context,
67
+ PREFS_NAME,
68
+ masterKey,
69
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
70
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
71
+ )
72
+
73
+ val existing = prefs.getString(PREFS_KEY, null)
74
+ if (existing != null) {
75
+ return Base64.decode(existing, Base64.NO_WRAP)
76
+ }
77
+
78
+ // Generate new random key
79
+ val newKey = ByteArray(32).also { SecureRandom().nextBytes(it) }
80
+ prefs.edit()
81
+ .putString(PREFS_KEY, Base64.encodeToString(newKey, Base64.NO_WRAP))
82
+ .apply()
83
+ return newKey
84
+ }
85
+ }
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Room with SQLCipher
91
+
92
+ ```kotlin
93
+ // ✅ Custom SupportSQLiteOpenHelper.Factory using SQLCipher
94
+ @Module
95
+ @InstallIn(SingletonComponent::class)
96
+ object DatabaseModule {
97
+
98
+ @Provides
99
+ @Singleton
100
+ fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
101
+ val passphrase = DatabaseKeyManager.getOrCreateKey(context)
102
+ val factory = SupportFactory(passphrase)
103
+
104
+ return Room.databaseBuilder(
105
+ context,
106
+ AppDatabase::class.java,
107
+ "encrypted_app_database"
108
+ )
109
+ .openHelperFactory(factory)
110
+ .addMigrations(MIGRATION_1_2)
111
+ .build()
112
+ }
113
+ }
114
+ ```
115
+
116
+ ---
117
+
118
+ ## User-Password-Derived Key
119
+
120
+ ```kotlin
121
+ // ✅ For apps where encryption key is derived from user's password
122
+ object PasswordKeyDerivation {
123
+
124
+ fun deriveKey(password: CharArray, salt: ByteArray): ByteArray {
125
+ val spec = PBEKeySpec(
126
+ password,
127
+ salt,
128
+ 100_000, // iterations
129
+ 256 // key length in bits
130
+ )
131
+ val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
132
+ return factory.generateSecret(spec).encoded
133
+ }
134
+
135
+ fun generateSalt(): ByteArray {
136
+ return ByteArray(32).also { SecureRandom().nextBytes(it) }
137
+ }
138
+ }
139
+
140
+ // Store the salt (not the key) in EncryptedSharedPreferences
141
+ // Re-derive the key each session from user's password
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Verifying Encryption
147
+
148
+ ```kotlin
149
+ // ✅ Verify database is encrypted (for debug/test)
150
+ fun isDatabaseEncrypted(context: Context, dbName: String): Boolean {
151
+ val dbFile = context.getDatabasePath(dbName)
152
+ if (!dbFile.exists()) return false
153
+
154
+ return try {
155
+ val bytes = dbFile.readBytes().take(16).toByteArray()
156
+ val header = String(bytes)
157
+ !header.startsWith("SQLite format 3") // encrypted files don't start with this
158
+ } catch (e: Exception) {
159
+ false
160
+ }
161
+ }
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Migration with Encrypted Database
167
+
168
+ ```kotlin
169
+ // ✅ Migrations work the same way — SQLCipher handles the encryption transparently
170
+ val MIGRATION_1_2 = object : Migration(1, 2) {
171
+ override fun migrate(database: SupportSQLiteDatabase) {
172
+ database.execSQL("ALTER TABLE users ADD COLUMN bio TEXT")
173
+ }
174
+ }
175
+
176
+ // No special handling needed — Room + SQLCipher = transparent
177
+ ```
178
+
179
+ ---
180
+
181
+ ## Performance Considerations
182
+
183
+ ```kotlin
184
+ // ✅ SQLCipher overhead — benchmark before requiring encryption
185
+ // Typical overhead: 5-15% slower reads, 5-20% slower writes
186
+ // Acceptable for most use cases — unacceptable for high-frequency sensors/logs
187
+
188
+ // ✅ If encryption overhead is a concern:
189
+ // - Encrypt only the sensitive tables using separate database files
190
+ // - Use per-row encryption for specific columns instead of full-DB encryption
191
+ // - Profile with Macrobenchmark before deciding
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Anti-Patterns
197
+
198
+ - Hardcoding the encryption passphrase in code — visible in decompiled APK
199
+ - Using the user's password directly as the DB key — weak, changes break DB
200
+ - Not storing the salt — can't re-derive key from password
201
+ - Not using Android Keystore to protect the DB key — key accessible on rooted devices
202
+ - Encrypting non-sensitive data — unnecessary performance overhead
203
+ - Sharing encrypted DB across users on the same device — key management nightmare
204
+
205
+ ---
206
+
207
+ ## Related Skills
208
+
209
+ - `room` — Room database setup
210
+ - `keystore` — Android Keystore for key storage
211
+ - `encrypted-storage` — EncryptedSharedPreferences for key storage
212
+ - `security` — general Android security patterns