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,210 @@
1
+ ---
2
+ name: state-restoration
3
+ description: >
4
+ Complete state restoration strategy for Android — covering UI state,
5
+ scroll position, form data, and Compose rememberSaveable.
6
+ Load this skill when deciding how to persist and restore UI state
7
+ across configuration changes and process death.
8
+ ---
9
+
10
+ # State Restoration
11
+
12
+ ## Overview
13
+
14
+ State restoration ensures the user sees a consistent UI after configuration changes (rotation) or returning after process death. Android provides multiple layers of restoration — from automatic (Compose rememberSaveable) to manual (SavedStateHandle). Choosing the right layer for each piece of state is the key design decision.
15
+
16
+ ---
17
+
18
+ ## Core Principles
19
+
20
+ - Match the **persistence layer** to the **state lifetime**
21
+ - UI-local state → `rememberSaveable`
22
+ - ViewModel state that must survive process death → `SavedStateHandle`
23
+ - Data loaded from network/DB → don't save, reload from repository
24
+ - Never duplicate state across multiple layers — one source of truth per state
25
+
26
+ ---
27
+
28
+ ## State Restoration Layers
29
+
30
+ | Layer | Survives Rotation | Survives Process Death | Use For |
31
+ | -------------------- | ----------------- | ---------------------- | ------------------------------------- |
32
+ | `remember` | ❌ | ❌ | Transient UI state (animation, focus) |
33
+ | `rememberSaveable` | ✅ | ✅ | Local UI state (expanded, scroll) |
34
+ | `SavedStateHandle` | ✅ | ✅ | ViewModel state (query, selected ID) |
35
+ | `Room` / `DataStore` | ✅ | ✅ | Persistent data |
36
+
37
+ ---
38
+
39
+ ## Compose — rememberSaveable
40
+
41
+ ```kotlin
42
+ // ✅ Simple value — auto-saved
43
+ @Composable
44
+ fun SearchBar() {
45
+ var query by rememberSaveable { mutableStateOf("") }
46
+ // query survives rotation and process death
47
+ TextField(value = query, onValueChange = { query = it })
48
+ }
49
+
50
+ // ✅ Custom type — requires Saver
51
+ data class FilterState(val category: String, val sort: String)
52
+
53
+ val FilterStateSaver = run {
54
+ val categoryKey = "category"
55
+ val sortKey = "sort"
56
+ mapSaver(
57
+ save = { mapOf(categoryKey to it.category, sortKey to it.sort) },
58
+ restore = { FilterState(it[categoryKey] as String, it[sortKey] as String) }
59
+ )
60
+ }
61
+
62
+ @Composable
63
+ fun FilterPanel() {
64
+ var filter by rememberSaveable(stateSaver = FilterStateSaver) {
65
+ mutableStateOf(FilterState("all", "asc"))
66
+ }
67
+ }
68
+
69
+ // ✅ List state — scroll position
70
+ @Composable
71
+ fun UserList(users: List<User>) {
72
+ val listState = rememberLazyListState()
73
+ // listState scroll position is automatically saved by rememberLazyListState
74
+ LazyColumn(state = listState) {
75
+ items(users) { UserItem(it) }
76
+ }
77
+ }
78
+ ```
79
+
80
+ ---
81
+
82
+ ## Compose — Hoist State to ViewModel
83
+
84
+ ```kotlin
85
+ // ✅ When state needs to be shared or outlive a single composable
86
+ // → hoist to ViewModel backed by SavedStateHandle
87
+
88
+ @HiltViewModel
89
+ class ProductViewModel @Inject constructor(
90
+ savedStateHandle: SavedStateHandle,
91
+ private val repository: ProductRepository
92
+ ) : ViewModel() {
93
+
94
+ // ✅ Search query — saved across process death
95
+ val query: StateFlow<String> = savedStateHandle
96
+ .getStateFlow("query", "")
97
+
98
+ // ✅ Products — reloaded from DB, not saved
99
+ val products: StateFlow<List<Product>> = query
100
+ .flatMapLatest { repository.search(it) }
101
+ .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
102
+
103
+ fun onQueryChanged(q: String) { savedStateHandle["query"] = q }
104
+ }
105
+
106
+ @Composable
107
+ fun ProductScreen(viewModel: ProductViewModel = hiltViewModel()) {
108
+ val query by viewModel.query.collectAsStateWithLifecycle()
109
+ val products by viewModel.products.collectAsStateWithLifecycle()
110
+
111
+ // UI renders from ViewModel state — no local state needed
112
+ }
113
+ ```
114
+
115
+ ---
116
+
117
+ ## View System — onSaveInstanceState
118
+
119
+ ```kotlin
120
+ // ✅ For custom Views that need to save state
121
+ class CounterView(context: Context) : View(context) {
122
+
123
+ private var count = 0
124
+
125
+ override fun onSaveInstanceState(): Parcelable {
126
+ val superState = super.onSaveInstanceState()
127
+ return SavedState(superState, count)
128
+ }
129
+
130
+ override fun onRestoreInstanceState(state: Parcelable?) {
131
+ if (state is SavedState) {
132
+ super.onRestoreInstanceState(state.superState)
133
+ count = state.count
134
+ } else {
135
+ super.onRestoreInstanceState(state)
136
+ }
137
+ }
138
+
139
+ @Parcelize
140
+ class SavedState(val parcelable: Parcelable?, val count: Int) : BaseSavedState(parcelable)
141
+ }
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Restoration Decision Tree
147
+
148
+ ```
149
+ Is this state local to a single composable?
150
+ YES → rememberSaveable
151
+ NO ↓
152
+
153
+ Is this state loaded from DB or network?
154
+ YES → reload from repository (don't save)
155
+ NO ↓
156
+
157
+ Is this state user input or a selected ID?
158
+ YES → SavedStateHandle in ViewModel
159
+ NO ↓
160
+
161
+ Is this critical persistent data?
162
+ YES → Room or DataStore
163
+ ```
164
+
165
+ ---
166
+
167
+ ## Testing Restoration
168
+
169
+ ```kotlin
170
+ // ✅ Test rememberSaveable with StateRestorationTester
171
+ @Test
172
+ fun searchQuery_survivesStateRestoration() {
173
+ val restorationTester = StateRestorationTester(composeTestRule)
174
+
175
+ restorationTester.setContent {
176
+ SearchBar()
177
+ }
178
+
179
+ composeTestRule.onNodeWithTag("search_field").performTextInput("kotlin")
180
+ restorationTester.emulateSavedInstanceStateRestore()
181
+ composeTestRule.onNodeWithTag("search_field").assertTextEquals("kotlin")
182
+ }
183
+
184
+ // ✅ Test SavedStateHandle restoration
185
+ @Test
186
+ fun viewModel_restoresQueryAfterProcessDeath() {
187
+ val handle = SavedStateHandle(mapOf("query" to "android"))
188
+ val vm = ProductViewModel(handle, fakeRepository)
189
+ assertThat(vm.query.value).isEqualTo("android")
190
+ }
191
+ ```
192
+
193
+ ---
194
+
195
+ ## Anti-Patterns
196
+
197
+ - Using `remember` instead of `rememberSaveable` for user-visible state — lost on rotation
198
+ - Saving large lists or bitmaps in `rememberSaveable` or `SavedStateHandle` — size limit and slow
199
+ - Duplicating state: saving in both ViewModel and rememberSaveable — two sources of truth
200
+ - Not testing restoration — the most common cause of rotation bugs
201
+ - Manually saving scroll position when `rememberLazyListState` does it automatically
202
+
203
+ ---
204
+
205
+ ## Related Skills
206
+
207
+ - `savedstatehandle` — SavedStateHandle in ViewModel
208
+ - `process-death-recovery` — surviving system-initiated kills
209
+ - `compose` — rememberSaveable and state hoisting in Compose
210
+ - `lifecycle` — when restoration occurs in the lifecycle
@@ -0,0 +1,207 @@
1
+ ---
2
+ name: bounded-context
3
+ description: >
4
+ Bounded Context pattern for Android feature organization.
5
+ Load this skill when defining feature boundaries, deciding what belongs
6
+ in a shared domain vs a feature-specific domain, preventing model pollution
7
+ across features, or designing the conceptual boundaries of a module.
8
+ ---
9
+
10
+ # Bounded Context
11
+
12
+ ## Overview
13
+
14
+ A Bounded Context defines the boundary within which a domain model is consistent and valid. In Android, each feature module (or major feature area) is a Bounded Context — it owns its own models, rules, and language. The same real-world concept (e.g. "User") may exist in multiple contexts with different shapes and responsibilities.
15
+
16
+ ---
17
+
18
+ ## Core Principles
19
+
20
+ - Each context owns its **own model** — never share a model across contexts if they evolve differently
21
+ - **Context map** makes inter-context relationships explicit
22
+ - Cross-context communication happens through **well-defined interfaces** — not direct model sharing
23
+ - The same word can mean different things in different contexts — that's intentional
24
+ - Shared kernel only for concepts that are **truly identical** across contexts
25
+
26
+ ---
27
+
28
+ ## Example: "User" in Multiple Contexts
29
+
30
+ ```kotlin
31
+ // ❌ Tempting but wrong — one "User" class used everywhere
32
+ data class User(
33
+ val id: String,
34
+ val name: String,
35
+ val email: String,
36
+ val role: UserRole,
37
+ val shippingAddresses: List<Address>, // only needed in Orders context
38
+ val orderHistory: List<OrderSummary>, // only needed in Orders context
39
+ val savedCards: List<PaymentCard>, // only needed in Payment context
40
+ val notificationPreferences: NotificationPrefs // only needed in Notifications
41
+ )
42
+ // This model becomes a dumping ground — bloated and hard to maintain
43
+
44
+ // ✅ Correct — each context defines its own model
45
+ // Auth context
46
+ data class AuthUser(
47
+ val id: String,
48
+ val email: String,
49
+ val role: UserRole,
50
+ val sessionToken: String
51
+ )
52
+
53
+ // Orders context
54
+ data class OrderCustomer(
55
+ val id: String,
56
+ val displayName: String,
57
+ val defaultShippingAddress: Address?
58
+ )
59
+
60
+ // Profile context
61
+ data class UserProfile(
62
+ val id: String,
63
+ val fullName: String,
64
+ val avatarUrl: String?,
65
+ val bio: String?
66
+ )
67
+
68
+ // Notifications context
69
+ data class NotificationRecipient(
70
+ val userId: String,
71
+ val pushToken: String?,
72
+ val preferences: NotificationPreferences
73
+ )
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Context Map
79
+
80
+ ```kotlin
81
+ // ✅ Explicit translation between contexts
82
+ // When Orders needs user info, it requests it through an interface
83
+
84
+ // Orders context defines what it needs
85
+ interface CustomerInfoProvider {
86
+ suspend fun getCustomer(userId: String): Result<OrderCustomer>
87
+ }
88
+
89
+ // The implementation in :app or a shared module translates
90
+ class CustomerInfoProviderImpl @Inject constructor(
91
+ private val userRepository: UserRepository // Auth/Profile context
92
+ ) : CustomerInfoProvider {
93
+
94
+ override suspend fun getCustomer(userId: String): Result<OrderCustomer> =
95
+ userRepository.getUser(userId).map { user ->
96
+ OrderCustomer(
97
+ id = user.id,
98
+ displayName = user.name,
99
+ defaultShippingAddress = user.addresses.firstOrNull { it.isDefault }
100
+ )
101
+ }
102
+ }
103
+ ```
104
+
105
+ ---
106
+
107
+ ## Feature Module as Bounded Context
108
+
109
+ ```kotlin
110
+ // ✅ :feature:orders owns its domain model
111
+ // feature/orders/domain/model/
112
+ data class Order(val id: String, val customerId: String, val items: List<OrderItem>, ...)
113
+ data class OrderItem(val productId: String, val name: String, val quantity: Int, ...)
114
+ data class OrderSummary(val id: String, val status: OrderStatus, val totalAmount: Money)
115
+
116
+ // ✅ :feature:catalog owns its domain model — Product means something different here
117
+ // feature/catalog/domain/model/
118
+ data class Product(val id: String, val name: String, val description: String, val price: Money, ...)
119
+ data class Category(val id: String, val name: String, val products: List<ProductSummary>)
120
+
121
+ // ✅ :feature:cart owns its domain model — Product in cart context = CartItem
122
+ // feature/cart/domain/model/
123
+ data class Cart(val id: String, val items: List<CartItem>)
124
+ data class CartItem(val productId: String, val productName: String, val unitPrice: Money, val quantity: Int)
125
+ // Note: CartItem has productName denormalized — it owns what it needs
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Shared Kernel
131
+
132
+ ```kotlin
133
+ // ✅ Truly shared concepts live in :core:domain
134
+ // These are stable and identical across all contexts
135
+
136
+ // :core:domain/model/shared/
137
+ @JvmInline value class UserId(val value: String)
138
+ @JvmInline value class ProductId(val value: String)
139
+ data class Money(val amount: Long, val currency: Currency)
140
+ enum class Currency { IRR, USD, EUR }
141
+
142
+ // All features import these from :core:domain — not from each other
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Anti-Corruption Layer
148
+
149
+ ```kotlin
150
+ // ✅ Translate external API models without polluting domain
151
+ // When integrating a payment gateway, don't let its model bleed in
152
+
153
+ // External (Zarinpal, Stripe, etc.)
154
+ data class PaymentGatewayResponse(
155
+ val status: Int,
156
+ val ref_id: String?,
157
+ val error_code: String?
158
+ )
159
+
160
+ // Anti-corruption layer — translates to domain language
161
+ class PaymentGatewayAdapter @Inject constructor(
162
+ private val gateway: PaymentGatewayService
163
+ ) {
164
+ suspend fun processPayment(request: PaymentRequest): Result<PaymentConfirmation> =
165
+ runCatching {
166
+ val response = gateway.pay(request.toGatewayRequest())
167
+ when (response.status) {
168
+ 100 -> PaymentConfirmation(
169
+ transactionId = response.ref_id ?: error("Missing ref_id"),
170
+ amount = request.amount
171
+ )
172
+ else -> throw PaymentException(response.error_code ?: "Unknown error")
173
+ }
174
+ }
175
+ }
176
+ ```
177
+
178
+ ---
179
+
180
+ ## When to Split vs Share
181
+
182
+ | Signal | Action |
183
+ | ------------------------------------------------------ | ------------------------------------------------ |
184
+ | Same concept evolves differently in two features | Split into two models |
185
+ | Same concept has identical fields and rules everywhere | Put in shared kernel |
186
+ | Feature A needs data from Feature B | Define interface in A, implement in :app |
187
+ | Two features share a DTO from the same API endpoint | Split into feature-specific domain models anyway |
188
+ | Changing one feature's model breaks another feature | Context boundary is violated — split |
189
+
190
+ ---
191
+
192
+ ## Anti-Patterns
193
+
194
+ - One giant `User` class used by auth, profile, orders, notifications — split by context
195
+ - Feature modules importing domain models from other feature modules — route through :core or interfaces
196
+ - Shared kernel growing to include feature-specific concepts — keep shared kernel minimal and stable
197
+ - Skipping the translation layer — directly mapping API DTOs to domain models used by multiple features
198
+
199
+ ---
200
+
201
+ ## Related Skills
202
+
203
+ - `modularization` — module boundaries that enforce context boundaries
204
+ - `feature-isolation` — isolating feature implementation details
205
+ - `domain-modeling` — modeling within a bounded context
206
+ - `clean-architecture` — layer boundaries within a context
207
+ - `repository-pattern` — repository interfaces as context boundaries
@@ -0,0 +1,229 @@
1
+ ---
2
+ name: clean-architecture
3
+ description: >
4
+ Clean Architecture layering for Android projects.
5
+ Load this skill when designing layer boundaries, defining dependencies between
6
+ layers, structuring packages, or deciding where logic belongs (UI vs Domain vs Data).
7
+ ---
8
+
9
+ # Clean Architecture
10
+
11
+ ## Overview
12
+
13
+ Clean Architecture organizes code into three concentric layers: **Presentation**, **Domain**, and **Data**. Dependencies flow inward only — outer layers depend on inner layers, never the reverse. The Domain layer is pure Kotlin with no Android dependencies.
14
+
15
+ ---
16
+
17
+ ## Layer Structure
18
+
19
+ ```
20
+ app/
21
+ ├── presentation/ # Compose UI, ViewModels, UiState, UiEvent
22
+ │ └── feature/
23
+ │ ├── FeatureScreen.kt
24
+ │ ├── FeatureViewModel.kt
25
+ │ └── FeatureUiState.kt
26
+ ├── domain/ # Pure Kotlin — UseCases, Entities, Repository interfaces
27
+ │ ├── model/
28
+ │ │ └── User.kt
29
+ │ ├── repository/
30
+ │ │ └── UserRepository.kt # interface only
31
+ │ └── usecase/
32
+ │ └── GetUserUseCase.kt
33
+ └── data/ # Repository implementations, Room, Retrofit, DataStore
34
+ ├── repository/
35
+ │ └── UserRepositoryImpl.kt
36
+ ├── local/
37
+ │ ├── UserDao.kt
38
+ │ └── UserEntity.kt
39
+ ├── remote/
40
+ │ ├── UserApiService.kt
41
+ │ └── UserDto.kt
42
+ └── mapper/
43
+ └── UserMapper.kt
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Dependency Rule
49
+
50
+ ```
51
+ Presentation → Domain ← Data
52
+ ```
53
+
54
+ - **Domain** depends on nothing (no Android, no Room, no Retrofit)
55
+ - **Presentation** depends on Domain (uses UseCases and models)
56
+ - **Data** depends on Domain (implements Repository interfaces)
57
+ - **Data** never imports from **Presentation**
58
+ - **Presentation** never imports from **Data** directly
59
+
60
+ ---
61
+
62
+ ## Domain Layer
63
+
64
+ ```kotlin
65
+ // ✅ Entity — pure Kotlin data class, no annotations
66
+ data class User(
67
+ val id: String,
68
+ val name: String,
69
+ val email: String,
70
+ val role: UserRole
71
+ )
72
+
73
+ enum class UserRole { ADMIN, MEMBER, GUEST }
74
+
75
+ // ✅ Repository interface — defined in Domain, implemented in Data
76
+ interface UserRepository {
77
+ suspend fun getUser(id: String): Result<User>
78
+ suspend fun getUsers(): Result<List<User>>
79
+ suspend fun saveUser(user: User): Result<Unit>
80
+ fun observeUsers(): Flow<List<User>>
81
+ }
82
+
83
+ // ✅ UseCase — single responsibility, pure logic
84
+ class GetUserUseCase @Inject constructor(
85
+ private val repository: UserRepository
86
+ ) {
87
+ suspend operator fun invoke(id: String): Result<User> =
88
+ repository.getUser(id)
89
+ }
90
+
91
+ class GetActiveUsersUseCase @Inject constructor(
92
+ private val repository: UserRepository
93
+ ) {
94
+ operator fun invoke(): Flow<List<User>> =
95
+ repository.observeUsers().map { users ->
96
+ users.filter { it.role != UserRole.GUEST }
97
+ }
98
+ }
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Data Layer
104
+
105
+ ```kotlin
106
+ // ✅ Entity — Room-specific, isolated in data layer
107
+ @Entity(tableName = "users")
108
+ data class UserEntity(
109
+ @PrimaryKey val id: String,
110
+ val name: String,
111
+ val email: String,
112
+ val role: String
113
+ )
114
+
115
+ // ✅ DTO — API response model
116
+ data class UserDto(
117
+ @SerialName("id") val id: String,
118
+ @SerialName("full_name") val name: String,
119
+ @SerialName("email_address") val email: String,
120
+ @SerialName("role") val role: String
121
+ )
122
+
123
+ // ✅ Mapper — converts between layers
124
+ object UserMapper {
125
+ fun UserDto.toDomain(): User = User(
126
+ id = id,
127
+ name = name,
128
+ email = email,
129
+ role = UserRole.valueOf(role.uppercase())
130
+ )
131
+
132
+ fun UserEntity.toDomain(): User = User(
133
+ id = id,
134
+ name = name,
135
+ email = email,
136
+ role = UserRole.valueOf(role.uppercase())
137
+ )
138
+
139
+ fun User.toEntity(): UserEntity = UserEntity(
140
+ id = id,
141
+ name = name,
142
+ email = email,
143
+ role = role.name.lowercase()
144
+ )
145
+ }
146
+
147
+ // ✅ Repository implementation — in Data, implements Domain interface
148
+ class UserRepositoryImpl @Inject constructor(
149
+ private val api: UserApiService,
150
+ private val dao: UserDao
151
+ ) : UserRepository {
152
+
153
+ override suspend fun getUser(id: String): Result<User> = runCatching {
154
+ val entity = dao.getUserById(id)
155
+ entity?.toDomain() ?: run {
156
+ val dto = api.getUser(id)
157
+ val domain = dto.toDomain()
158
+ dao.insert(domain.toEntity())
159
+ domain
160
+ }
161
+ }
162
+
163
+ override fun observeUsers(): Flow<List<User>> =
164
+ dao.observeAll().map { entities -> entities.map { it.toDomain() } }
165
+ }
166
+ ```
167
+
168
+ ---
169
+
170
+ ## Presentation Layer
171
+
172
+ ```kotlin
173
+ // ✅ ViewModel uses UseCases — never Repository directly
174
+ @HiltViewModel
175
+ class UserListViewModel @Inject constructor(
176
+ private val getActiveUsersUseCase: GetActiveUsersUseCase
177
+ ) : ViewModel() {
178
+
179
+ val state: StateFlow<UserListUiState> =
180
+ getActiveUsersUseCase()
181
+ .map { UserListUiState.Success(it) }
182
+ .catch { emit(UserListUiState.Error(it.message ?: "Error")) }
183
+ .stateIn(
184
+ scope = viewModelScope,
185
+ started = SharingStarted.WhileSubscribed(5_000),
186
+ initialValue = UserListUiState.Loading
187
+ )
188
+ }
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Hilt Binding
194
+
195
+ ```kotlin
196
+ // ✅ Bind interface to implementation in data module
197
+ @Module
198
+ @InstallIn(SingletonComponent::class)
199
+ abstract class RepositoryModule {
200
+
201
+ @Binds
202
+ @Singleton
203
+ abstract fun bindUserRepository(
204
+ impl: UserRepositoryImpl
205
+ ): UserRepository
206
+ }
207
+ ```
208
+
209
+ ---
210
+
211
+ ## Anti-Patterns
212
+
213
+ - Importing `android.*` or `androidx.*` in Domain layer — Domain must be pure Kotlin
214
+ - ViewModel calling Repository directly — always go through a UseCase
215
+ - Presentation importing Data classes (Entity, DTO) — only Domain models cross into Presentation
216
+ - UseCase containing more than one responsibility — one UseCase, one operation
217
+ - Repository interface defined in Data layer — interfaces belong in Domain
218
+ - Skipping the mapper — never pass DTO or Entity into Domain or Presentation
219
+
220
+ ---
221
+
222
+ ## Related Skills
223
+
224
+ - `mvvm` — ViewModel and UI state structure
225
+ - `use-case-design` — designing UseCases in Domain layer
226
+ - `repository-pattern` — Repository interface and implementation patterns
227
+ - `dto-mapping` — mapping between layers
228
+ - `hilt` — dependency injection wiring
229
+ - `domain-modeling` — modeling domain entities