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: paging
3
+ description: >
4
+ Jetpack Paging 3 setup and patterns for Android.
5
+ Load this skill when loading large datasets incrementally, implementing
6
+ infinite scroll, paginating network or database results, or combining
7
+ paged network data with a local Room cache.
8
+ ---
9
+
10
+ # Paging
11
+
12
+ ## Overview
13
+ Jetpack Paging 3 enables loading large datasets in pages — from network, database, or both. It integrates with Room, Retrofit, and Compose. The library handles load state, error retry, and list diffing automatically.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Use Paging when a list **could grow unbounded** — don't load everything at once
20
+ - **PagingSource** defines how to load a single page
21
+ - **RemoteMediator** coordinates network + Room cache for offline-first paging
22
+ - **PagingData** is consumed only once — use `cachedIn(viewModelScope)` to survive rotation
23
+ - Never collect `PagingData` directly in ViewModel — use `collectAsLazyPagingItems` in Compose
24
+
25
+ ---
26
+
27
+ ## Setup
28
+
29
+ ```toml
30
+ [versions]
31
+ paging = "3.3.2"
32
+
33
+ [libraries]
34
+ paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging" }
35
+ paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging" }
36
+ paging-room = { module = "androidx.room:room-paging", version.ref = "room" }
37
+ ```
38
+
39
+ ---
40
+
41
+ ## PagingSource — Network Only
42
+
43
+ ```kotlin
44
+ // ✅ Network-only PagingSource
45
+ class UserPagingSource(
46
+ private val api: UserApi,
47
+ private val query: String
48
+ ) : PagingSource<Int, UserDto>() {
49
+
50
+ override suspend fun load(params: LoadParams<Int>): LoadResult<Int, UserDto> {
51
+ val page = params.key ?: 1
52
+
53
+ return try {
54
+ val response = api.getUsers(
55
+ query = query,
56
+ page = page,
57
+ pageSize = params.loadSize
58
+ )
59
+
60
+ LoadResult.Page(
61
+ data = response.users,
62
+ prevKey = if (page == 1) null else page - 1,
63
+ nextKey = if (response.users.isEmpty()) null else page + 1
64
+ )
65
+ } catch (e: IOException) {
66
+ LoadResult.Error(e)
67
+ } catch (e: HttpException) {
68
+ LoadResult.Error(e)
69
+ }
70
+ }
71
+
72
+ override fun getRefreshKey(state: PagingState<Int, UserDto>): Int? {
73
+ return state.anchorPosition?.let { anchor ->
74
+ state.closestPageToPosition(anchor)?.prevKey?.plus(1)
75
+ ?: state.closestPageToPosition(anchor)?.nextKey?.minus(1)
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Repository — Network-Only Paging
84
+
85
+ ```kotlin
86
+ class UserRepository @Inject constructor(
87
+ private val api: UserApi,
88
+ private val mapper: UserMapper
89
+ ) {
90
+ fun searchUsers(query: String): Flow<PagingData<User>> = Pager(
91
+ config = PagingConfig(
92
+ pageSize = 20,
93
+ prefetchDistance = 5,
94
+ enablePlaceholders = false
95
+ ),
96
+ pagingSourceFactory = { UserPagingSource(api, query) }
97
+ ).flow.map { pagingData ->
98
+ pagingData.map { mapper.toDomain(it) }
99
+ }
100
+ }
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Room + Paging (Database-Only)
106
+
107
+ ```kotlin
108
+ // ✅ Room generates PagingSource automatically
109
+ @Dao
110
+ interface UserDao {
111
+ @Query("SELECT * FROM users ORDER BY name ASC")
112
+ fun pagingSource(): PagingSource<Int, UserEntity>
113
+ }
114
+
115
+ // Repository
116
+ class UserRepository @Inject constructor(private val userDao: UserDao) {
117
+ fun observeUsersPaged(): Flow<PagingData<User>> = Pager(
118
+ config = PagingConfig(pageSize = 20),
119
+ pagingSourceFactory = { userDao.pagingSource() }
120
+ ).flow.map { it.map(UserEntity::toDomain) }
121
+ }
122
+ ```
123
+
124
+ ---
125
+
126
+ ## RemoteMediator — Network + Room Cache
127
+
128
+ ```kotlin
129
+ // ✅ RemoteMediator: network fills Room, Room provides paged data
130
+ @OptIn(ExperimentalPagingApi::class)
131
+ class UserRemoteMediator(
132
+ private val api: UserApi,
133
+ private val db: AppDatabase
134
+ ) : RemoteMediator<Int, UserEntity>() {
135
+
136
+ override suspend fun load(
137
+ loadType: LoadType,
138
+ state: PagingState<Int, UserEntity>
139
+ ): MediatorResult {
140
+
141
+ val page = when (loadType) {
142
+ LoadType.REFRESH -> 1
143
+ LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
144
+ LoadType.APPEND -> {
145
+ val lastItem = state.lastItemOrNull()
146
+ ?: return MediatorResult.Success(endOfPaginationReached = false)
147
+ getNextPageForItem(lastItem)
148
+ }
149
+ }
150
+
151
+ return try {
152
+ val response = api.getUsers(page = page, pageSize = state.config.pageSize)
153
+
154
+ db.withTransaction {
155
+ if (loadType == LoadType.REFRESH) {
156
+ db.userDao().deleteAll()
157
+ }
158
+ db.userDao().upsertAll(response.users.map { it.toEntity() })
159
+ }
160
+
161
+ MediatorResult.Success(endOfPaginationReached = response.users.isEmpty())
162
+ } catch (e: IOException) {
163
+ MediatorResult.Error(e)
164
+ } catch (e: HttpException) {
165
+ MediatorResult.Error(e)
166
+ }
167
+ }
168
+
169
+ private suspend fun getNextPageForItem(item: UserEntity): Int {
170
+ // derive page number from item position
171
+ return (db.userDao().getItemIndex(item.id) / 20) + 2
172
+ }
173
+ }
174
+
175
+ // ✅ Pager with RemoteMediator
176
+ @OptIn(ExperimentalPagingApi::class)
177
+ fun usersPaged(): Flow<PagingData<User>> = Pager(
178
+ config = PagingConfig(pageSize = 20),
179
+ remoteMediator = UserRemoteMediator(api, db),
180
+ pagingSourceFactory = { db.userDao().pagingSource() }
181
+ ).flow.map { it.map(UserEntity::toDomain) }
182
+ ```
183
+
184
+ ---
185
+
186
+ ## ViewModel
187
+
188
+ ```kotlin
189
+ @HiltViewModel
190
+ class UserListViewModel @Inject constructor(
191
+ private val repository: UserRepository
192
+ ) : ViewModel() {
193
+
194
+ private val _query = MutableStateFlow("")
195
+
196
+ // ✅ cachedIn — survives rotation without re-fetching
197
+ val users: Flow<PagingData<User>> = _query
198
+ .debounce(300)
199
+ .flatMapLatest { query -> repository.searchUsers(query) }
200
+ .cachedIn(viewModelScope)
201
+
202
+ fun onQueryChanged(query: String) { _query.value = query }
203
+ }
204
+ ```
205
+
206
+ ---
207
+
208
+ ## Compose UI
209
+
210
+ ```kotlin
211
+ @Composable
212
+ fun UserListScreen(viewModel: UserListViewModel = hiltViewModel()) {
213
+ val users = viewModel.users.collectAsLazyPagingItems()
214
+
215
+ LazyColumn {
216
+ items(
217
+ count = users.itemCount,
218
+ key = users.itemKey { it.id }
219
+ ) { index ->
220
+ val user = users[index]
221
+ if (user != null) UserCard(user = user)
222
+ else UserCardPlaceholder()
223
+ }
224
+
225
+ // ✅ Load state handling
226
+ when (val state = users.loadState.append) {
227
+ is LoadState.Loading -> item { LoadingIndicator() }
228
+ is LoadState.Error -> item {
229
+ ErrorItem(
230
+ message = state.error.message ?: "Error",
231
+ onRetry = { users.retry() }
232
+ )
233
+ }
234
+ else -> Unit
235
+ }
236
+ }
237
+
238
+ // ✅ Refresh state
239
+ when (users.loadState.refresh) {
240
+ is LoadState.Loading -> FullScreenLoading()
241
+ is LoadState.Error -> FullScreenError(onRetry = { users.refresh() })
242
+ else -> Unit
243
+ }
244
+ }
245
+ ```
246
+
247
+ ---
248
+
249
+ ## Anti-Patterns
250
+
251
+ - Loading entire dataset without Paging when the list can grow unbounded
252
+ - Not using `cachedIn(viewModelScope)` — re-fetches from page 1 on rotation
253
+ - Collecting `PagingData` in ViewModel with `collect {}` — it's a one-shot stream
254
+ - Missing load state handling — no loading indicator or error state shown
255
+ - Using `enablePlaceholders = true` without proper placeholder UI
256
+
257
+ ---
258
+
259
+ ## Related Skills
260
+ - `room` — Room PagingSource generation
261
+ - `retrofit` — network data source for PagingSource
262
+ - `repository-pattern` — Pager lives in repository
263
+ - `compose` — LazyColumn with LazyPagingItems
264
+ - `cache-strategy` — offline-first paging with RemoteMediator
@@ -0,0 +1,250 @@
1
+ ---
2
+ name: proto-datastore
3
+ description: >
4
+ Proto DataStore setup and usage for typed object persistence in Android.
5
+ Load this skill when storing complex structured data with type safety,
6
+ using Protobuf schemas, or when Preferences DataStore is insufficient.
7
+ ---
8
+
9
+ # Proto DataStore
10
+
11
+ ## Overview
12
+ Proto DataStore stores typed objects defined by Protobuf schemas. Unlike Preferences DataStore (which uses string keys), Proto DataStore is strongly typed, schema-enforced, and forward-compatible by design. It is ideal for storing structured settings or state that evolves over time.
13
+
14
+ ---
15
+
16
+ ## Core Principles
17
+
18
+ - Use Proto DataStore when **type safety and schema evolution** matter
19
+ - Protobuf schemas define the contract — never change field numbers once released
20
+ - Like Preferences DataStore — **one instance per file**, inject via Hilt
21
+ - Always handle **IOException** in Flow collection
22
+ - Add new fields to the proto — never remove or renumber existing fields
23
+
24
+ ---
25
+
26
+ ## Setup
27
+
28
+ ```toml
29
+ # libs.versions.toml
30
+ [versions]
31
+ datastore = "1.1.1"
32
+ protobuf = "4.26.1"
33
+ protobuf-plugin = "0.9.4"
34
+
35
+ [libraries]
36
+ datastore-proto = { module = "androidx.datastore:datastore", version.ref = "datastore" }
37
+ protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf" }
38
+ protobuf-kotlin-lite = { module = "com.google.protobuf:protobuf-kotlin-lite", version.ref = "protobuf" }
39
+
40
+ [plugins]
41
+ protobuf = { id = "com.google.protobuf", version.ref = "protobuf-plugin" }
42
+ ```
43
+
44
+ ```kotlin
45
+ // build.gradle.kts
46
+ plugins {
47
+ alias(libs.plugins.protobuf)
48
+ }
49
+
50
+ dependencies {
51
+ implementation(libs.datastore.proto)
52
+ implementation(libs.protobuf.javalite)
53
+ implementation(libs.protobuf.kotlin.lite)
54
+ }
55
+
56
+ protobuf {
57
+ protoc {
58
+ artifact = "com.google.protobuf:protoc:${libs.versions.protobuf.get()}"
59
+ }
60
+ generateProtoTasks {
61
+ all().forEach { task ->
62
+ task.builtins {
63
+ create("java") { option("lite") }
64
+ create("kotlin") { option("lite") }
65
+ }
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Proto Schema Definition
74
+
75
+ ```protobuf
76
+ // src/main/proto/user_preferences.proto
77
+ syntax = "proto3";
78
+ option java_package = "com.example.app.datastore";
79
+ option java_multiple_files = true;
80
+
81
+ message UserPreferences {
82
+ bool is_dark_mode = 1;
83
+ string language = 2;
84
+ int32 font_size = 3;
85
+ SortOrder sort_order = 4;
86
+ int64 last_sync_timestamp = 5;
87
+ // ✅ New fields added here — never remove or renumber existing
88
+ }
89
+
90
+ enum SortOrder {
91
+ SORT_ORDER_UNSPECIFIED = 0;
92
+ SORT_ORDER_ASC = 1;
93
+ SORT_ORDER_DESC = 2;
94
+ }
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Serializer
100
+
101
+ ```kotlin
102
+ // ✅ Implement Serializer for the proto type
103
+ object UserPreferencesSerializer : Serializer<UserPreferences> {
104
+
105
+ override val defaultValue: UserPreferences = UserPreferences.getDefaultInstance()
106
+
107
+ override suspend fun readFrom(input: InputStream): UserPreferences {
108
+ try {
109
+ return UserPreferences.parseFrom(input)
110
+ } catch (exception: InvalidProtocolBufferException) {
111
+ throw CorruptionException("Cannot read proto.", exception)
112
+ }
113
+ }
114
+
115
+ override suspend fun writeTo(t: UserPreferences, output: OutputStream) {
116
+ t.writeTo(output)
117
+ }
118
+ }
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Creating DataStore
124
+
125
+ ```kotlin
126
+ // ✅ Create via extension property
127
+ val Context.userPreferencesDataStore: DataStore<UserPreferences> by dataStore(
128
+ fileName = "user_preferences.pb",
129
+ serializer = UserPreferencesSerializer
130
+ )
131
+
132
+ // ✅ Provide via Hilt
133
+ @Module
134
+ @InstallIn(SingletonComponent::class)
135
+ object DataStoreModule {
136
+
137
+ @Provides
138
+ @Singleton
139
+ fun provideUserPreferencesDataStore(
140
+ @ApplicationContext context: Context
141
+ ): DataStore<UserPreferences> = context.userPreferencesDataStore
142
+ }
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Repository
148
+
149
+ ```kotlin
150
+ class UserPreferencesRepository @Inject constructor(
151
+ private val dataStore: DataStore<UserPreferences>
152
+ ) {
153
+
154
+ // ✅ Read — expose as Flow
155
+ val userPreferences: Flow<UserPreferences> = dataStore.data
156
+ .catch { e ->
157
+ if (e is IOException) emit(UserPreferences.getDefaultInstance())
158
+ else throw e
159
+ }
160
+
161
+ val isDarkMode: Flow<Boolean> = userPreferences.map { it.isDarkMode }
162
+ val language: Flow<String> = userPreferences.map { it.language }
163
+
164
+ // ✅ Write — use updateData or update (proto-specific)
165
+ suspend fun setDarkMode(enabled: Boolean) {
166
+ dataStore.updateData { current ->
167
+ current.toBuilder()
168
+ .setIsDarkMode(enabled)
169
+ .build()
170
+ }
171
+ }
172
+
173
+ suspend fun setLanguage(language: String) {
174
+ dataStore.updateData { current ->
175
+ current.toBuilder()
176
+ .setLanguage(language)
177
+ .build()
178
+ }
179
+ }
180
+
181
+ // ✅ Kotlin DSL style (with protobuf-kotlin-lite)
182
+ suspend fun updatePreferences(update: UserPreferences.Builder.() -> Unit) {
183
+ dataStore.updateData { current ->
184
+ current.copy(update)
185
+ }
186
+ }
187
+
188
+ // Usage:
189
+ // preferencesRepository.updatePreferences {
190
+ // isDarkMode = true
191
+ // language = "fa"
192
+ // }
193
+ }
194
+ ```
195
+
196
+ ---
197
+
198
+ ## Schema Evolution Rules
199
+
200
+ ```protobuf
201
+ // ✅ Safe changes
202
+ // - Add new optional fields with new field numbers
203
+ // - Add new enum values (with default 0 for unknown)
204
+
205
+ // ❌ Breaking changes — NEVER do these to a released schema
206
+ // - Remove a field
207
+ // - Rename a field (the name is cosmetic in proto3, but confusing)
208
+ // - Change a field's type
209
+ // - Reuse a field number for a different field
210
+
211
+ // ✅ Example: safe evolution
212
+ message UserPreferences {
213
+ bool is_dark_mode = 1;
214
+ string language = 2;
215
+ int32 font_size = 3; // existing
216
+ SortOrder sort_order = 4; // existing
217
+ // New field in v2 — safe to add
218
+ bool notifications_enabled = 5;
219
+ string theme_color = 6;
220
+ }
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Proto DataStore vs Preferences DataStore
226
+
227
+ | | Proto DataStore | Preferences DataStore |
228
+ |--|----------------|----------------------|
229
+ | Type safety | ✅ Strongly typed | ⚠️ Key-based |
230
+ | Schema evolution | ✅ Protobuf rules | ❌ Manual |
231
+ | Setup complexity | ⚠️ Higher | ✅ Simple |
232
+ | Best for | Structured objects | Simple settings |
233
+
234
+ ---
235
+
236
+ ## Anti-Patterns
237
+
238
+ - Reusing or renumbering proto field numbers — corrupts data for existing users
239
+ - Removing fields from proto — breaks deserialization of existing data
240
+ - Not providing a default value in Serializer — crashes on empty/new install
241
+ - Multiple DataStore instances for the same file — data corruption
242
+ - Storing large binary data in proto — use files or Room instead
243
+
244
+ ---
245
+
246
+ ## Related Skills
247
+ - `datastore` — Preferences DataStore for simple key-value storage
248
+ - `hilt` — singleton DataStore provision
249
+ - `repository-pattern` — wrapping DataStore in a Repository
250
+ - `key-value-store-strategy` — choosing between DataStore variants