android-sdd 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +143 -0
- package/package.json +27 -0
- package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
- package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
- package/skills/Android Platform/Configuration/SKILL.md +201 -0
- package/skills/Android Platform/Filesystem/SKILL.md +216 -0
- package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
- package/skills/Android Platform/Manifest/SKILL.md +226 -0
- package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
- package/skills/Android Platform/Resources/SKILL.md +234 -0
- package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
- package/skills/Android Platform/State Restoration/SKILL.md +210 -0
- package/skills/Architecture/Bounded Context/SKILL.md +207 -0
- package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
- package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
- package/skills/Architecture/Entity Design/SKILL.md +243 -0
- package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
- package/skills/Architecture/MVI/SKILL.md +224 -0
- package/skills/Architecture/MVVM/SKILL.md +198 -0
- package/skills/Architecture/Modularization/SKILL.md +194 -0
- package/skills/Architecture/Offline First/SKILL.md +249 -0
- package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
- package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
- package/skills/Architecture/State Management/SKILL.md +229 -0
- package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
- package/skills/Architecture/Use Case Design/SKILL.md +244 -0
- package/skills/Architecture/Value Object/SKILL.md +226 -0
- package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
- package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
- package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
- package/skills/Build System/Build Cache/SKILL.md +233 -0
- package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
- package/skills/Build System/Build Variant/SKILL.md +215 -0
- package/skills/Build System/Convention Plugin/SKILL.md +288 -0
- package/skills/Build System/Dependency Management/SKILL.md +261 -0
- package/skills/Build System/Gradle/SKILL.md +284 -0
- package/skills/Build System/Incremental Build/SKILL.md +199 -0
- package/skills/Build System/KAPT/SKILL.md +198 -0
- package/skills/Build System/KSP/SKILL.md +263 -0
- package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
- package/skills/Build System/Specialized/C++/SKILL.md +308 -0
- package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
- package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
- package/skills/Build System/Version Catalog/SKILL.md +304 -0
- package/skills/Concurrency/Background Processing/SKILL.md +185 -0
- package/skills/Concurrency/Channel/SKILL.md +207 -0
- package/skills/Concurrency/Coroutine/SKILL.md +200 -0
- package/skills/Concurrency/Flow/SKILL.md +179 -0
- package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
- package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
- package/skills/Concurrency/StateFlow/SKILL.md +175 -0
- package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
- package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
- package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
- package/skills/Core Language/DSL/SKILL.md +186 -0
- package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
- package/skills/Core Language/Immutability/SKILL.md +156 -0
- package/skills/Core Language/KMP/SKILL.md +182 -0
- package/skills/Core Language/Kotlin/SKILL.md +187 -0
- package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
- package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
- package/skills/Core Language/Serialization/SKILL.md +191 -0
- package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
- package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
- package/skills/Data Layer/DAO/SKILL.md +225 -0
- package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
- package/skills/Data Layer/DataStore/SKILL.md +264 -0
- package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
- package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
- package/skills/Data Layer/File Storage/SKILL.md +247 -0
- package/skills/Data Layer/Indexing/SKILL.md +184 -0
- package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
- package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
- package/skills/Data Layer/Migration/SKILL.md +243 -0
- package/skills/Data Layer/Paging/SKILL.md +264 -0
- package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
- package/skills/Data Layer/Room/SKILL.md +244 -0
- package/skills/Data Layer/SQLite/SKILL.md +255 -0
- package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
- package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
- package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
- package/skills/Dependency Injection/Koin/SKILL.md +282 -0
- package/skills/Developer Experience/Detekt/SKILL.md +272 -0
- package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
- package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
- package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
- package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
- package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
- package/skills/Media/Audio/SKILL.md +257 -0
- package/skills/Media/Camera/SKILL.md +229 -0
- package/skills/Media/CameraX/SKILL.md +295 -0
- package/skills/Media/ExoPlayer/SKILL.md +258 -0
- package/skills/Media/Video/SKILL.md +228 -0
- package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
- package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
- package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
- package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
- package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
- package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
- package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
- package/skills/Navigation/Navigation/SKILL.md +215 -0
- package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
- package/skills/Networking/API Contract/SKILL.md +220 -0
- package/skills/Networking/Authentication/SKILL.md +210 -0
- package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
- package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
- package/skills/Networking/Ktor/SKILL.md +219 -0
- package/skills/Networking/Multipart Upload/SKILL.md +213 -0
- package/skills/Networking/OkHttp/SKILL.md +193 -0
- package/skills/Networking/REST/SKILL.md +178 -0
- package/skills/Networking/Rate Limiting/SKILL.md +170 -0
- package/skills/Networking/Retrofit/SKILL.md +241 -0
- package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
- package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
- package/skills/Networking/WebSocket/SKILL.md +224 -0
- package/skills/Observability/Crash Reporting/SKILL.md +219 -0
- package/skills/Observability/Logging/SKILL.md +168 -0
- package/skills/Observability/Metrics/SKILL.md +227 -0
- package/skills/Observability/Structured Logging/SKILL.md +234 -0
- package/skills/Performance/ANR Prevention/SKILL.md +192 -0
- package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
- package/skills/Performance/App Startup/SKILL.md +183 -0
- package/skills/Performance/Baseline Profile/SKILL.md +205 -0
- package/skills/Performance/Battery Optimization/SKILL.md +192 -0
- package/skills/Performance/Benchmark/SKILL.md +182 -0
- package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
- package/skills/Performance/Compose Optimization/SKILL.md +187 -0
- package/skills/Performance/Heap Management/SKILL.md +184 -0
- package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
- package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
- package/skills/Performance/Rendering Performance/SKILL.md +205 -0
- package/skills/Performance/Startup Optimization/SKILL.md +219 -0
- package/skills/Security/Biometric/SKILL.md +224 -0
- package/skills/Security/Certificate Transparency/SKILL.md +158 -0
- package/skills/Security/Cryptography/SKILL.md +244 -0
- package/skills/Security/Encrypted Storage/SKILL.md +273 -0
- package/skills/Security/Frida Detection/SKILL.md +230 -0
- package/skills/Security/Hook Detection/SKILL.md +197 -0
- package/skills/Security/Keystore/SKILL.md +272 -0
- package/skills/Security/Network Security Config/SKILL.md +186 -0
- package/skills/Security/Obfuscation/SKILL.md +226 -0
- package/skills/Security/Proguard/SKILL.md +202 -0
- package/skills/Security/R8/SKILL.md +234 -0
- package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
- package/skills/Security/Root Detection/SKILL.md +220 -0
- package/skills/Security/Secure Networking/SKILL.md +220 -0
- package/skills/System Integration/AlarmManager/SKILL.md +182 -0
- package/skills/System Integration/App Widget/SKILL.md +182 -0
- package/skills/System Integration/Deep Link/SKILL.md +187 -0
- package/skills/System Integration/Foreground Service/SKILL.md +212 -0
- package/skills/System Integration/Notification/SKILL.md +237 -0
- package/skills/System Integration/WorkManager/SKILL.md +256 -0
- package/skills/System Integration/clipboard/SKILL.md +155 -0
- package/skills/System Integration/share-intent/SKILL.md +182 -0
- package/skills/Testing/Compose Testing/SKILL.md +296 -0
- package/skills/Testing/Espresso/SKILL.md +292 -0
- package/skills/Testing/Fake Data/SKILL.md +245 -0
- package/skills/Testing/Integration Testing/SKILL.md +288 -0
- package/skills/Testing/Mocking/SKILL.md +229 -0
- package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
- package/skills/Testing/UI Testing/SKILL.md +293 -0
- package/skills/Testing/Unit Testing/SKILL.md +309 -0
- package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
- package/skills/UI System/Compose/SKILL.md +296 -0
- package/skills/UI System/Compose Animation/SKILL.md +281 -0
- package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
- package/skills/UI System/Compose Navigation/SKILL.md +255 -0
- package/skills/UI System/Compose Performance/SKILL.md +274 -0
- package/skills/UI System/Design System/SKILL.md +217 -0
- package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
- package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
- package/skills/UI System/Loading Strategy/SKILL.md +254 -0
- package/skills/UI System/Material 3/SKILL.md +279 -0
- package/skills/UI System/RTL/SKILL.md +179 -0
- package/src/index.ts +182 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,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
|