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,247 @@
1
+ ---
2
+ name: file-storage
3
+ description: >
4
+ File storage patterns for Android — saving, reading, and managing files
5
+ across internal storage, external storage, and cache.
6
+ Load this skill when persisting files (images, PDFs, exports, downloads),
7
+ choosing storage location, or managing file lifecycle.
8
+ ---
9
+
10
+ # File Storage
11
+
12
+ ## Overview
13
+
14
+ File storage is used when data doesn't fit neatly into a database or key-value store — images, documents, exports, downloaded content, and binary files. Android provides several storage locations with different visibility, permission, and persistence characteristics.
15
+
16
+ ---
17
+
18
+ ## Core Principles
19
+
20
+ - Choose the **right storage location** for the file's purpose and visibility
21
+ - Always perform file I/O on **Dispatchers.IO** — never main thread
22
+ - Use **FileProvider** to share files with other apps — never expose raw paths
23
+ - Cache files are **not guaranteed** — don't use for critical data
24
+ - Clean up files when they're no longer needed — storage is finite
25
+
26
+ ---
27
+
28
+ ## Storage Location Decision
29
+
30
+ ```
31
+ Is the file private to the app?
32
+ YES → Does the system need to delete it automatically?
33
+ YES → cacheDir (temporary)
34
+ NO → filesDir (permanent internal)
35
+ NO → Is it app-specific but user-visible?
36
+ YES → getExternalFilesDir() (no permission needed)
37
+ NO → MediaStore (shared media) or Downloads
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Writing Files
43
+
44
+ ```kotlin
45
+ // ✅ Internal storage — private, permanent
46
+ suspend fun saveReport(context: Context, fileName: String, content: ByteArray) {
47
+ withContext(Dispatchers.IO) {
48
+ val file = File(context.filesDir, fileName)
49
+ file.writeBytes(content)
50
+ }
51
+ }
52
+
53
+ // ✅ Subdirectory in internal storage
54
+ suspend fun saveInSubdir(context: Context, subdir: String, fileName: String, content: String) {
55
+ withContext(Dispatchers.IO) {
56
+ val dir = File(context.filesDir, subdir).also { it.mkdirs() }
57
+ File(dir, fileName).writeText(content)
58
+ }
59
+ }
60
+
61
+ // ✅ Cache — temporary, system may delete
62
+ suspend fun cacheDownload(context: Context, fileName: String, content: ByteArray) {
63
+ withContext(Dispatchers.IO) {
64
+ File(context.cacheDir, fileName).writeBytes(content)
65
+ }
66
+ }
67
+
68
+ // ✅ External app-specific — user-visible, no permission needed
69
+ suspend fun saveToExternalDocs(context: Context, fileName: String, content: ByteArray) {
70
+ withContext(Dispatchers.IO) {
71
+ val dir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)
72
+ ?: return@withContext
73
+ File(dir, fileName).writeBytes(content)
74
+ }
75
+ }
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Reading Files
81
+
82
+ ```kotlin
83
+ // ✅ Read with existence check
84
+ suspend fun readFile(context: Context, fileName: String): ByteArray? {
85
+ return withContext(Dispatchers.IO) {
86
+ val file = File(context.filesDir, fileName)
87
+ if (file.exists()) file.readBytes() else null
88
+ }
89
+ }
90
+
91
+ // ✅ Read as stream (large files)
92
+ suspend fun readLargeFile(context: Context, fileName: String): Flow<ByteArray> = flow {
93
+ withContext(Dispatchers.IO) {
94
+ val file = File(context.filesDir, fileName)
95
+ file.inputStream().buffered().use { stream ->
96
+ val buffer = ByteArray(8192)
97
+ var bytesRead: Int
98
+ while (stream.read(buffer).also { bytesRead = it } != -1) {
99
+ emit(buffer.copyOf(bytesRead))
100
+ }
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ ---
107
+
108
+ ## File Management
109
+
110
+ ```kotlin
111
+ // ✅ Delete file
112
+ suspend fun deleteFile(context: Context, fileName: String): Boolean {
113
+ return withContext(Dispatchers.IO) {
114
+ File(context.filesDir, fileName).delete()
115
+ }
116
+ }
117
+
118
+ // ✅ List files in directory
119
+ suspend fun listFiles(context: Context, subdir: String): List<String> {
120
+ return withContext(Dispatchers.IO) {
121
+ File(context.filesDir, subdir)
122
+ .listFiles()
123
+ ?.map { it.name }
124
+ ?: emptyList()
125
+ }
126
+ }
127
+
128
+ // ✅ Get file size
129
+ fun getFileSize(context: Context, fileName: String): Long {
130
+ return File(context.filesDir, fileName).length()
131
+ }
132
+
133
+ // ✅ Check if file exists
134
+ fun fileExists(context: Context, fileName: String): Boolean {
135
+ return File(context.filesDir, fileName).exists()
136
+ }
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Saving Bitmaps
142
+
143
+ ```kotlin
144
+ // ✅ Save bitmap to internal storage
145
+ suspend fun saveBitmap(context: Context, bitmap: Bitmap, fileName: String) {
146
+ withContext(Dispatchers.IO) {
147
+ val file = File(context.filesDir, fileName)
148
+ file.outputStream().use { out ->
149
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out)
150
+ }
151
+ }
152
+ }
153
+
154
+ // ✅ Load bitmap
155
+ suspend fun loadBitmap(context: Context, fileName: String): Bitmap? {
156
+ return withContext(Dispatchers.IO) {
157
+ val file = File(context.filesDir, fileName)
158
+ if (file.exists()) BitmapFactory.decodeFile(file.absolutePath) else null
159
+ }
160
+ }
161
+ ```
162
+
163
+ ---
164
+
165
+ ## Sharing Files via FileProvider
166
+
167
+ ```kotlin
168
+ // ✅ Get shareable URI — never expose raw file:// URI
169
+ fun getShareableUri(context: Context, file: File): Uri {
170
+ return FileProvider.getUriForFile(
171
+ context,
172
+ "${context.packageName}.fileprovider",
173
+ file
174
+ )
175
+ }
176
+
177
+ // ✅ Share file
178
+ fun shareFile(context: Context, file: File, mimeType: String) {
179
+ val uri = getShareableUri(context, file)
180
+ val intent = Intent(Intent.ACTION_SEND).apply {
181
+ type = mimeType
182
+ putExtra(Intent.EXTRA_STREAM, uri)
183
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
184
+ }
185
+ context.startActivity(Intent.createChooser(intent, null))
186
+ }
187
+ ```
188
+
189
+ ---
190
+
191
+ ## Cache Management
192
+
193
+ ```kotlin
194
+ // ✅ Calculate cache size
195
+ suspend fun getCacheSizeBytes(context: Context): Long {
196
+ return withContext(Dispatchers.IO) {
197
+ context.cacheDir.walkTopDown().sumOf { it.length() }
198
+ }
199
+ }
200
+
201
+ // ✅ Clear cache
202
+ suspend fun clearCache(context: Context) {
203
+ withContext(Dispatchers.IO) {
204
+ context.cacheDir.deleteRecursively()
205
+ }
206
+ }
207
+
208
+ // ✅ Trim cache to max size (LRU — delete oldest first)
209
+ suspend fun trimCache(context: Context, maxBytes: Long) {
210
+ withContext(Dispatchers.IO) {
211
+ val files = context.cacheDir
212
+ .walkTopDown()
213
+ .filter { it.isFile }
214
+ .sortedBy { it.lastModified() }
215
+ .toMutableList()
216
+
217
+ var totalSize = files.sumOf { it.length() }
218
+ for (file in files) {
219
+ if (totalSize <= maxBytes) break
220
+ totalSize -= file.length()
221
+ file.delete()
222
+ }
223
+ }
224
+ }
225
+ ```
226
+
227
+ ---
228
+
229
+ ## Anti-Patterns
230
+
231
+ - File I/O on the main thread — causes ANR
232
+ - Storing files in external storage without checking availability
233
+ - Using `file://` URI directly with other apps — crashes on API 24+
234
+ - Not cleaning up temp files — accumulates storage over time
235
+ - Storing sensitive data in external storage — accessible to other apps
236
+ - Not checking `file.exists()` before reading — `FileNotFoundException`
237
+ - Recursive directory listing on large directories on main thread
238
+
239
+ ---
240
+
241
+ ## Related Skills
242
+
243
+ - `filesystem` — Android filesystem fundamentals
244
+ - `binary-storage` — storing binary/media data
245
+ - `encrypted-database` — encrypting sensitive files
246
+ - `cache-strategy` — cache invalidation and management
247
+ - `workmanager` — background file operations
@@ -0,0 +1,184 @@
1
+ ---
2
+ name: indexing
3
+ description: >
4
+ SQLite/Room database indexing strategy for Android.
5
+ Load this skill when defining indices on entities, diagnosing slow queries,
6
+ or deciding which columns need indexing for query performance.
7
+ ---
8
+
9
+ # Indexing
10
+
11
+ ## Overview
12
+
13
+ A database index is a data structure that speeds up query lookups at the cost of extra storage and slower writes. Without proper indices, queries perform a full table scan — acceptable for small tables, catastrophic for large ones. In Room, indices are declared on `@Entity`.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Index columns used in **WHERE**, **JOIN ON**, and **ORDER BY** clauses
20
+ - Index foreign key columns — Room/SQLite does not do this automatically
21
+ - **Don't over-index** — every index slows down INSERT/UPDATE/DELETE
22
+ - Unique indices enforce data integrity at the DB level
23
+ - Composite indices cover multi-column WHERE clauses
24
+
25
+ ---
26
+
27
+ ## Declaring Indices in Room
28
+
29
+ ```kotlin
30
+ // ✅ Single column index
31
+ @Entity(
32
+ tableName = "users",
33
+ indices = [
34
+ Index(value = ["email"], unique = true), // unique constraint
35
+ Index(value = ["status"]), // for filtering
36
+ Index(value = ["created_at"]) // for sorting
37
+ ]
38
+ )
39
+ data class UserEntity(
40
+ @PrimaryKey val id: String,
41
+ val email: String,
42
+ val status: String,
43
+ @ColumnInfo(name = "created_at") val createdAt: Long
44
+ )
45
+
46
+ // ✅ Composite index — covers multi-column queries
47
+ @Entity(
48
+ tableName = "orders",
49
+ indices = [
50
+ Index(value = ["user_id", "status"]), // WHERE user_id = ? AND status = ?
51
+ Index(value = ["user_id"]) // WHERE user_id = ? alone also uses this
52
+ ]
53
+ )
54
+ data class OrderEntity(
55
+ @PrimaryKey val id: String,
56
+ @ColumnInfo(name = "user_id") val userId: String,
57
+ val status: String,
58
+ val total: Int
59
+ )
60
+ ```
61
+
62
+ ---
63
+
64
+ ## When to Add an Index
65
+
66
+ ```kotlin
67
+ // ✅ Index needed — WHERE on non-primary-key column
68
+ @Query("SELECT * FROM users WHERE status = :status")
69
+ // → index on status
70
+
71
+ // ✅ Index needed — ORDER BY on non-primary-key column
72
+ @Query("SELECT * FROM users ORDER BY created_at DESC")
73
+ // → index on created_at
74
+
75
+ // ✅ Index needed — JOIN condition
76
+ @Query("SELECT * FROM orders o JOIN users u ON o.user_id = u.id")
77
+ // → index on orders.user_id (foreign key)
78
+
79
+ // ✅ Index needed — LIKE prefix search
80
+ @Query("SELECT * FROM users WHERE full_name LIKE :prefix || '%'")
81
+ // → index on full_name (helps prefix search, not suffix)
82
+
83
+ // ❌ Index NOT needed — primary key is already indexed
84
+ @Query("SELECT * FROM users WHERE id = :id")
85
+ // → no extra index needed
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Index Decision Matrix
91
+
92
+ | Query Pattern | Index Type |
93
+ | ------------------------------ | ------------------------------ |
94
+ | `WHERE col = ?` | Single index on `col` |
95
+ | `WHERE col1 = ? AND col2 = ?` | Composite index `(col1, col2)` |
96
+ | `ORDER BY col` | Single index on `col` |
97
+ | `WHERE col1 = ? ORDER BY col2` | Composite index `(col1, col2)` |
98
+ | `LIKE 'prefix%'` | Single index on `col` |
99
+ | Foreign key column | Single index (always) |
100
+ | Unique constraint | Unique index |
101
+
102
+ ---
103
+
104
+ ## Foreign Key Indices
105
+
106
+ ```kotlin
107
+ // ✅ Always index foreign key columns — SQLite doesn't auto-index them
108
+ @Entity(
109
+ tableName = "orders",
110
+ foreignKeys = [
111
+ ForeignKey(
112
+ entity = UserEntity::class,
113
+ parentColumns = ["id"],
114
+ childColumns = ["user_id"],
115
+ onDelete = ForeignKey.CASCADE
116
+ )
117
+ ],
118
+ indices = [
119
+ Index(value = ["user_id"]) // ✅ required — without this, cascades are slow
120
+ ]
121
+ )
122
+ data class OrderEntity(
123
+ @PrimaryKey val id: String,
124
+ @ColumnInfo(name = "user_id") val userId: String
125
+ )
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Diagnosing Missing Indices
131
+
132
+ ```kotlin
133
+ // ✅ Enable Room query logging in debug
134
+ Room.databaseBuilder(context, AppDatabase::class.java, "app_database")
135
+ .setQueryCallback(
136
+ queryCallback = { sqlQuery, bindArgs ->
137
+ Log.d("RoomQuery", "Query: $sqlQuery Args: $bindArgs")
138
+ },
139
+ executor = Executors.newSingleThreadExecutor()
140
+ )
141
+ .build()
142
+
143
+ // ✅ Use EXPLAIN QUERY PLAN to check index usage
144
+ // Run in a test or via ADB:
145
+ // EXPLAIN QUERY PLAN SELECT * FROM users WHERE status = 'active'
146
+ // Look for "SCAN TABLE" (no index) vs "SEARCH TABLE USING INDEX" (indexed)
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Composite Index Column Order
152
+
153
+ ```kotlin
154
+ // ✅ Put the most selective / most frequently filtered column FIRST
155
+ Index(value = ["user_id", "status"])
156
+ // Best for: WHERE user_id = ? AND status = ?
157
+ // Also good for: WHERE user_id = ?
158
+ // NOT useful for: WHERE status = ? alone
159
+
160
+ // ✅ For range queries, range column goes LAST
161
+ Index(value = ["user_id", "created_at"])
162
+ // Good for: WHERE user_id = ? AND created_at > ?
163
+ // Not for: WHERE created_at > ? alone
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Anti-Patterns
169
+
170
+ - No index on foreign key columns — DELETE/CASCADE operations become full table scans
171
+ - Index on every column — slows writes, wastes storage, rarely helps queries
172
+ - Composite index with wrong column order — first column must be used in WHERE
173
+ - Index on low-cardinality columns alone (e.g., boolean) — rarely helps (only 2 values)
174
+ - Missing index on ORDER BY column in large tables — full sort every query
175
+ - Unique index as a substitute for proper validation — validate in app layer too
176
+
177
+ ---
178
+
179
+ ## Related Skills
180
+
181
+ - `room` — Room entity and database setup
182
+ - `dao` — queries that benefit from indices
183
+ - `sqlite` — raw SQLite index management
184
+ - `database-versioning-strategy` — adding indices via migration
@@ -0,0 +1,185 @@
1
+ ---
2
+ name: key-value-store-strategy
3
+ description: >
4
+ Choosing the right key-value storage for Android — DataStore, SharedPreferences,
5
+ EncryptedSharedPreferences, or in-memory. Load this skill when deciding where
6
+ to store simple values, flags, tokens, or settings.
7
+ ---
8
+
9
+ # Key-Value Store Strategy
10
+
11
+ ## Overview
12
+
13
+ Android provides several key-value storage options. Choosing the wrong one leads to security issues, data loss, or unnecessary complexity. This skill defines which store to use for each type of data.
14
+
15
+ ---
16
+
17
+ ## Decision Matrix
18
+
19
+ | Data Type | Storage | Reason |
20
+ | ------------------------------ | ------------------------------- | ---------------------------------- |
21
+ | User settings, preferences | DataStore (Preferences) | Async, type-safe, coroutine-native |
22
+ | Structured settings object | DataStore (Proto) | Schema-safe, evolves cleanly |
23
+ | Auth token, sensitive value | EncryptedSharedPreferences | Encrypted at rest |
24
+ | Session flags (in-memory only) | In-memory (ViewModel/singleton) | No persistence needed |
25
+ | Feature flags from server | DataStore + remote config | Persisted, refreshable |
26
+ | Legacy migration source | SharedPreferences → DataStore | Migrate away from SP |
27
+
28
+ ---
29
+
30
+ ## DataStore (Preferences) — Default Choice
31
+
32
+ ```kotlin
33
+ // ✅ Use for: app settings, UI preferences, onboarding state, feature flags
34
+ val IS_ONBOARDING_DONE = booleanPreferencesKey("onboarding_done")
35
+ val SELECTED_LANGUAGE = stringPreferencesKey("language")
36
+ val LAST_SYNC_TIME = longPreferencesKey("last_sync")
37
+
38
+ // Read
39
+ val isDone: Flow<Boolean> = dataStore.data.map { it[IS_ONBOARDING_DONE] ?: false }
40
+
41
+ // Write
42
+ suspend fun markOnboardingDone() {
43
+ dataStore.edit { it[IS_ONBOARDING_DONE] = true }
44
+ }
45
+ ```
46
+
47
+ ---
48
+
49
+ ## EncryptedSharedPreferences — Sensitive Data
50
+
51
+ ```kotlin
52
+ // ✅ Use for: auth tokens, refresh tokens, PII, secrets
53
+ // Never store tokens in plain DataStore or SharedPreferences
54
+
55
+ dependencies {
56
+ implementation(libs.security.crypto)
57
+ }
58
+
59
+ fun createEncryptedPrefs(context: Context): SharedPreferences {
60
+ val masterKey = MasterKey.Builder(context)
61
+ .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
62
+ .build()
63
+
64
+ return EncryptedSharedPreferences.create(
65
+ context,
66
+ "secure_prefs",
67
+ masterKey,
68
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
69
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
70
+ )
71
+ }
72
+
73
+ // ✅ Provide via Hilt
74
+ @Provides
75
+ @Singleton
76
+ fun provideEncryptedPrefs(@ApplicationContext context: Context): SharedPreferences =
77
+ createEncryptedPrefs(context)
78
+
79
+ // ✅ Repository wrapping encrypted prefs
80
+ class TokenRepository @Inject constructor(
81
+ private val encryptedPrefs: SharedPreferences
82
+ ) {
83
+ companion object {
84
+ private const val KEY_ACCESS_TOKEN = "access_token"
85
+ private const val KEY_REFRESH_TOKEN = "refresh_token"
86
+ }
87
+
88
+ fun getAccessToken(): String? = encryptedPrefs.getString(KEY_ACCESS_TOKEN, null)
89
+ fun getRefreshToken(): String? = encryptedPrefs.getString(KEY_REFRESH_TOKEN, null)
90
+
91
+ fun saveTokens(accessToken: String, refreshToken: String) {
92
+ encryptedPrefs.edit()
93
+ .putString(KEY_ACCESS_TOKEN, accessToken)
94
+ .putString(KEY_REFRESH_TOKEN, refreshToken)
95
+ .apply()
96
+ }
97
+
98
+ fun clearTokens() {
99
+ encryptedPrefs.edit()
100
+ .remove(KEY_ACCESS_TOKEN)
101
+ .remove(KEY_REFRESH_TOKEN)
102
+ .apply()
103
+ }
104
+ }
105
+ ```
106
+
107
+ ---
108
+
109
+ ## In-Memory Store — Session-Only Data
110
+
111
+ ```kotlin
112
+ // ✅ Use for: data that only lives for the current session
113
+ // Examples: current user object after login, selected items, temp state
114
+
115
+ // ✅ In ViewModel — scoped to screen
116
+ @HiltViewModel
117
+ class CheckoutViewModel @Inject constructor() : ViewModel() {
118
+ private val _selectedItems = MutableStateFlow<List<Item>>(emptyList())
119
+ val selectedItems = _selectedItems.asStateFlow()
120
+ }
121
+
122
+ // ✅ In singleton — scoped to app session
123
+ @Singleton
124
+ class SessionStore @Inject constructor() {
125
+ private var _currentUser: User? = null
126
+ val currentUser: User? get() = _currentUser
127
+
128
+ fun setUser(user: User) { _currentUser = user }
129
+ fun clearUser() { _currentUser = null }
130
+ }
131
+ ```
132
+
133
+ ---
134
+
135
+ ## SharedPreferences — Legacy Only
136
+
137
+ ```kotlin
138
+ // ❌ Don't use in new code — use DataStore instead
139
+ // ✅ Only acceptable when:
140
+ // - Migrating existing code incrementally
141
+ // - A third-party library requires SharedPreferences
142
+
143
+ // ✅ If you must use it, apply() not commit()
144
+ prefs.edit().putString("key", "value").apply() // async
145
+ // NOT:
146
+ prefs.edit().putString("key", "value").commit() // blocks main thread
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Migration: SharedPreferences → DataStore
152
+
153
+ ```kotlin
154
+ // ✅ Migrate automatically on first DataStore access
155
+ val dataStore = context.createDataStore(
156
+ name = "app_prefs",
157
+ migrations = listOf(
158
+ SharedPreferencesMigration(
159
+ context = context,
160
+ sharedPreferencesName = "legacy_prefs",
161
+ keysToMigrate = setOf("language", "is_dark_mode") // migrate only needed keys
162
+ )
163
+ )
164
+ )
165
+ ```
166
+
167
+ ---
168
+
169
+ ## Anti-Patterns
170
+
171
+ - Storing auth tokens in plain DataStore or SharedPreferences — readable without encryption
172
+ - Using SharedPreferences in new code — use DataStore instead
173
+ - Storing large objects as JSON strings in key-value store — use Room
174
+ - Multiple instances of DataStore for the same file — data corruption
175
+ - Using `commit()` on SharedPreferences — blocks main thread
176
+ - Storing user PII (name, email) in plain storage — security and compliance risk
177
+
178
+ ---
179
+
180
+ ## Related Skills
181
+
182
+ - `datastore` — Preferences DataStore implementation
183
+ - `proto-datastore` — typed object storage
184
+ - `encrypted-storage` — Android Keystore and EncryptedSharedPreferences
185
+ - `cache-strategy` — caching with key-value stores