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,224 @@
1
+ ---
2
+ name: mvi
3
+ description: >
4
+ Model-View-Intent pattern for Android with Jetpack Compose.
5
+ Load this skill when implementing strict unidirectional data flow,
6
+ modeling all user interactions as Intents, using a single immutable
7
+ state per screen, or managing side effects as Effects separate from state.
8
+ ---
9
+
10
+ # MVI
11
+
12
+ ## Overview
13
+
14
+ MVI enforces strict unidirectional data flow: the View emits **Intents**, the ViewModel processes them and produces a new immutable **State**, and one-time **Effects** handle side effects like navigation or toasts. Unlike MVVM, all user interactions are modeled as a sealed `Intent` class — nothing is called imperatively.
15
+
16
+ ---
17
+
18
+ ## Core Principles
19
+
20
+ - **One immutable State** per screen — never partial updates
21
+ - All user actions are **Intents** — sealed class, no direct function calls from UI
22
+ - **Effects** are one-time events (navigation, toast) — separate from State
23
+ - State transitions are **pure** — `reduce(currentState, intent) → newState`
24
+ - ViewModel processes intents sequentially — no race conditions on state
25
+
26
+ ---
27
+
28
+ ## Contract (State + Intent + Effect)
29
+
30
+ ```kotlin
31
+ // ✅ Define the full screen contract in one place
32
+ object UserListContract {
33
+
34
+ data class State(
35
+ val isLoading: Boolean = false,
36
+ val users: List<User> = emptyList(),
37
+ val error: String? = null,
38
+ val searchQuery: String = ""
39
+ )
40
+
41
+ sealed interface Intent {
42
+ data object LoadUsers : Intent
43
+ data class SearchChanged(val query: String) : Intent
44
+ data class UserClicked(val userId: String) : Intent
45
+ data class DeleteUser(val userId: String) : Intent
46
+ }
47
+
48
+ sealed interface Effect {
49
+ data class NavigateToDetail(val userId: String) : Effect
50
+ data class ShowSnackbar(val message: String) : Effect
51
+ }
52
+ }
53
+ ```
54
+
55
+ ---
56
+
57
+ ## ViewModel
58
+
59
+ ```kotlin
60
+ // ✅ MVI ViewModel — processes intents, emits state and effects
61
+ @HiltViewModel
62
+ class UserListViewModel @Inject constructor(
63
+ private val getUsersUseCase: GetUsersUseCase,
64
+ private val deleteUserUseCase: DeleteUserUseCase
65
+ ) : ViewModel() {
66
+
67
+ private val _state = MutableStateFlow(UserListContract.State())
68
+ val state: StateFlow<UserListContract.State> = _state.asStateFlow()
69
+
70
+ private val _effects = Channel<UserListContract.Effect>(Channel.BUFFERED)
71
+ val effects: Flow<UserListContract.Effect> = _effects.receiveAsFlow()
72
+
73
+ fun handleIntent(intent: UserListContract.Intent) {
74
+ when (intent) {
75
+ is UserListContract.Intent.LoadUsers -> loadUsers()
76
+ is UserListContract.Intent.SearchChanged -> onSearchChanged(intent.query)
77
+ is UserListContract.Intent.UserClicked -> onUserClicked(intent.userId)
78
+ is UserListContract.Intent.DeleteUser -> deleteUser(intent.userId)
79
+ }
80
+ }
81
+
82
+ private fun loadUsers() {
83
+ viewModelScope.launch {
84
+ _state.update { it.copy(isLoading = true, error = null) }
85
+ getUsersUseCase().fold(
86
+ onSuccess = { users ->
87
+ _state.update { it.copy(isLoading = false, users = users) }
88
+ },
89
+ onFailure = { error ->
90
+ _state.update { it.copy(isLoading = false, error = error.message) }
91
+ }
92
+ )
93
+ }
94
+ }
95
+
96
+ private fun onSearchChanged(query: String) {
97
+ _state.update { it.copy(searchQuery = query) }
98
+ }
99
+
100
+ private fun onUserClicked(userId: String) {
101
+ viewModelScope.launch {
102
+ _effects.send(UserListContract.Effect.NavigateToDetail(userId))
103
+ }
104
+ }
105
+
106
+ private fun deleteUser(userId: String) {
107
+ viewModelScope.launch {
108
+ deleteUserUseCase(userId).fold(
109
+ onSuccess = {
110
+ loadUsers()
111
+ _effects.send(UserListContract.Effect.ShowSnackbar("User deleted"))
112
+ },
113
+ onFailure = {
114
+ _effects.send(UserListContract.Effect.ShowSnackbar("Delete failed"))
115
+ }
116
+ )
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ ---
123
+
124
+ ## Composable
125
+
126
+ ```kotlin
127
+ // ✅ View sends intents — never calls ViewModel functions directly
128
+ @Composable
129
+ fun UserListScreen(
130
+ onNavigateToDetail: (String) -> Unit,
131
+ viewModel: UserListViewModel = hiltViewModel()
132
+ ) {
133
+ val state by viewModel.state.collectAsStateWithLifecycle()
134
+ val snackbarHostState = remember { SnackbarHostState() }
135
+
136
+ // ✅ Collect one-time effects
137
+ LaunchedEffect(Unit) {
138
+ viewModel.effects.collect { effect ->
139
+ when (effect) {
140
+ is UserListContract.Effect.NavigateToDetail ->
141
+ onNavigateToDetail(effect.userId)
142
+ is UserListContract.Effect.ShowSnackbar ->
143
+ snackbarHostState.showSnackbar(effect.message)
144
+ }
145
+ }
146
+ }
147
+
148
+ // ✅ Trigger initial load via intent
149
+ LaunchedEffect(Unit) {
150
+ viewModel.handleIntent(UserListContract.Intent.LoadUsers)
151
+ }
152
+
153
+ Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) }) { padding ->
154
+ UserListContent(
155
+ state = state,
156
+ onIntent = viewModel::handleIntent,
157
+ modifier = Modifier.padding(padding)
158
+ )
159
+ }
160
+ }
161
+
162
+ // ✅ Content composable receives state and onIntent — no ViewModel reference
163
+ @Composable
164
+ private fun UserListContent(
165
+ state: UserListContract.State,
166
+ onIntent: (UserListContract.Intent) -> Unit,
167
+ modifier: Modifier = Modifier
168
+ ) {
169
+ Column(modifier = modifier) {
170
+ SearchBar(
171
+ query = state.searchQuery,
172
+ onQueryChange = { onIntent(UserListContract.Intent.SearchChanged(it)) }
173
+ )
174
+
175
+ when {
176
+ state.isLoading -> LoadingState()
177
+ state.error != null -> ErrorState(
178
+ message = state.error,
179
+ onRetry = { onIntent(UserListContract.Intent.LoadUsers) }
180
+ )
181
+ state.users.isEmpty() -> EmptyState()
182
+ else -> UserList(
183
+ users = state.users,
184
+ onUserClick = { onIntent(UserListContract.Intent.UserClicked(it)) },
185
+ onDeleteClick = { onIntent(UserListContract.Intent.DeleteUser(it)) }
186
+ )
187
+ }
188
+ }
189
+ }
190
+ ```
191
+
192
+ ---
193
+
194
+ ## MVI vs MVVM — When to Use Each
195
+
196
+ | Concern | MVVM | MVI |
197
+ | -------------- | ---------------------------- | -------------------------------------- |
198
+ | Complexity | Simple to medium screens | Complex screens with many interactions |
199
+ | State tracing | Multiple StateFlows possible | Single immutable State — easy to trace |
200
+ | Testability | Test individual functions | Test intent → state transitions |
201
+ | Boilerplate | Less | More (Contract class) |
202
+ | Predictability | Good | Excellent |
203
+
204
+ Use **MVVM** as the default. Reach for **MVI** when a screen has many concurrent interactions and state bugs are hard to trace.
205
+
206
+ ---
207
+
208
+ ## Anti-Patterns
209
+
210
+ - Calling ViewModel functions directly from UI — use `handleIntent()`
211
+ - Mutable state fields inside the State data class — keep State fully immutable
212
+ - Sending navigation as State change — navigation is an Effect, not State
213
+ - Multiple state flows per screen — one `StateFlow<State>` only
214
+ - Processing intents with side effects inside `reduce()` — `reduce()` must be pure if used
215
+
216
+ ---
217
+
218
+ ## Related Skills
219
+
220
+ - `mvvm` — simpler alternative for straightforward screens
221
+ - `unidirectional-data-flow` — the underlying pattern MVI is built on
222
+ - `state-management` — state patterns and tools
223
+ - `side-effect-management` — handling effects correctly
224
+ - `compose` — collecting state in Compose UI
@@ -0,0 +1,198 @@
1
+ ---
2
+ name: mvvm
3
+ description: >
4
+ Model-View-ViewModel pattern for Android with Jetpack Compose.
5
+ Load this skill when structuring a screen with ViewModel, defining
6
+ UI state, handling user events, managing one-time side effects,
7
+ or wiring ViewModel to Compose UI.
8
+ ---
9
+
10
+ # MVVM
11
+
12
+ ## Overview
13
+
14
+ MVVM separates UI (View) from business logic (ViewModel) via observable state. In Compose, the ViewModel exposes `StateFlow` for UI state and a `Channel` or `SharedFlow` for one-time events. The composable observes state and delegates all actions to the ViewModel.
15
+
16
+ ---
17
+
18
+ ## Core Principles
19
+
20
+ - ViewModel exposes **one UI state** as `StateFlow` — never multiple disconnected flags
21
+ - One-time events (navigation, toasts) go through a `Channel` — not `StateFlow`
22
+ - Composables are **stateless** — they receive state and emit events only
23
+ - ViewModel has **no reference** to View, Context, or composable
24
+ - User actions are modeled as a sealed `UiEvent` class — not individual functions for simple screens
25
+
26
+ ---
27
+
28
+ ## UI State Model
29
+
30
+ ```kotlin
31
+ // ✅ Single sealed state per screen
32
+ sealed interface UserDetailUiState {
33
+ data object Loading : UserDetailUiState
34
+ data class Success(val user: User) : UserDetailUiState
35
+ data class Error(val message: String) : UserDetailUiState
36
+ }
37
+
38
+ // ✅ Or data class for screens with multiple fields
39
+ data class UserListUiState(
40
+ val isLoading: Boolean = false,
41
+ val users: List<User> = emptyList(),
42
+ val error: String? = null,
43
+ val searchQuery: String = ""
44
+ )
45
+ ```
46
+
47
+ ---
48
+
49
+ ## One-Time Events
50
+
51
+ ```kotlin
52
+ // ✅ Events that happen once — not persistent state
53
+ sealed interface UserDetailEvent {
54
+ data object NavigateBack : UserDetailEvent
55
+ data class ShowToast(val message: String) : UserDetailEvent
56
+ data class NavigateToEdit(val userId: String) : UserDetailEvent
57
+ }
58
+ ```
59
+
60
+ ---
61
+
62
+ ## ViewModel
63
+
64
+ ```kotlin
65
+ // ✅ Standard ViewModel structure
66
+ @HiltViewModel
67
+ class UserDetailViewModel @Inject constructor(
68
+ savedStateHandle: SavedStateHandle,
69
+ private val getUserUseCase: GetUserUseCase,
70
+ private val deleteUserUseCase: DeleteUserUseCase
71
+ ) : ViewModel() {
72
+
73
+ private val userId: String = checkNotNull(savedStateHandle["userId"])
74
+
75
+ // UI state
76
+ private val _state = MutableStateFlow<UserDetailUiState>(UserDetailUiState.Loading)
77
+ val state: StateFlow<UserDetailUiState> = _state.asStateFlow()
78
+
79
+ // One-time events
80
+ private val _events = Channel<UserDetailEvent>(Channel.BUFFERED)
81
+ val events: Flow<UserDetailEvent> = _events.receiveAsFlow()
82
+
83
+ init {
84
+ loadUser()
85
+ }
86
+
87
+ fun loadUser() {
88
+ viewModelScope.launch {
89
+ _state.value = UserDetailUiState.Loading
90
+ getUserUseCase(userId).fold(
91
+ onSuccess = { _state.value = UserDetailUiState.Success(it) },
92
+ onFailure = { _state.value = UserDetailUiState.Error(it.message ?: "Error") }
93
+ )
94
+ }
95
+ }
96
+
97
+ fun onDeleteClick() {
98
+ viewModelScope.launch {
99
+ deleteUserUseCase(userId).fold(
100
+ onSuccess = { _events.send(UserDetailEvent.NavigateBack) },
101
+ onFailure = { _events.send(UserDetailEvent.ShowToast("Delete failed")) }
102
+ )
103
+ }
104
+ }
105
+
106
+ fun onEditClick() {
107
+ viewModelScope.launch {
108
+ _events.send(UserDetailEvent.NavigateToEdit(userId))
109
+ }
110
+ }
111
+ }
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Composable
117
+
118
+ ```kotlin
119
+ // ✅ Stateless composable — observes state, delegates actions
120
+ @Composable
121
+ fun UserDetailScreen(
122
+ onNavigateBack: () -> Unit,
123
+ onNavigateToEdit: (String) -> Unit,
124
+ viewModel: UserDetailViewModel = hiltViewModel()
125
+ ) {
126
+ val state by viewModel.state.collectAsStateWithLifecycle()
127
+ val context = LocalContext.current
128
+
129
+ // ✅ Collect one-time events
130
+ LaunchedEffect(Unit) {
131
+ viewModel.events.collect { event ->
132
+ when (event) {
133
+ is UserDetailEvent.NavigateBack -> onNavigateBack()
134
+ is UserDetailEvent.ShowToast -> Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show()
135
+ is UserDetailEvent.NavigateToEdit -> onNavigateToEdit(event.userId)
136
+ }
137
+ }
138
+ }
139
+
140
+ UserDetailContent(
141
+ state = state,
142
+ onDeleteClick = viewModel::onDeleteClick,
143
+ onEditClick = viewModel::onEditClick,
144
+ onRetryClick = viewModel::loadUser
145
+ )
146
+ }
147
+
148
+ // ✅ Pure content composable — no ViewModel reference
149
+ @Composable
150
+ private fun UserDetailContent(
151
+ state: UserDetailUiState,
152
+ onDeleteClick: () -> Unit,
153
+ onEditClick: () -> Unit,
154
+ onRetryClick: () -> Unit
155
+ ) {
156
+ when (state) {
157
+ is UserDetailUiState.Loading -> LoadingState()
158
+ is UserDetailUiState.Success -> UserDetailBody(state.user, onDeleteClick, onEditClick)
159
+ is UserDetailUiState.Error -> ErrorState(state.message, onRetryClick)
160
+ }
161
+ }
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Shared ViewModel (Between Screens)
167
+
168
+ ```kotlin
169
+ // ✅ Shared ViewModel scoped to NavBackStackEntry
170
+ @Composable
171
+ fun ParentScreen(navController: NavController) {
172
+ val parentEntry = remember(navController) {
173
+ navController.getBackStackEntry(ParentRoute)
174
+ }
175
+ val sharedViewModel: SharedViewModel = hiltViewModel(parentEntry)
176
+ }
177
+ ```
178
+
179
+ ---
180
+
181
+ ## Anti-Patterns
182
+
183
+ - Multiple `StateFlow` properties for one screen — use a single state class
184
+ - Using `StateFlow` for navigation events — use `Channel` instead (won't replay)
185
+ - Passing `Context` into ViewModel — use `ApplicationContext` only when necessary via `@ApplicationContext`
186
+ - Calling `viewModel.someFlow.collect {}` without `collectAsStateWithLifecycle` in Compose
187
+ - Business logic inside composables — belongs in ViewModel or use case
188
+ - ViewModel referencing a specific composable or Fragment
189
+
190
+ ---
191
+
192
+ ## Related Skills
193
+
194
+ - `clean-architecture` — layer structure ViewModel sits within
195
+ - `state-management` — advanced state patterns
196
+ - `side-effect-management` — handling side effects from ViewModel
197
+ - `use-case-design` — what the ViewModel delegates to
198
+ - `savedstatehandle` — persisting ViewModel state across process death
@@ -0,0 +1,194 @@
1
+ ---
2
+ name: modularization
3
+ description: >
4
+ App modularization strategy for Android projects.
5
+ Load this skill when deciding how to split a project into modules,
6
+ defining module types and responsibilities, managing module dependencies,
7
+ or planning build performance improvements through modularization.
8
+ ---
9
+
10
+ # Modularization
11
+
12
+ ## Overview
13
+ Modularization splits the app into independent Gradle modules. Each module has a single responsibility, clear API boundaries, and explicit dependencies. This improves build times (parallel compilation, build cache), enforces architecture boundaries, and enables code reuse across features.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Modules depend **inward** — feature modules never depend on other feature modules
20
+ - Each module exposes a **minimal public API** — use `internal` for implementation details
21
+ - **No circular dependencies** between modules
22
+ - Core/shared modules are **stable** — feature modules are volatile
23
+ - Module boundaries enforce **architecture layers** — a `data` module cannot import a `ui` module
24
+
25
+ ---
26
+
27
+ ## Module Types
28
+
29
+ ```
30
+ :app — entry point, wires everything together
31
+ :core:ui — shared Compose components, theme, design system
32
+ :core:domain — shared domain models and interfaces
33
+ :core:data — shared repository implementations, database, network
34
+ :core:common — utilities, extensions, base classes
35
+ :feature:users — users feature (presentation + domain + data for this feature)
36
+ :feature:products — products feature
37
+ :feature:settings — settings feature
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Recommended Structure
43
+
44
+ ```
45
+ app/
46
+ ├── app/ # :app — Application, MainActivity, DI root
47
+ ├── core/
48
+ │ ├── ui/ # :core:ui — Theme, shared components
49
+ │ ├── domain/ # :core:domain — Shared models, base UseCases
50
+ │ ├── data/ # :core:data — Network, DB, shared repos
51
+ │ ├── common/ # :core:common — Extensions, utils, Result
52
+ │ └── testing/ # :core:testing — Fakes, test utilities
53
+ └── feature/
54
+ ├── users/ # :feature:users
55
+ │ ├── src/main/
56
+ │ │ ├── presentation/ # Screen, ViewModel, UiState
57
+ │ │ ├── domain/ # Feature-specific UseCases
58
+ │ │ └── data/ # Feature-specific Repository
59
+ │ └── build.gradle.kts
60
+ └── products/ # :feature:products
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Dependency Graph
66
+
67
+ ```
68
+ :app
69
+ ├── :feature:users
70
+ │ ├── :core:domain
71
+ │ ├── :core:data
72
+ │ └── :core:ui
73
+ ├── :feature:products
74
+ │ ├── :core:domain
75
+ │ ├── :core:data
76
+ │ └── :core:ui
77
+ └── :core:common
78
+
79
+ # ✅ Allowed
80
+ :feature:users → :core:domain
81
+ :feature:users → :core:ui
82
+ :app → :feature:users
83
+
84
+ # ❌ Not allowed
85
+ :feature:users → :feature:products # feature-to-feature dependency
86
+ :core:data → :feature:users # core depending on feature
87
+ :core:ui → :core:data # ui depending on data
88
+ ```
89
+
90
+ ---
91
+
92
+ ## build.gradle.kts per Module
93
+
94
+ ```kotlin
95
+ // ✅ Feature module build file
96
+ plugins {
97
+ alias(libs.plugins.android.library)
98
+ alias(libs.plugins.kotlin.android)
99
+ alias(libs.plugins.hilt)
100
+ alias(libs.plugins.ksp)
101
+ }
102
+
103
+ android {
104
+ namespace = "com.example.feature.users"
105
+ // shared config via convention plugin
106
+ }
107
+
108
+ dependencies {
109
+ implementation(project(":core:domain"))
110
+ implementation(project(":core:data"))
111
+ implementation(project(":core:ui"))
112
+ implementation(project(":core:common"))
113
+
114
+ implementation(libs.hilt.android)
115
+ ksp(libs.hilt.compiler)
116
+
117
+ testImplementation(project(":core:testing"))
118
+ }
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Navigation Between Modules
124
+
125
+ ```kotlin
126
+ // ✅ Feature modules expose navigation routes as constants
127
+ // :feature:users
128
+ object UsersNavigation {
129
+ const val ROUTE = "users"
130
+ const val USER_DETAIL_ROUTE = "users/{userId}"
131
+
132
+ fun userDetailRoute(userId: String) = "users/$userId"
133
+ }
134
+
135
+ // ✅ :app wires navigation — features don't know about each other
136
+ @Composable
137
+ fun AppNavHost(navController: NavHostController) {
138
+ NavHost(navController = navController, startDestination = UsersNavigation.ROUTE) {
139
+ usersNavGraph(navController)
140
+ productsNavGraph(navController)
141
+ }
142
+ }
143
+
144
+ // ✅ Each feature exposes a NavGraph extension
145
+ fun NavGraphBuilder.usersNavGraph(navController: NavController) {
146
+ navigation(
147
+ startDestination = UsersNavigation.ROUTE,
148
+ route = "users_graph"
149
+ ) {
150
+ composable(UsersNavigation.ROUTE) { UserListScreen(navController) }
151
+ composable(UsersNavigation.USER_DETAIL_ROUTE) { UserDetailScreen(navController) }
152
+ }
153
+ }
154
+ ```
155
+
156
+ ---
157
+
158
+ ## Visibility Control
159
+
160
+ ```kotlin
161
+ // ✅ Use internal to hide implementation from other modules
162
+ internal class UserRepositoryImpl @Inject constructor(...) : UserRepository
163
+
164
+ // ✅ Only expose what other modules need
165
+ class GetUserUseCase @Inject constructor(...) // public — used by presentation
166
+ internal class UserRemoteDataSource(...) // internal — implementation detail
167
+
168
+ // ✅ Hilt: internal classes need explicit binding
169
+ @Module
170
+ @InstallIn(SingletonComponent::class)
171
+ internal abstract class UserModule {
172
+ @Binds
173
+ abstract fun bindUserRepository(impl: UserRepositoryImpl): UserRepository
174
+ }
175
+ ```
176
+
177
+ ---
178
+
179
+ ## Anti-Patterns
180
+
181
+ - Feature modules depending on each other — use `:app` or a shared `:core` module to mediate
182
+ - Putting everything in `:core:common` — keep common truly generic; feature logic belongs in features
183
+ - Circular dependencies — will cause Gradle build failure
184
+ - Public classes that should be `internal` — leaks implementation details
185
+ - One mega-module — defeats purpose of modularization; build times won't improve
186
+
187
+ ---
188
+
189
+ ## Related Skills
190
+ - `multi-module-architecture` — detailed multi-module setup and advanced patterns
191
+ - `hilt` — Hilt setup across modules
192
+ - `gradle` — Gradle configuration for multi-module projects
193
+ - `convention-plugin` — sharing build configuration across modules
194
+ - `clean-architecture` — layer boundaries that modularization enforces