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,238 @@
1
+ ---
2
+ name: domain-error-model
3
+ description: >
4
+ Domain error hierarchy design for Android Clean Architecture.
5
+ Load this skill when designing the sealed class error hierarchy,
6
+ deciding how granular errors should be, structuring errors by domain
7
+ context, or defining the contract between data and domain layers.
8
+ ---
9
+
10
+ # Domain Error Model
11
+
12
+ ## Overview
13
+ The domain error model is a sealed class hierarchy that represents all possible failure states in the application, using the language of the business — not HTTP codes or SQL exceptions. It lives in the domain layer and is the single contract between data (which produces errors) and presentation (which displays them).
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - **Pure Kotlin** — no Android, Retrofit, or Room imports
20
+ - **Sealed hierarchy** — exhaustive, forces handling at every call site
21
+ - **Business language** — `Unauthorized` not `Http401`, `NotFound` not `Http404`
22
+ - **Granularity by need** — only as specific as the UI needs to differentiate behavior
23
+ - **Stable** — rarely changes; presentation adapts to it, not the other way
24
+
25
+ ---
26
+
27
+ ## Full Domain Error Hierarchy
28
+
29
+ ```kotlin
30
+ // domain/error/AppError.kt
31
+
32
+ sealed interface AppError {
33
+
34
+ // ── Network errors ──────────────────────────────────────────────────
35
+ sealed interface Network : AppError {
36
+ /** Device has no internet connection */
37
+ data object NoConnection : Network
38
+
39
+ /** Request timed out */
40
+ data object Timeout : Network
41
+
42
+ /** Server returned 5xx */
43
+ data class ServerError(val code: Int) : Network
44
+
45
+ /** 401 — session expired or token invalid */
46
+ data object Unauthorized : Network
47
+
48
+ /** 403 — user lacks permission */
49
+ data object Forbidden : Network
50
+
51
+ /** 404 — resource does not exist on server */
52
+ data object NotFound : Network
53
+
54
+ /** 429 — rate limited */
55
+ data object RateLimited : Network
56
+ }
57
+
58
+ // ── Data / business errors ───────────────────────────────────────────
59
+ sealed interface Data : AppError {
60
+ /** Requested entity does not exist locally */
61
+ data object NotFound : Data
62
+
63
+ /** Operation would violate a uniqueness constraint */
64
+ data class Conflict(val field: String) : Data
65
+
66
+ /** Input failed business validation */
67
+ data class Validation(val errors: Map<String, String>) : Data
68
+
69
+ /** Operation not allowed in current state */
70
+ data class InvalidState(val reason: String) : Data
71
+ }
72
+
73
+ // ── Storage errors ───────────────────────────────────────────────────
74
+ sealed interface Storage : AppError {
75
+ /** Device storage is full */
76
+ data object DiskFull : Storage
77
+
78
+ /** Local database or file is corrupted */
79
+ data object Corrupted : Storage
80
+
81
+ /** Unclassified storage error */
82
+ data class Unknown(val cause: Throwable) : Storage
83
+ }
84
+
85
+ // ── Auth / security errors ───────────────────────────────────────────
86
+ sealed interface Auth : AppError {
87
+ /** User is not authenticated */
88
+ data object NotAuthenticated : Auth
89
+
90
+ /** Biometric / PIN verification failed */
91
+ data object BiometricFailed : Auth
92
+
93
+ /** Account is suspended or locked */
94
+ data object AccountSuspended : Auth
95
+ }
96
+
97
+ // ── Unexpected / unclassified ────────────────────────────────────────
98
+ data class Unexpected(val cause: Throwable) : AppError
99
+ }
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Wrapping as Exception (for Result interop)
105
+
106
+ ```kotlin
107
+ // ✅ Wrap AppError in an exception so it can travel through Result<T>
108
+ class AppException(val error: AppError) : Exception(error.toString())
109
+
110
+ // Extension to lift AppError into Result failure
111
+ fun <T> Result.Companion.domainFailure(error: AppError): Result<T> =
112
+ failure(AppException(error))
113
+
114
+ // Extension to extract AppError from Result failure
115
+ fun <T> Result<T>.appErrorOrNull(): AppError? =
116
+ exceptionOrNull()?.let { if (it is AppException) it.error else null }
117
+
118
+ // ✅ Usage in UseCase
119
+ class GetUserUseCase @Inject constructor(
120
+ private val repository: UserRepository
121
+ ) {
122
+ suspend operator fun invoke(id: String): Result<User> =
123
+ repository.getUser(id).fold(
124
+ onSuccess = { user ->
125
+ if (user.status == UserStatus.DELETED)
126
+ Result.domainFailure(AppError.Data.InvalidState("User is deleted"))
127
+ else
128
+ Result.success(user)
129
+ },
130
+ onFailure = { Result.failure(it) }
131
+ )
132
+ }
133
+ ```
134
+
135
+ ---
136
+
137
+ ## Per-Feature Error Scoping
138
+
139
+ ```kotlin
140
+ // ✅ Feature-specific errors extend AppError — keeps domain focused
141
+ sealed interface OrderError : AppError {
142
+ data object CartEmpty : OrderError
143
+ data class InsufficientStock(val productName: String, val available: Int) : OrderError
144
+ data object PaymentDeclined : OrderError
145
+ data class DeliveryUnavailable(val reason: String) : OrderError
146
+ }
147
+
148
+ // ✅ Usage
149
+ class PlaceOrderUseCase @Inject constructor(
150
+ private val orderRepository: OrderRepository,
151
+ private val inventoryRepository: InventoryRepository
152
+ ) {
153
+ suspend operator fun invoke(cartId: String): Result<Order> = runCatching {
154
+ val cart = orderRepository.getCart(cartId).getOrThrow()
155
+
156
+ if (cart.items.isEmpty())
157
+ throw AppException(OrderError.CartEmpty)
158
+
159
+ cart.items.forEach { item ->
160
+ val stock = inventoryRepository.getStock(item.productId).getOrThrow()
161
+ if (stock < item.quantity)
162
+ throw AppException(OrderError.InsufficientStock(item.name, stock))
163
+ }
164
+
165
+ orderRepository.placeOrder(cart).getOrThrow()
166
+ }
167
+ }
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Handling in ViewModel
173
+
174
+ ```kotlin
175
+ // ✅ Exhaustive when expression forces handling every error
176
+ private fun handleError(error: AppError): String = when (error) {
177
+ is AppError.Network.NoConnection -> "No internet connection"
178
+ is AppError.Network.Timeout -> "Request timed out"
179
+ is AppError.Network.Unauthorized -> "Session expired"
180
+ is AppError.Network.Forbidden -> "Permission denied"
181
+ is AppError.Network.NotFound -> "Not found"
182
+ is AppError.Network.ServerError -> "Server error (${error.code})"
183
+ is AppError.Network.RateLimited -> "Too many requests"
184
+ is AppError.Data.NotFound -> "Item not found"
185
+ is AppError.Data.Conflict -> "Already exists: ${error.field}"
186
+ is AppError.Data.Validation -> error.errors.values.first()
187
+ is AppError.Data.InvalidState -> error.reason
188
+ is AppError.Storage.DiskFull -> "Storage full"
189
+ is AppError.Storage.Corrupted -> "Data corrupted"
190
+ is AppError.Storage.Unknown -> "Storage error"
191
+ is AppError.Auth.NotAuthenticated -> "Please log in"
192
+ is AppError.Auth.BiometricFailed -> "Authentication failed"
193
+ is AppError.Auth.AccountSuspended -> "Account suspended"
194
+ is AppError.Unexpected -> "Something went wrong"
195
+ // Feature errors
196
+ is OrderError.CartEmpty -> "Your cart is empty"
197
+ is OrderError.InsufficientStock -> "Not enough stock for ${error.productName}"
198
+ is OrderError.PaymentDeclined -> "Payment declined"
199
+ is OrderError.DeliveryUnavailable -> error.reason
200
+ }
201
+ ```
202
+
203
+ ---
204
+
205
+ ## Retry Decision
206
+
207
+ ```kotlin
208
+ // ✅ Determine if an error is retryable
209
+ fun AppError.isRetryable(): Boolean = when (this) {
210
+ is AppError.Network.NoConnection,
211
+ is AppError.Network.Timeout,
212
+ is AppError.Network.ServerError -> true
213
+ is AppError.Network.RateLimited -> true
214
+ else -> false
215
+ }
216
+
217
+ // ✅ Determine if error requires re-authentication
218
+ fun AppError.requiresReAuth(): Boolean =
219
+ this is AppError.Network.Unauthorized || this is AppError.Auth.NotAuthenticated
220
+ ```
221
+
222
+ ---
223
+
224
+ ## Anti-Patterns
225
+
226
+ - `AppError.Unexpected` as the only error type — no granularity; can't make smart UI decisions
227
+ - HTTP codes in domain error names (`Http404`, `Http401`) — domain must speak business language
228
+ - Domain error importing `retrofit2.HttpException` — breaks domain layer independence
229
+ - Flat error list instead of sealed hierarchy — no compile-time exhaustiveness
230
+ - Too many error subtypes nobody handles differently — YAGNI; add when UI needs to differentiate
231
+
232
+ ---
233
+
234
+ ## Related Skills
235
+ - `error-handling` — propagation strategy across layers
236
+ - `error-mapping` — converting raw exceptions to domain errors
237
+ - `failure-strategy` — deciding how to respond to each error
238
+ - `user-friendly-errors` — displaying errors in UI
@@ -0,0 +1,255 @@
1
+ ---
2
+ name: error-handling
3
+ description: >
4
+ Error handling strategy for Android Clean Architecture projects.
5
+ Load this skill when designing how errors propagate across layers,
6
+ using Result<T> for error representation, handling errors in ViewModel,
7
+ or deciding between exceptions and sealed classes for error modeling.
8
+ ---
9
+
10
+ # Error Handling
11
+
12
+ ## Overview
13
+ Error handling defines how failures travel from the data layer to the UI. In Clean Architecture, errors are represented as `Result<T>` in the domain and data layers, mapped to domain-specific error types, and finally translated to user-friendly messages in the presentation layer. Exceptions are caught at layer boundaries — never leaked into the UI.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - **Catch exceptions at boundaries** — Data layer catches, wraps in `Result.failure()`
20
+ - **Domain errors are sealed classes** — not raw exceptions; typed and exhaustive
21
+ - **ViewModel translates** domain errors to UI state — never expose raw exceptions to UI
22
+ - **Never swallow errors silently** — always log or propagate
23
+ - **`runCatching`** for converting exception-throwing code to `Result<T>`
24
+
25
+ ---
26
+
27
+ ## Result<T> Across Layers
28
+
29
+ ```
30
+ Data Layer Domain Layer Presentation Layer
31
+ ───────────────── ────────────────── ───────────────────
32
+ Repository UseCase ViewModel
33
+ runCatching { → Result<T> → UiState.Error(msg)
34
+ api.call() → .map { } →
35
+ } → .mapError { } →
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Data Layer — Catching at the Boundary
41
+
42
+ ```kotlin
43
+ // ✅ Repository wraps all exceptions in Result
44
+ class UserRepositoryImpl @Inject constructor(
45
+ private val api: UserApiService,
46
+ private val dao: UserDao
47
+ ) : UserRepository {
48
+
49
+ override suspend fun getUser(id: String): Result<User> = runCatching {
50
+ dao.getById(id)?.toDomain()
51
+ ?: api.getUser(id).toDomain().also { dao.insert(it.toEntity()) }
52
+ }
53
+
54
+ override suspend fun createUser(user: User): Result<User> = runCatching {
55
+ val dto = api.createUser(user.toCreateRequest())
56
+ val created = dto.toDomain()
57
+ dao.insert(created.toEntity())
58
+ created
59
+ }
60
+
61
+ // ✅ Flow — use catch operator
62
+ override fun observeUsers(): Flow<List<User>> =
63
+ dao.observeAll()
64
+ .map { entities -> entities.map { it.toDomain() } }
65
+ .catch { e ->
66
+ // Log and emit empty — or rethrow depending on strategy
67
+ Timber.e(e, "Failed to observe users")
68
+ emit(emptyList())
69
+ }
70
+ }
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Domain Layer — Typed Error Model
76
+
77
+ ```kotlin
78
+ // ✅ Domain errors — see domain-error-model skill for full detail
79
+ sealed interface UserError {
80
+ data object NotFound : UserError
81
+ data object Unauthorized : UserError
82
+ data class NetworkError(val message: String) : UserError
83
+ data class ValidationError(val field: String, val reason: String) : UserError
84
+ }
85
+
86
+ // ✅ UseCase maps raw Result to domain Result
87
+ class GetUserUseCase @Inject constructor(
88
+ private val repository: UserRepository
89
+ ) {
90
+ suspend operator fun invoke(id: String): Result<User> =
91
+ repository.getUser(id)
92
+ .mapCatching { user ->
93
+ // Additional domain validation
94
+ check(user.status != UserStatus.DELETED) { "User has been deleted" }
95
+ user
96
+ }
97
+ }
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Presentation Layer — ViewModel Error Handling
103
+
104
+ ```kotlin
105
+ // ✅ ViewModel translates Result to UiState
106
+ @HiltViewModel
107
+ class UserDetailViewModel @Inject constructor(
108
+ savedStateHandle: SavedStateHandle,
109
+ private val getUserUseCase: GetUserUseCase,
110
+ private val deleteUserUseCase: DeleteUserUseCase
111
+ ) : ViewModel() {
112
+
113
+ private val userId = checkNotNull(savedStateHandle.get<String>("userId"))
114
+
115
+ private val _state = MutableStateFlow<UserDetailUiState>(UserDetailUiState.Loading)
116
+ val state: StateFlow<UserDetailUiState> = _state.asStateFlow()
117
+
118
+ private val _events = Channel<UserDetailEvent>(Channel.BUFFERED)
119
+ val events: Flow<UserDetailEvent> = _events.receiveAsFlow()
120
+
121
+ init { loadUser() }
122
+
123
+ fun loadUser() {
124
+ viewModelScope.launch {
125
+ _state.value = UserDetailUiState.Loading
126
+ getUserUseCase(userId).fold(
127
+ onSuccess = { user ->
128
+ _state.value = UserDetailUiState.Success(user)
129
+ },
130
+ onFailure = { error ->
131
+ _state.value = UserDetailUiState.Error(
132
+ message = error.toUserMessage(),
133
+ canRetry = error.isRetryable()
134
+ )
135
+ }
136
+ )
137
+ }
138
+ }
139
+
140
+ fun onDeleteClick() {
141
+ viewModelScope.launch {
142
+ deleteUserUseCase(userId).fold(
143
+ onSuccess = {
144
+ _events.send(UserDetailEvent.NavigateBack)
145
+ },
146
+ onFailure = { error ->
147
+ _events.send(UserDetailEvent.ShowSnackbar(error.toUserMessage()))
148
+ }
149
+ )
150
+ }
151
+ }
152
+ }
153
+ ```
154
+
155
+ ---
156
+
157
+ ## Error Extension Functions
158
+
159
+ ```kotlin
160
+ // ✅ Centralized error translation
161
+ fun Throwable.toUserMessage(): String = when (this) {
162
+ is HttpException -> when (code()) {
163
+ 401 -> "Session expired. Please log in again."
164
+ 403 -> "You don't have permission to do this."
165
+ 404 -> "The requested item was not found."
166
+ 429 -> "Too many requests. Please wait a moment."
167
+ in 500..599 -> "Server error. Please try again later."
168
+ else -> "Something went wrong. Please try again."
169
+ }
170
+ is IOException,
171
+ is SocketTimeoutException -> "No internet connection. Please check your network."
172
+ is CancellationException -> throw this // never swallow cancellation
173
+ else -> message ?: "An unexpected error occurred."
174
+ }
175
+
176
+ fun Throwable.isRetryable(): Boolean = when (this) {
177
+ is IOException,
178
+ is SocketTimeoutException -> true
179
+ is HttpException -> code() in 500..599
180
+ else -> false
181
+ }
182
+
183
+ // ✅ Result extensions
184
+ fun <T> Result<T>.onFailureLog(tag: String = "App"): Result<T> = onFailure { e ->
185
+ if (e !is CancellationException) Timber.tag(tag).e(e)
186
+ }
187
+
188
+ suspend fun <T> Result<T>.orThrowDomain(
189
+ transform: (Throwable) -> Throwable = { it }
190
+ ): T = fold(
191
+ onSuccess = { it },
192
+ onFailure = { throw transform(it) }
193
+ )
194
+ ```
195
+
196
+ ---
197
+
198
+ ## Flow Error Handling
199
+
200
+ ```kotlin
201
+ // ✅ Wrap Flow emissions in Result for error propagation
202
+ fun <T> Flow<T>.asResult(): Flow<Result<T>> =
203
+ map { Result.success(it) }
204
+ .catch { emit(Result.failure(it)) }
205
+
206
+ // ✅ Usage in ViewModel
207
+ val state: StateFlow<UserListUiState> =
208
+ getUsersUseCase()
209
+ .asResult()
210
+ .map { result ->
211
+ result.fold(
212
+ onSuccess = { UserListUiState.Success(it) },
213
+ onFailure = { UserListUiState.Error(it.toUserMessage()) }
214
+ )
215
+ }
216
+ .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), UserListUiState.Loading)
217
+ ```
218
+
219
+ ---
220
+
221
+ ## Never Swallow CancellationException
222
+
223
+ ```kotlin
224
+ // ✅ Always rethrow CancellationException
225
+ try {
226
+ someOperation()
227
+ } catch (e: CancellationException) {
228
+ throw e // ✅ must rethrow — coroutine cancellation mechanism depends on this
229
+ } catch (e: Exception) {
230
+ handleError(e)
231
+ }
232
+
233
+ // ✅ runCatching is safe — it doesn't swallow CancellationException in Kotlin 1.7+
234
+ val result = runCatching { someOperation() }
235
+ // But be careful with older Kotlin versions
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Anti-Patterns
241
+
242
+ - Catching `Exception` in ViewModel and showing generic message — typed errors give better UX
243
+ - Leaking `HttpException` or `IOException` into Domain layer — map at the repository boundary
244
+ - Empty `catch` blocks — always log or propagate
245
+ - Swallowing `CancellationException` — breaks coroutine cancellation
246
+ - Storing error state as `String?` in UiState — use a sealed class for typed error display
247
+
248
+ ---
249
+
250
+ ## Related Skills
251
+ - `domain-error-model` — typed domain error hierarchy
252
+ - `error-mapping` — mapping between layer-specific errors
253
+ - `failure-strategy` — deciding how to respond to different error types
254
+ - `user-friendly-errors` — translating technical errors to UI messages
255
+ - `state-management` — modeling error state in UiState