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,200 @@
1
+ ---
2
+ name: coroutine
3
+ description: >
4
+ Kotlin Coroutines fundamentals and patterns for Android development.
5
+ Load this skill when launching coroutines, choosing the right scope,
6
+ managing coroutine lifecycle, handling exceptions, or structuring
7
+ async operations with suspend functions.
8
+ ---
9
+
10
+ # Coroutine
11
+
12
+ ## Overview
13
+
14
+ Kotlin Coroutines provide a structured way to write asynchronous code sequentially. On Android, coroutines are the standard for all async operations — network calls, database queries, and background work. Every coroutine must be launched in a scope that controls its lifecycle.
15
+
16
+ ---
17
+
18
+ ## Core Principles
19
+
20
+ - Always use the **narrowest scope** available — `viewModelScope`, `lifecycleScope`, not `GlobalScope`
21
+ - Use `Dispatchers.IO` for blocking I/O, `Dispatchers.Default` for CPU-heavy work
22
+ - Never use `GlobalScope` — it has no lifecycle and can't be cancelled
23
+ - Exceptions in coroutines must be handled explicitly — they don't propagate like regular exceptions
24
+ - Use `supervisorScope` when child failures should not cancel siblings
25
+
26
+ ---
27
+
28
+ ## Scope Selection
29
+
30
+ ```kotlin
31
+ // ✅ viewModelScope — cancelled when ViewModel is cleared
32
+ class UserViewModel : ViewModel() {
33
+ fun loadUser() {
34
+ viewModelScope.launch {
35
+ // auto-cancelled when ViewModel is destroyed
36
+ }
37
+ }
38
+ }
39
+
40
+ // ✅ lifecycleScope — cancelled when lifecycle is destroyed
41
+ class UserFragment : Fragment() {
42
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
43
+ viewLifecycleOwner.lifecycleScope.launch {
44
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
45
+ // cancelled when below STARTED
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ // ✅ Custom scope — for non-lifecycle components
52
+ @Singleton
53
+ class SyncManager @Inject constructor() {
54
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
55
+
56
+ fun startSync() {
57
+ scope.launch { /* ... */ }
58
+ }
59
+
60
+ fun cancel() {
61
+ scope.cancel()
62
+ }
63
+ }
64
+
65
+ // ❌ Never use GlobalScope
66
+ GlobalScope.launch { /* no lifecycle, no cancellation */ }
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Dispatcher Selection
72
+
73
+ ```kotlin
74
+ // ✅ IO — network, database, file operations
75
+ viewModelScope.launch(Dispatchers.IO) {
76
+ val users = userDao.getAll()
77
+ }
78
+
79
+ // ✅ Default — CPU-intensive work (sorting, parsing, computation)
80
+ viewModelScope.launch(Dispatchers.Default) {
81
+ val sorted = largeList.sortedBy { it.name }
82
+ }
83
+
84
+ // ✅ Main — UI updates (usually implicit in viewModelScope)
85
+ viewModelScope.launch(Dispatchers.Main) {
86
+ binding.textView.text = "Updated"
87
+ }
88
+
89
+ // ✅ withContext — switch dispatcher inside a coroutine
90
+ suspend fun processData(): List<Item> = withContext(Dispatchers.Default) {
91
+ rawData.map { parse(it) }
92
+ }
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Exception Handling
98
+
99
+ ```kotlin
100
+ // ✅ try/catch inside launch
101
+ viewModelScope.launch {
102
+ try {
103
+ val user = repository.getUser(id)
104
+ _state.value = UiState.Success(user)
105
+ } catch (e: Exception) {
106
+ _state.value = UiState.Error(e.message ?: "Error")
107
+ }
108
+ }
109
+
110
+ // ✅ CoroutineExceptionHandler for unhandled exceptions
111
+ val handler = CoroutineExceptionHandler { _, throwable ->
112
+ Timber.e(throwable, "Unhandled coroutine exception")
113
+ }
114
+
115
+ viewModelScope.launch(handler) {
116
+ riskyOperation()
117
+ }
118
+
119
+ // ✅ runCatching — functional style
120
+ val result = runCatching { repository.getUser(id) }
121
+ result.onSuccess { _state.value = UiState.Success(it) }
122
+ result.onFailure { _state.value = UiState.Error(it.message ?: "Error") }
123
+
124
+ // ❌ async exception — not caught by try/catch on launch
125
+ val deferred = viewModelScope.async { riskyOperation() }
126
+ deferred.await() // exception thrown here — wrap in try/catch
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Parallel Execution
132
+
133
+ ```kotlin
134
+ // ✅ Run multiple operations in parallel with async
135
+ suspend fun loadDashboard(): Dashboard {
136
+ return coroutineScope {
137
+ val users = async { userRepository.getUsers() }
138
+ val orders = async { orderRepository.getOrders() }
139
+ val stats = async { statsRepository.getStats() }
140
+
141
+ Dashboard(
142
+ users = users.await().getOrDefault(emptyList()),
143
+ orders = orders.await().getOrDefault(emptyList()),
144
+ stats = stats.await().getOrNull()
145
+ )
146
+ }
147
+ }
148
+
149
+ // ✅ supervisorScope — one failure doesn't cancel others
150
+ suspend fun loadOptionalData() = supervisorScope {
151
+ val primary = async { primaryRepository.getData() }
152
+ val secondary = async { secondaryRepository.getData() } // failure here won't cancel primary
153
+
154
+ PrimaryData(
155
+ main = primary.await(),
156
+ extra = runCatching { secondary.await() }.getOrNull()
157
+ )
158
+ }
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Cancellation
164
+
165
+ ```kotlin
166
+ // ✅ Check cancellation in long loops
167
+ suspend fun processItems(items: List<Item>) {
168
+ items.forEach { item ->
169
+ ensureActive() // throws CancellationException if cancelled
170
+ processItem(item)
171
+ }
172
+ }
173
+
174
+ // ✅ withTimeout
175
+ suspend fun fetchWithTimeout(): User = withTimeout(5_000) {
176
+ api.getUser(id)
177
+ }
178
+
179
+ // ✅ withTimeoutOrNull — returns null on timeout
180
+ val user = withTimeoutOrNull(5_000) { api.getUser(id) }
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Anti-Patterns
186
+
187
+ - Using `GlobalScope` — no lifecycle management, can't be cancelled
188
+ - Launching coroutines without handling exceptions — silent failures
189
+ - Calling blocking functions (`Thread.sleep`, blocking I/O) without `Dispatchers.IO`
190
+ - Using `runBlocking` in Android code outside of tests — blocks the thread
191
+ - Ignoring `CancellationException` in catch blocks — breaks structured concurrency
192
+
193
+ ---
194
+
195
+ ## Related Skills
196
+
197
+ - `flow` — reactive streams with coroutines
198
+ - `structured-concurrency` — parent-child coroutine relationships
199
+ - `viewmodel` — viewModelScope usage
200
+ - `lifecycle` — lifecycleScope and repeatOnLifecycle
@@ -0,0 +1,179 @@
1
+ ---
2
+ name: flow
3
+ description: >
4
+ Kotlin Flow for reactive streams in Android.
5
+ Load this skill when building cold streams, transforming data pipelines,
6
+ collecting flows with lifecycle awareness, combining multiple flows,
7
+ or converting callbacks to Flow.
8
+ ---
9
+
10
+ # Flow
11
+
12
+ ## Overview
13
+
14
+ Kotlin Flow is a cold, asynchronous stream built on coroutines. It emits values sequentially and is collected in a coroutine. Flow is the standard for reactive data streams in Android — from database queries to UI state. Unlike LiveData, Flow is lifecycle-agnostic and must be collected with lifecycle awareness in the UI layer.
15
+
16
+ ---
17
+
18
+ ## Core Principles
19
+
20
+ - Flow is **cold** — it only runs when collected
21
+ - Collect flows in the UI with `collectAsStateWithLifecycle` (Compose) or `repeatOnLifecycle`
22
+ - Use `flowOn` to switch the dispatcher for upstream operators — not for collection
23
+ - Prefer `StateFlow` for UI state and `SharedFlow` for events — not plain `Flow`
24
+ - Use `callbackFlow` to bridge callback-based APIs to Flow
25
+
26
+ ---
27
+
28
+ ## Basic Flow
29
+
30
+ ```kotlin
31
+ // ✅ Cold flow — runs fresh for each collector
32
+ fun getUsers(): Flow<List<User>> = flow {
33
+ while (true) {
34
+ emit(repository.getUsers())
35
+ delay(30_000) // poll every 30s
36
+ }
37
+ }
38
+
39
+ // ✅ Flow from Room — already a Flow
40
+ fun getUsersFromDb(): Flow<List<User>> = userDao.getAllUsers() // Room emits on every change
41
+ ```
42
+
43
+ ---
44
+
45
+ ## Flow Operators
46
+
47
+ ```kotlin
48
+ // ✅ Transform
49
+ val userNames: Flow<List<String>> = getUsersFlow()
50
+ .map { users -> users.map { it.name } }
51
+
52
+ // ✅ Filter
53
+ val activeUsers: Flow<List<User>> = getUsersFlow()
54
+ .map { users -> users.filter { it.isActive } }
55
+
56
+ // ✅ debounce — wait for stable value
57
+ val searchResults: Flow<List<User>> = searchQuery
58
+ .debounce(300)
59
+ .distinctUntilChanged()
60
+ .flatMapLatest { query -> searchRepository.search(query) }
61
+
62
+ // ✅ combine — merge two flows into one
63
+ val uiState: Flow<UiState> = combine(usersFlow, filtersFlow) { users, filters ->
64
+ UiState(users = users.filter { filters.matches(it) })
65
+ }
66
+
67
+ // ✅ zip — pair emissions one-to-one
68
+ val paired: Flow<Pair<User, Order>> = usersFlow.zip(ordersFlow) { user, order ->
69
+ user to order
70
+ }
71
+
72
+ // ✅ flatMapLatest — cancel previous and switch to latest
73
+ val results: Flow<List<Result>> = queryFlow
74
+ .flatMapLatest { query -> searchUseCase(query) }
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Error Handling
80
+
81
+ ```kotlin
82
+ // ✅ catch — handle errors in the stream
83
+ getUsersFlow()
84
+ .catch { error ->
85
+ emit(emptyList()) // emit fallback
86
+ Timber.e(error)
87
+ }
88
+ .collect { users -> render(users) }
89
+
90
+ // ✅ retry
91
+ getUsersFlow()
92
+ .retry(3) { cause -> cause is IOException }
93
+ .collect { users -> render(users) }
94
+
95
+ // ✅ onEach for side effects
96
+ getUsersFlow()
97
+ .onEach { users -> Timber.d("Received ${users.size} users") }
98
+ .catch { Timber.e(it) }
99
+ .collect { render(it) }
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Lifecycle-Aware Collection
105
+
106
+ ```kotlin
107
+ // ✅ Compose — collectAsStateWithLifecycle
108
+ @Composable
109
+ fun UserListScreen(viewModel: UserListViewModel = hiltViewModel()) {
110
+ val state by viewModel.state.collectAsStateWithLifecycle()
111
+ UserListContent(state)
112
+ }
113
+
114
+ // ✅ Fragment — repeatOnLifecycle
115
+ viewLifecycleOwner.lifecycleScope.launch {
116
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
117
+ viewModel.state.collect { render(it) }
118
+ }
119
+ }
120
+
121
+ // ❌ Never collect without lifecycle awareness in UI
122
+ lifecycleScope.launch {
123
+ viewModel.state.collect { render(it) } // doesn't cancel when backgrounded
124
+ }
125
+ ```
126
+
127
+ ---
128
+
129
+ ## callbackFlow — Bridge Callbacks to Flow
130
+
131
+ ```kotlin
132
+ // ✅ Convert callback API to Flow
133
+ fun listenToLocation(): Flow<Location> = callbackFlow {
134
+ val callback = object : LocationCallback() {
135
+ override fun onLocationResult(result: LocationResult) {
136
+ result.lastLocation?.let { trySend(it) }
137
+ }
138
+ }
139
+
140
+ locationManager.requestUpdates(callback)
141
+
142
+ awaitClose {
143
+ locationManager.removeUpdates(callback) // cleanup on cancellation
144
+ }
145
+ }
146
+ ```
147
+
148
+ ---
149
+
150
+ ## flowOn — Upstream Dispatcher
151
+
152
+ ```kotlin
153
+ // ✅ flowOn changes dispatcher for everything upstream of it
154
+ val processedData: Flow<List<Item>> = rawDataFlow
155
+ .map { parseHeavy(it) } // runs on Default
156
+ .filter { it.isValid } // runs on Default
157
+ .flowOn(Dispatchers.Default) // applies to map + filter above
158
+ .map { it.toUiModel() } // runs on collection dispatcher
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Anti-Patterns
164
+
165
+ - Collecting flow without lifecycle awareness in Android UI — subscribes even in background
166
+ - Using `flow { }` for hot data that should be `StateFlow` or `SharedFlow`
167
+ - Calling `.collect {}` directly in a composable body — use `collectAsStateWithLifecycle`
168
+ - Using `flatMapMerge` for search/autocomplete — use `flatMapLatest` to cancel previous
169
+ - Not calling `awaitClose` in `callbackFlow` — resource leak on cancellation
170
+
171
+ ---
172
+
173
+ ## Related Skills
174
+
175
+ - `stateflow` — hot state holder for UI state
176
+ - `sharedflow` — hot shared stream for events
177
+ - `coroutine` — coroutine fundamentals
178
+ - `lifecycle` — lifecycle-aware collection patterns
179
+ - `reactive-streams` — broader reactive programming concepts
@@ -0,0 +1,185 @@
1
+ ---
2
+ name: mutex-strategy
3
+ description: >
4
+ Mutex and coroutine-based locking strategies in Android.
5
+ Load this skill when protecting shared mutable state across coroutines,
6
+ preventing concurrent access to a critical section, serializing operations,
7
+ or avoiding race conditions in async code.
8
+ ---
9
+
10
+ # Mutex Strategy
11
+
12
+ ## Overview
13
+ `Mutex` is a coroutine-friendly mutual exclusion lock. Unlike Java's `synchronized`, it suspends rather than blocks the thread while waiting for the lock. It is the correct tool for protecting shared mutable state that multiple coroutines may access concurrently.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Use `Mutex` instead of `synchronized` in coroutine code — `synchronized` blocks the thread
20
+ - Keep the critical section **as short as possible** — don't do heavy work inside `withLock`
21
+ - Never `withLock` on the main dispatcher for blocking operations
22
+ - Use `Mutex` for single-resource protection — use `Semaphore` for limiting concurrency count
23
+ - Prefer immutable state + atomic updates where possible — avoid Mutex if you can
24
+
25
+ ---
26
+
27
+ ## Basic Mutex Usage
28
+
29
+ ```kotlin
30
+ // ✅ Protect shared mutable state
31
+ class TokenCache @Inject constructor() {
32
+ private val mutex = Mutex()
33
+ private var cachedToken: String? = null
34
+ private var tokenExpiry: Long = 0L
35
+
36
+ suspend fun getToken(fetchToken: suspend () -> String): String {
37
+ mutex.withLock {
38
+ val now = System.currentTimeMillis()
39
+ if (cachedToken != null && now < tokenExpiry) {
40
+ return cachedToken!!
41
+ }
42
+ val newToken = fetchToken()
43
+ cachedToken = newToken
44
+ tokenExpiry = now + 3_600_000L // 1 hour
45
+ return newToken
46
+ }
47
+ }
48
+
49
+ suspend fun invalidate() {
50
+ mutex.withLock {
51
+ cachedToken = null
52
+ tokenExpiry = 0L
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Token Refresh with Mutex
61
+
62
+ ```kotlin
63
+ // ✅ Prevent multiple simultaneous token refreshes
64
+ class AuthRepository @Inject constructor(
65
+ private val api: AuthApi,
66
+ private val tokenStorage: TokenStorage
67
+ ) {
68
+ private val refreshMutex = Mutex()
69
+
70
+ suspend fun refreshToken(): String? {
71
+ return refreshMutex.withLock {
72
+ // Check if another coroutine already refreshed while we were waiting
73
+ val currentToken = tokenStorage.getAccessToken()
74
+ if (currentToken != null && !isTokenExpired(currentToken)) {
75
+ return@withLock currentToken
76
+ }
77
+
78
+ runCatching {
79
+ val response = api.refresh(tokenStorage.getRefreshToken() ?: return@withLock null)
80
+ tokenStorage.saveTokens(response.accessToken, response.refreshToken)
81
+ response.accessToken
82
+ }.getOrNull()
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Semaphore — Limit Concurrency
91
+
92
+ ```kotlin
93
+ // ✅ Semaphore — allow max N concurrent operations
94
+ class ImageProcessor @Inject constructor() {
95
+ // Max 3 concurrent image processing operations
96
+ private val semaphore = Semaphore(3)
97
+
98
+ suspend fun processImage(imageUri: Uri): Bitmap {
99
+ return semaphore.withPermit {
100
+ heavyImageProcessing(imageUri)
101
+ }
102
+ }
103
+ }
104
+
105
+ // ✅ Rate-limited API calls
106
+ class ApiThrottler(maxConcurrent: Int = 5) {
107
+ private val semaphore = Semaphore(maxConcurrent)
108
+
109
+ suspend fun <T> throttled(call: suspend () -> T): T {
110
+ return semaphore.withPermit { call() }
111
+ }
112
+ }
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Mutex vs Other Synchronization
118
+
119
+ | Tool | Suspends? | Use For |
120
+ |------|-----------|---------|
121
+ | `Mutex` | ✅ Yes | Single-resource exclusive access |
122
+ | `Semaphore` | ✅ Yes | Limit number of concurrent operations |
123
+ | `synchronized` | ❌ Blocks thread | Non-coroutine Java interop only |
124
+ | `AtomicInteger` | N/A | Simple counter without lock |
125
+ | `StateFlow.update` | N/A | Atomic state updates |
126
+
127
+ ---
128
+
129
+ ## Atomic Alternatives
130
+
131
+ ```kotlin
132
+ // ✅ AtomicInteger for simple counters — no Mutex needed
133
+ class RequestCounter {
134
+ private val count = AtomicInteger(0)
135
+ fun increment() = count.incrementAndGet()
136
+ fun get() = count.get()
137
+ }
138
+
139
+ // ✅ StateFlow.update for UI state — thread-safe without Mutex
140
+ private val _state = MutableStateFlow(UiState())
141
+ fun updateUser(user: User) {
142
+ _state.update { it.copy(user = user) } // atomic — no Mutex needed
143
+ }
144
+ ```
145
+
146
+ ---
147
+
148
+ ## tryLock — Non-Blocking Attempt
149
+
150
+ ```kotlin
151
+ // ✅ tryLock — skip if already locked
152
+ class PeriodicSync @Inject constructor() {
153
+ private val syncMutex = Mutex()
154
+
155
+ suspend fun trySyncNow() {
156
+ if (!syncMutex.tryLock()) {
157
+ // sync already in progress — skip
158
+ return
159
+ }
160
+ try {
161
+ performSync()
162
+ } finally {
163
+ syncMutex.unlock()
164
+ }
165
+ }
166
+ }
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Anti-Patterns
172
+
173
+ - Using `synchronized {}` in coroutine code — blocks the thread, not coroutine-friendly
174
+ - Long operations inside `withLock` — holds the lock too long, blocks other coroutines
175
+ - Nested `withLock` calls on the same Mutex — deadlock
176
+ - Using Mutex for simple counter updates — use `AtomicInteger` instead
177
+ - Not releasing the lock in finally — use `withLock` extension, not manual lock/unlock
178
+
179
+ ---
180
+
181
+ ## Related Skills
182
+ - `coroutine` — coroutine fundamentals
183
+ - `structured-concurrency` — parent-child relationships and cancellation
184
+ - `synchronization-policy` — broader thread safety strategy
185
+ - `authentication` — token refresh with Mutex