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,228 @@
1
+ ---
2
+ name: reactive-state-management
3
+ description: >
4
+ Reactive state management patterns for Android using StateFlow and Flow.
5
+ Load this skill when designing how state is produced, transformed, and
6
+ consumed across ViewModel and UI layers in a reactive, observable way.
7
+ ---
8
+
9
+ # Reactive State Management
10
+
11
+ ## Overview
12
+
13
+ Reactive State Management means that UI state is modeled as an observable stream. The UI never pulls state — it reacts to state emissions. State is always immutable, flows in one direction, and is the single source of truth for what the UI renders.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - State is **immutable** — never mutate, always replace via `copy()`
20
+ - State flows **one direction** — ViewModel → UI, never reverse
21
+ - UI **observes** state — never reads it on demand
22
+ - There is **one state object** per screen — not multiple separate fields
23
+ - State is **complete** — the UI can render correctly from state alone
24
+
25
+ ---
26
+
27
+ ## State Modeling
28
+
29
+ ```kotlin
30
+ // ✅ Single sealed state per screen
31
+ data class UserListUiState(
32
+ val users: List<User> = emptyList(),
33
+ val isLoading: Boolean = false,
34
+ val error: String? = null,
35
+ val isEmpty: Boolean = false
36
+ )
37
+
38
+ // ✅ Use sealed class for mutually exclusive states
39
+ sealed class ScreenState<out T> {
40
+ object Loading : ScreenState<Nothing>()
41
+ data class Success<T>(val data: T) : ScreenState<T>()
42
+ data class Error(val message: String) : ScreenState<Nothing>()
43
+ }
44
+ ```
45
+
46
+ ---
47
+
48
+ ## ViewModel — Producing State
49
+
50
+ ```kotlin
51
+ class UserListViewModel(
52
+ private val getUsersUseCase: GetUsersUseCase
53
+ ) : ViewModel() {
54
+
55
+ // ✅ Private mutable — public immutable
56
+ private val _state = MutableStateFlow(UserListUiState())
57
+ val state: StateFlow<UserListUiState> = _state.asStateFlow()
58
+
59
+ // ✅ One-off events via SharedFlow
60
+ private val _events = MutableSharedFlow<UserListEvent>()
61
+ val events: SharedFlow<UserListEvent> = _events.asSharedFlow()
62
+
63
+ init {
64
+ loadUsers()
65
+ }
66
+
67
+ fun loadUsers() {
68
+ viewModelScope.launch {
69
+ _state.update { it.copy(isLoading = true, error = null) }
70
+ getUsersUseCase()
71
+ .onSuccess { users ->
72
+ _state.update {
73
+ it.copy(
74
+ isLoading = false,
75
+ users = users,
76
+ isEmpty = users.isEmpty()
77
+ )
78
+ }
79
+ }
80
+ .onFailure { error ->
81
+ _state.update {
82
+ it.copy(isLoading = false, error = error.message)
83
+ }
84
+ }
85
+ }
86
+ }
87
+
88
+ fun onUserClicked(userId: String) {
89
+ viewModelScope.launch {
90
+ _events.emit(UserListEvent.NavigateToDetail(userId))
91
+ }
92
+ }
93
+ }
94
+
95
+ // ✅ Events for one-time side effects
96
+ sealed class UserListEvent {
97
+ data class NavigateToDetail(val userId: String) : UserListEvent()
98
+ data class ShowSnackbar(val message: String) : UserListEvent()
99
+ }
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Deriving State from Flow
105
+
106
+ ```kotlin
107
+ // ✅ stateIn — derive StateFlow from upstream Flow
108
+ class UserListViewModel(repository: UserRepository) : ViewModel() {
109
+
110
+ val state: StateFlow<UserListUiState> = repository
111
+ .observeUsers()
112
+ .map { users ->
113
+ UserListUiState(
114
+ users = users,
115
+ isEmpty = users.isEmpty()
116
+ )
117
+ }
118
+ .stateIn(
119
+ scope = viewModelScope,
120
+ started = SharingStarted.WhileSubscribed(5_000),
121
+ initialValue = UserListUiState(isLoading = true)
122
+ )
123
+ }
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Combining Multiple Sources
129
+
130
+ ```kotlin
131
+ // ✅ combine — merge multiple streams into one state
132
+ class DashboardViewModel(
133
+ userRepository: UserRepository,
134
+ settingsRepository: SettingsRepository
135
+ ) : ViewModel() {
136
+
137
+ val state: StateFlow<DashboardUiState> = combine(
138
+ userRepository.observeCurrentUser(),
139
+ settingsRepository.observeSettings()
140
+ ) { user, settings ->
141
+ DashboardUiState(
142
+ userName = user.name,
143
+ isDarkMode = settings.isDarkMode
144
+ )
145
+ }.stateIn(
146
+ scope = viewModelScope,
147
+ started = SharingStarted.WhileSubscribed(5_000),
148
+ initialValue = DashboardUiState()
149
+ )
150
+ }
151
+ ```
152
+
153
+ ---
154
+
155
+ ## UI — Consuming State
156
+
157
+ ```kotlin
158
+ // ✅ Compose — collect state
159
+ @Composable
160
+ fun UserListScreen(viewModel: UserListViewModel = hiltViewModel()) {
161
+ val state by viewModel.state.collectAsStateWithLifecycle()
162
+
163
+ // ✅ Collect one-time events
164
+ val lifecycleOwner = LocalLifecycleOwner.current
165
+ LaunchedEffect(Unit) {
166
+ viewModel.events
167
+ .flowWithLifecycle(lifecycleOwner.lifecycle)
168
+ .collect { event ->
169
+ when (event) {
170
+ is UserListEvent.NavigateToDetail -> navController.navigate(...)
171
+ is UserListEvent.ShowSnackbar -> snackbarHostState.showSnackbar(...)
172
+ }
173
+ }
174
+ }
175
+
176
+ UserListContent(state = state, onUserClick = viewModel::onUserClicked)
177
+ }
178
+
179
+ // ✅ Fragment — collect with repeatOnLifecycle
180
+ viewLifecycleOwner.lifecycleScope.launch {
181
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
182
+ viewModel.state.collect { state ->
183
+ renderState(state)
184
+ }
185
+ }
186
+ }
187
+ ```
188
+
189
+ ---
190
+
191
+ ## State vs Events
192
+
193
+ | Type | Use for | Implementation |
194
+ | --------- | ------------------------------------------ | -------------- |
195
+ | **State** | Persistent UI data (list, loading, error) | `StateFlow` |
196
+ | **Event** | One-time actions (navigate, toast, dialog) | `SharedFlow` |
197
+
198
+ ```kotlin
199
+ // ✅ State — persists, survives recomposition
200
+ val isLoading: StateFlow<Boolean>
201
+
202
+ // ✅ Event — fires once, not replayed
203
+ val navigationEvent: SharedFlow<NavigationEvent>
204
+
205
+ // ❌ Don't model navigation in state — it replays on rotation
206
+ data class UiState(val navigateTo: String?) // wrong
207
+ ```
208
+
209
+ ---
210
+
211
+ ## Anti-Patterns
212
+
213
+ - Multiple separate `StateFlow` fields per screen — use one state object
214
+ - Exposing `MutableStateFlow` from ViewModel — always expose as `StateFlow`
215
+ - Collecting state with `lifecycleScope.launch {}` without `repeatOnLifecycle` — leaks in background
216
+ - Modeling one-time events as state — use `SharedFlow` for events
217
+ - Calling `stateFlow.value` in UI to read state imperatively — always collect reactively
218
+ - Triggering side effects inside `map {}` or `combine {}` — use `onEach` or handle in ViewModel
219
+
220
+ ---
221
+
222
+ ## Related Skills
223
+
224
+ - `stateflow` — StateFlow internals and patterns
225
+ - `sharedflow` — SharedFlow for one-time events
226
+ - `reactive-streams` — Flow operators and transformation
227
+ - `mvvm` — ViewModel and state ownership
228
+ - `side-effect-management` — handling side effects in reactive pipelines
@@ -0,0 +1,235 @@
1
+ ---
2
+ name: reactive-streams
3
+ description: >
4
+ Reactive Streams patterns and operators for Android development.
5
+ Load this skill when working with data streams, chaining async operations,
6
+ transforming flows, combining multiple sources, or designing reactive pipelines
7
+ across layers.
8
+ ---
9
+
10
+ # Reactive Streams
11
+
12
+ ## Overview
13
+
14
+ Reactive Streams is a standard for asynchronous stream processing with non-blocking backpressure. In Kotlin/Android, this is implemented via `Flow`. This skill covers how to design, transform, and consume reactive streams correctly across all layers.
15
+
16
+ ---
17
+
18
+ ## Core Principles
19
+
20
+ - Data flows **downward** — from data source to UI, never upward
21
+ - Streams are **cold by default** (`Flow`) — only active when collected
22
+ - **Never collect a Flow inside another Flow** — use operators instead
23
+ - **Never expose `MutableStateFlow` or `MutableSharedFlow`** from public APIs
24
+ - Transformation happens in the **middle layers** — not in UI, not in data source
25
+
26
+ ---
27
+
28
+ ## Flow Basics
29
+
30
+ ```kotlin
31
+ // ✅ Cold flow — executes only when collected
32
+ fun getUsers(): Flow<List<User>> = flow {
33
+ while (true) {
34
+ emit(repository.fetchUsers())
35
+ delay(30_000)
36
+ }
37
+ }
38
+
39
+ // ✅ Flow from suspend function
40
+ fun getUserById(id: String): Flow<User> = flow {
41
+ emit(repository.getUser(id))
42
+ }
43
+
44
+ // ✅ Flow from Room (already returns Flow)
45
+ @Query("SELECT * FROM users")
46
+ fun observeUsers(): Flow<List<UserEntity>>
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Key Operators
52
+
53
+ ### Transformation
54
+
55
+ ```kotlin
56
+ // map — transform each emission
57
+ val names: Flow<List<String>> = usersFlow.map { users ->
58
+ users.map { it.name }
59
+ }
60
+
61
+ // flatMapLatest — switch to new flow on each emission (search, user selection)
62
+ val results: Flow<List<Result>> = queryFlow.flatMapLatest { query ->
63
+ repository.search(query)
64
+ }
65
+
66
+ // transform — emit multiple values per input
67
+ val events: Flow<Event> = actionsFlow.transform { action ->
68
+ emit(Event.Started)
69
+ process(action)
70
+ emit(Event.Completed)
71
+ }
72
+ ```
73
+
74
+ ### Filtering
75
+
76
+ ```kotlin
77
+ // filter — conditional emission
78
+ val activeUsers = usersFlow.filter { it.isActive }
79
+
80
+ // distinctUntilChanged — suppress duplicate emissions
81
+ val query = searchFlow.distinctUntilChanged()
82
+
83
+ // debounce — wait for silence (search input)
84
+ val debouncedQuery = searchFlow
85
+ .debounce(300)
86
+ .distinctUntilChanged()
87
+ ```
88
+
89
+ ### Combination
90
+
91
+ ```kotlin
92
+ // combine — latest from each source
93
+ val uiState: Flow<UiState> = combine(
94
+ usersFlow,
95
+ isLoadingFlow,
96
+ errorFlow
97
+ ) { users, isLoading, error ->
98
+ UiState(users = users, isLoading = isLoading, error = error)
99
+ }
100
+
101
+ // zip — pair emissions one-to-one
102
+ val paired: Flow<Pair<A, B>> = flowA.zip(flowB) { a, b -> a to b }
103
+
104
+ // merge — emit from all sources as they arrive
105
+ val allEvents: Flow<Event> = merge(clickFlow, scrollFlow, keyboardFlow)
106
+ ```
107
+
108
+ ### Error Handling
109
+
110
+ ```kotlin
111
+ // ✅ catch — handle errors without stopping the stream
112
+ usersFlow
113
+ .catch { e -> emit(emptyList()) }
114
+ .collect { ... }
115
+
116
+ // ✅ retry — retry on failure
117
+ networkFlow
118
+ .retry(3) { e -> e is IOException }
119
+ .collect { ... }
120
+
121
+ // ✅ retryWhen — conditional retry with backoff
122
+ networkFlow
123
+ .retryWhen { cause, attempt ->
124
+ cause is IOException && attempt < 3
125
+ }
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Flow Across Layers
131
+
132
+ ```kotlin
133
+ // ✅ Repository — expose Flow from data source
134
+ class UserRepository {
135
+ fun observeUsers(): Flow<List<User>> =
136
+ localDataSource.observeUsers().map { entities ->
137
+ entities.map { it.toDomain() }
138
+ }
139
+ }
140
+
141
+ // ✅ UseCase — transform and combine
142
+ class GetActiveUsersUseCase(private val repository: UserRepository) {
143
+ operator fun invoke(): Flow<List<User>> =
144
+ repository.observeUsers()
145
+ .map { users -> users.filter { it.isActive } }
146
+ .distinctUntilChanged()
147
+ }
148
+
149
+ // ✅ ViewModel — collect into StateFlow
150
+ class UserViewModel(useCase: GetActiveUsersUseCase) : ViewModel() {
151
+ val users: StateFlow<List<User>> = useCase()
152
+ .stateIn(
153
+ scope = viewModelScope,
154
+ started = SharingStarted.WhileSubscribed(5_000),
155
+ initialValue = emptyList()
156
+ )
157
+ }
158
+
159
+ // ✅ UI — collect with lifecycle awareness
160
+ viewLifecycleOwner.lifecycleScope.launch {
161
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
162
+ viewModel.users.collect { users ->
163
+ adapter.submitList(users)
164
+ }
165
+ }
166
+ }
167
+ ```
168
+
169
+ ---
170
+
171
+ ## stateIn vs shareIn
172
+
173
+ ```kotlin
174
+ // ✅ stateIn — convert Flow to StateFlow (single value, replays last)
175
+ val state: StateFlow<T> = flow.stateIn(
176
+ scope = viewModelScope,
177
+ started = SharingStarted.WhileSubscribed(5_000),
178
+ initialValue = initialValue
179
+ )
180
+
181
+ // ✅ shareIn — convert Flow to SharedFlow (multicast, configurable replay)
182
+ val shared: SharedFlow<T> = flow.shareIn(
183
+ scope = viewModelScope,
184
+ started = SharingStarted.WhileSubscribed(5_000),
185
+ replay = 1
186
+ )
187
+
188
+ // WhileSubscribed(5_000) — keeps upstream alive 5s after last subscriber
189
+ // Lazy — starts only when first subscriber appears
190
+ // Eagerly — starts immediately
191
+ ```
192
+
193
+ ---
194
+
195
+ ## Backpressure
196
+
197
+ ```kotlin
198
+ // ✅ buffer — decouple producer from consumer
199
+ fastProducerFlow
200
+ .buffer(capacity = 64)
201
+ .collect { slowConsumer(it) }
202
+
203
+ // ✅ conflate — drop intermediate values, keep latest
204
+ sensorFlow
205
+ .conflate()
206
+ .collect { render(it) }
207
+
208
+ // ✅ collectLatest — cancel previous on new emission
209
+ searchFlow.collectLatest { query ->
210
+ val results = repository.search(query)
211
+ showResults(results)
212
+ }
213
+ ```
214
+
215
+ ---
216
+
217
+ ## Anti-Patterns
218
+
219
+ - Collecting Flow inside `viewModelScope.launch {}` directly in UI — use `repeatOnLifecycle`
220
+ - Using `GlobalScope` to launch flow collection — always use scoped coroutines
221
+ - Nested `collect` calls — use `flatMapLatest` or `combine` instead
222
+ - Creating a new Flow on every recomposition in Compose — hoist to ViewModel
223
+ - Using `flow.first()` in a loop — use operators instead
224
+ - Ignoring backpressure on fast producers — use `buffer`, `conflate`, or `collectLatest`
225
+ - Exposing `MutableSharedFlow` — always expose as `SharedFlow` or `Flow`
226
+
227
+ ---
228
+
229
+ ## Related Skills
230
+
231
+ - `coroutine` — coroutine scope and dispatcher management
232
+ - `stateflow` — StateFlow specific patterns
233
+ - `sharedflow` — SharedFlow specific patterns
234
+ - `state-management` — UI state with reactive streams
235
+ - `repository-pattern` — exposing streams from data layer
@@ -0,0 +1,191 @@
1
+ ---
2
+ name: serialization
3
+ description: >
4
+ Kotlinx Serialization setup and usage for Android and KMP projects.
5
+ Load this skill when serializing/deserializing JSON or other formats,
6
+ configuring the Json instance, mapping API responses, or handling
7
+ custom serialization logic.
8
+ ---
9
+
10
+ # Serialization
11
+
12
+ ## Overview
13
+
14
+ Kotlinx Serialization is the standard serialization library for Kotlin/Android. It is compile-time safe, KMP-compatible, and integrates natively with Retrofit, Ktor, and DataStore.
15
+
16
+ ---
17
+
18
+ ## Core Principles
19
+
20
+ - Use `@Serializable` on all data transfer objects (DTOs)
21
+ - Never use Gson or Moshi — use Kotlinx Serialization
22
+ - Configure a **single shared `Json` instance** per module — never create inline
23
+ - Domain models must **not** be `@Serializable` — only DTOs
24
+ - Use `@SerialName` when API field names differ from Kotlin naming conventions
25
+
26
+ ---
27
+
28
+ ## Setup
29
+
30
+ ```toml
31
+ # libs.versions.toml
32
+ [versions]
33
+ kotlinx-serialization = "1.7.1"
34
+
35
+ [libraries]
36
+ kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
37
+
38
+ [plugins]
39
+ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
40
+ ```
41
+
42
+ ```kotlin
43
+ // build.gradle.kts
44
+ plugins {
45
+ alias(libs.plugins.kotlin.serialization)
46
+ }
47
+
48
+ dependencies {
49
+ implementation(libs.kotlinx.serialization.json)
50
+ }
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Json Instance Configuration
56
+
57
+ ```kotlin
58
+ // ✅ Single shared instance — define once, inject everywhere
59
+ val json = Json {
60
+ ignoreUnknownKeys = true // safe for API evolution
61
+ isLenient = false // strict parsing by default
62
+ encodeDefaults = false // don't serialize default values
63
+ prettyPrint = false // compact output in production
64
+ coerceInputValues = true // handle null → default for non-nullable
65
+ }
66
+ ```
67
+
68
+ ---
69
+
70
+ ## DTO Design
71
+
72
+ ```kotlin
73
+ // ✅ DTOs are @Serializable — domain models are not
74
+ @Serializable
75
+ data class UserDto(
76
+ @SerialName("user_id") val id: String,
77
+ @SerialName("full_name") val name: String,
78
+ @SerialName("email_address") val email: String,
79
+ @SerialName("is_active") val isActive: Boolean = true,
80
+ @SerialName("created_at") val createdAt: Long? = null
81
+ )
82
+
83
+ // ✅ Domain model — clean, no serialization annotations
84
+ data class User(
85
+ val id: String,
86
+ val name: String,
87
+ val email: String,
88
+ val isActive: Boolean,
89
+ val createdAt: Long?
90
+ )
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Custom Serializers
96
+
97
+ ```kotlin
98
+ // ✅ Use custom serializer for types that can't be annotated
99
+ object UUIDSerializer : KSerializer<UUID> {
100
+ override val descriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
101
+ override fun serialize(encoder: Encoder, value: UUID) = encoder.encodeString(value.toString())
102
+ override fun deserialize(decoder: Decoder): UUID = UUID.fromString(decoder.decodeString())
103
+ }
104
+
105
+ // Apply on property
106
+ @Serializable
107
+ data class OrderDto(
108
+ @Serializable(with = UUIDSerializer::class)
109
+ val id: UUID
110
+ )
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Sealed Class Serialization
116
+
117
+ ```kotlin
118
+ // ✅ Use @JsonClassDiscriminator for polymorphic types
119
+ @Serializable
120
+ @JsonClassDiscriminator("type")
121
+ sealed class EventDto {
122
+ @Serializable
123
+ @SerialName("click")
124
+ data class Click(val x: Int, val y: Int) : EventDto()
125
+
126
+ @Serializable
127
+ @SerialName("scroll")
128
+ data class Scroll(val delta: Int) : EventDto()
129
+ }
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Integration with Retrofit
135
+
136
+ ```kotlin
137
+ // ✅ Use kotlinx-serialization converter — not Gson
138
+ dependencies {
139
+ implementation(libs.retrofit.kotlinx.serialization)
140
+ }
141
+
142
+ val retrofit = Retrofit.Builder()
143
+ .baseUrl(baseUrl)
144
+ .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
145
+ .build()
146
+ ```
147
+
148
+ ## Integration with Ktor
149
+
150
+ ```kotlin
151
+ // ✅ Use ContentNegotiation plugin
152
+ install(ContentNegotiation) {
153
+ json(json) // pass the shared Json instance
154
+ }
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Handling Unknown/Dynamic Fields
160
+
161
+ ```kotlin
162
+ // ✅ Use JsonObject for truly dynamic content
163
+ @Serializable
164
+ data class WebhookDto(
165
+ val event: String,
166
+ val payload: JsonObject // dynamic structure
167
+ )
168
+
169
+ // Extract typed value from JsonObject
170
+ val userId = webhook.payload["user_id"]?.jsonPrimitive?.content
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Anti-Patterns
176
+
177
+ - Using Gson or Moshi — not KMP-compatible, less type-safe
178
+ - `@Serializable` on domain models — couples domain to transport layer
179
+ - Creating `Json {}` inline at call site — inconsistent configuration
180
+ - Missing `@SerialName` when API uses snake_case — breaks deserialization
181
+ - Using `Any` or `Map<String, Any>` — loses type safety; use `JsonObject`
182
+ - Ignoring `ignoreUnknownKeys = true` — crashes on API evolution
183
+
184
+ ---
185
+
186
+ ## Related Skills
187
+
188
+ - `dto-mapping` — mapping between DTOs and domain models
189
+ - `retrofit` — Retrofit setup with serialization converter
190
+ - `ktor` — Ktor client setup with serialization plugin
191
+ - `kmp` — shared serialization setup in multiplatform projects