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,244 @@
1
+ ---
2
+ name: use-case-design
3
+ description: >
4
+ Use Case design for Android Clean Architecture.
5
+ Load this skill when creating UseCase classes, deciding what logic
6
+ belongs in a UseCase vs Repository vs ViewModel, structuring UseCase
7
+ inputs and outputs, or composing multiple UseCases together.
8
+ ---
9
+
10
+ # Use Case Design
11
+
12
+ ## Overview
13
+
14
+ A UseCase (also called Interactor) encapsulates a single business operation. It lives in the Domain layer, depends only on Repository interfaces and domain models, and is called by the ViewModel. ViewModels never call Repositories directly — they always go through a UseCase.
15
+
16
+ ---
17
+
18
+ ## Core Principles
19
+
20
+ - **One UseCase, one operation** — `GetUserUseCase`, not `UserUseCase`
21
+ - Lives in **Domain layer** — pure Kotlin, no Android imports
22
+ - Depends on **Repository interfaces** — not implementations
23
+ - Called by **ViewModel only** — never from UI or another UseCase (compose at ViewModel level)
24
+ - Returns **Result<T>** for suspend operations, **Flow<T>** for reactive streams
25
+ - **No state** — UseCases are stateless; state lives in ViewModel
26
+
27
+ ---
28
+
29
+ ## Standard UseCase (Suspend)
30
+
31
+ ```kotlin
32
+ // ✅ Suspend UseCase — one operation, invokable via operator fun
33
+ class GetUserUseCase @Inject constructor(
34
+ private val userRepository: UserRepository
35
+ ) {
36
+ suspend operator fun invoke(userId: String): Result<User> =
37
+ userRepository.getUser(userId)
38
+ }
39
+
40
+ class DeleteUserUseCase @Inject constructor(
41
+ private val userRepository: UserRepository
42
+ ) {
43
+ suspend operator fun invoke(userId: String): Result<Unit> =
44
+ userRepository.deleteUser(userId)
45
+ }
46
+
47
+ class CreateOrderUseCase @Inject constructor(
48
+ private val orderRepository: OrderRepository,
49
+ private val cartRepository: CartRepository,
50
+ private val inventoryRepository: InventoryRepository
51
+ ) {
52
+ suspend operator fun invoke(cartId: String, userId: String): Result<Order> =
53
+ runCatching {
54
+ val cart = cartRepository.getCart(cartId).getOrThrow()
55
+ require(cart.items.isNotEmpty()) { "Cannot create order from empty cart" }
56
+
57
+ // Business rule: validate inventory before placing order
58
+ cart.items.forEach { item ->
59
+ val available = inventoryRepository.getStock(item.productId).getOrThrow()
60
+ require(available >= item.quantity) {
61
+ "Insufficient stock for ${item.productName}"
62
+ }
63
+ }
64
+
65
+ val order = orderRepository.create(cart.toOrderRequest(userId)).getOrThrow()
66
+ cartRepository.clear(cartId)
67
+ order
68
+ }
69
+ }
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Reactive UseCase (Flow)
75
+
76
+ ```kotlin
77
+ // ✅ Flow UseCase — for ongoing observation
78
+ class ObserveUsersUseCase @Inject constructor(
79
+ private val userRepository: UserRepository
80
+ ) {
81
+ operator fun invoke(): Flow<List<User>> =
82
+ userRepository.observeUsers()
83
+ }
84
+
85
+ // ✅ Flow UseCase with transformation
86
+ class ObserveActiveUsersUseCase @Inject constructor(
87
+ private val userRepository: UserRepository
88
+ ) {
89
+ operator fun invoke(): Flow<List<User>> =
90
+ userRepository.observeUsers()
91
+ .map { users -> users.filter { it.status == UserStatus.ACTIVE } }
92
+ .distinctUntilChanged()
93
+ }
94
+
95
+ // ✅ Flow UseCase combining multiple sources
96
+ class ObserveDashboardUseCase @Inject constructor(
97
+ private val userRepository: UserRepository,
98
+ private val orderRepository: OrderRepository
99
+ ) {
100
+ operator fun invoke(userId: String): Flow<DashboardData> =
101
+ combine(
102
+ userRepository.observeUser(userId),
103
+ orderRepository.observeRecentOrders(userId)
104
+ ) { user, orders ->
105
+ DashboardData(
106
+ userName = user?.name ?: "",
107
+ recentOrderCount = orders.size,
108
+ pendingOrderCount = orders.count { it.status == OrderStatus.PENDING }
109
+ )
110
+ }
111
+ }
112
+ ```
113
+
114
+ ---
115
+
116
+ ## UseCase with Parameters
117
+
118
+ ```kotlin
119
+ // ✅ Simple primitive params — pass directly
120
+ class SearchProductsUseCase @Inject constructor(
121
+ private val repository: ProductRepository
122
+ ) {
123
+ suspend operator fun invoke(query: String, filter: ProductFilter): Result<List<Product>> =
124
+ repository.search(query, filter)
125
+ }
126
+
127
+ // ✅ Complex params — use a dedicated Params data class
128
+ class PlaceOrderUseCase @Inject constructor(
129
+ private val repository: OrderRepository
130
+ ) {
131
+ data class Params(
132
+ val cartId: String,
133
+ val shippingAddressId: String,
134
+ val paymentMethodId: String,
135
+ val couponCode: String? = null
136
+ )
137
+
138
+ suspend operator fun invoke(params: Params): Result<Order> =
139
+ runCatching {
140
+ // apply coupon if present
141
+ val discount = params.couponCode?.let {
142
+ repository.validateCoupon(it).getOrThrow()
143
+ }
144
+ repository.placeOrder(params.toRequest(discount)).getOrThrow()
145
+ }
146
+ }
147
+
148
+ // ViewModel usage
149
+ fun onPlaceOrder() {
150
+ viewModelScope.launch {
151
+ val params = PlaceOrderUseCase.Params(
152
+ cartId = state.value.cartId,
153
+ shippingAddressId = state.value.selectedAddressId,
154
+ paymentMethodId = state.value.selectedPaymentId,
155
+ couponCode = state.value.couponCode.takeIf { it.isNotBlank() }
156
+ )
157
+ placeOrderUseCase(params).fold(
158
+ onSuccess = { _events.send(Event.NavigateToConfirmation(it.id)) },
159
+ onFailure = { _state.update { s -> s.copy(error = it.message) } }
160
+ )
161
+ }
162
+ }
163
+ ```
164
+
165
+ ---
166
+
167
+ ## ViewModel Usage
168
+
169
+ ```kotlin
170
+ // ✅ ViewModel composes UseCases — never calls Repository directly
171
+ @HiltViewModel
172
+ class UserDetailViewModel @Inject constructor(
173
+ savedStateHandle: SavedStateHandle,
174
+ private val getUserUseCase: GetUserUseCase,
175
+ private val updateUserUseCase: UpdateUserUseCase,
176
+ private val deleteUserUseCase: DeleteUserUseCase
177
+ ) : ViewModel() {
178
+
179
+ private val userId: String = checkNotNull(savedStateHandle["userId"])
180
+
181
+ val state: StateFlow<UserDetailUiState> =
182
+ getUserUseCase.asFlow(userId) // extension if needed
183
+ .map { result ->
184
+ result.fold(
185
+ onSuccess = { UserDetailUiState.Success(it) },
186
+ onFailure = { UserDetailUiState.Error(it.message ?: "Error") }
187
+ )
188
+ }
189
+ .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), UserDetailUiState.Loading)
190
+
191
+ fun onDeleteClick() {
192
+ viewModelScope.launch {
193
+ deleteUserUseCase(userId).fold(
194
+ onSuccess = { _events.send(Event.NavigateBack) },
195
+ onFailure = { _events.send(Event.ShowError(it.message ?: "Delete failed")) }
196
+ )
197
+ }
198
+ }
199
+ }
200
+ ```
201
+
202
+ ---
203
+
204
+ ## UseCase Package Structure
205
+
206
+ ```
207
+ domain/
208
+ └── usecase/
209
+ ├── user/
210
+ │ ├── GetUserUseCase.kt
211
+ │ ├── GetUsersUseCase.kt
212
+ │ ├── ObserveUsersUseCase.kt
213
+ │ ├── CreateUserUseCase.kt
214
+ │ ├── UpdateUserUseCase.kt
215
+ │ └── DeleteUserUseCase.kt
216
+ ├── order/
217
+ │ ├── PlaceOrderUseCase.kt
218
+ │ ├── CancelOrderUseCase.kt
219
+ │ └── ObserveOrdersUseCase.kt
220
+ └── auth/
221
+ ├── LoginUseCase.kt
222
+ └── LogoutUseCase.kt
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Anti-Patterns
228
+
229
+ - ViewModel calling Repository directly — always go through UseCase
230
+ - UseCase with multiple unrelated responsibilities — split into separate UseCases
231
+ - UseCase holding state — stateless only; state belongs in ViewModel
232
+ - UseCase importing Android framework classes — Domain layer is pure Kotlin
233
+ - Generic `UserUseCase` with many methods — one class, one operation
234
+ - UseCase calling another UseCase — compose at ViewModel level instead
235
+
236
+ ---
237
+
238
+ ## Related Skills
239
+
240
+ - `clean-architecture` — layer structure UseCase sits within
241
+ - `mvvm` — ViewModel calling UseCases
242
+ - `repository-pattern` — Repository interfaces UseCase depends on
243
+ - `domain-modeling` — domain models UseCase operates on
244
+ - `side-effect-management` — handling side effects triggered by UseCases
@@ -0,0 +1,226 @@
1
+ ---
2
+ name: value-object
3
+ description: >
4
+ Value Object pattern for Android domain modeling.
5
+ Load this skill when wrapping primitive types with business meaning,
6
+ implementing validation at construction time, designing immutable value types,
7
+ using Kotlin inline/value classes, or eliminating primitive obsession.
8
+ ---
9
+
10
+ # Value Object
11
+
12
+ ## Overview
13
+
14
+ A Value Object is an immutable object defined entirely by its values, not by identity. Two Value Objects with the same values are equal. They encapsulate validation and domain rules at the point of creation, eliminating invalid states and primitive obsession across the codebase.
15
+
16
+ ---
17
+
18
+ ## Core Principles
19
+
20
+ - **No identity** — equality is based on values, not `id`
21
+ - **Immutable** — all fields are `val`; produce new instances instead of mutating
22
+ - **Self-validating** — invalid values cannot be constructed; validation in `init`
23
+ - **Behavior-rich** — operations and rules belong here, not in the caller
24
+ - **Replaces primitives** — prefer `Email` over `String`, `Money` over `Long`
25
+
26
+ ---
27
+
28
+ ## Kotlin Value Class (Inline Class)
29
+
30
+ ```kotlin
31
+ // ✅ Single-field value object — use @JvmInline value class (zero overhead)
32
+ @JvmInline
33
+ value class Email(val value: String) {
34
+ init {
35
+ require(value.isNotBlank()) { "Email cannot be blank" }
36
+ require(value.contains("@")) { "Invalid email format: $value" }
37
+ require(value.length <= 254) { "Email exceeds maximum length" }
38
+ }
39
+
40
+ val domain: String get() = value.substringAfter("@")
41
+ val local: String get() = value.substringBefore("@")
42
+ }
43
+
44
+ @JvmInline
45
+ value class UserId(val value: String) {
46
+ init { require(value.isNotBlank()) { "UserId cannot be blank" } }
47
+ }
48
+
49
+ @JvmInline
50
+ value class PhoneNumber(val value: String) {
51
+ init {
52
+ val digits = value.filter { it.isDigit() }
53
+ require(digits.length in 10..15) { "Invalid phone number: $value" }
54
+ }
55
+
56
+ val normalized: String get() = value.filter { it.isDigit() }
57
+ }
58
+
59
+ @JvmInline
60
+ value class Percentage(val value: Double) {
61
+ init {
62
+ require(value in 0.0..100.0) { "Percentage must be between 0 and 100: $value" }
63
+ }
64
+
65
+ fun toFraction(): Double = value / 100.0
66
+ }
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Multi-Field Value Object
72
+
73
+ ```kotlin
74
+ // ✅ Multi-field value object — regular data class
75
+ data class Money(
76
+ val amount: Long, // smallest unit (e.g. cents, rials)
77
+ val currency: Currency
78
+ ) {
79
+ init {
80
+ require(amount >= 0) { "Money amount cannot be negative: $amount" }
81
+ }
82
+
83
+ operator fun plus(other: Money): Money {
84
+ require(currency == other.currency) { "Cannot add different currencies" }
85
+ return copy(amount = amount + other.amount)
86
+ }
87
+
88
+ operator fun minus(other: Money): Money {
89
+ require(currency == other.currency) { "Cannot subtract different currencies" }
90
+ require(amount >= other.amount) { "Cannot subtract: result would be negative" }
91
+ return copy(amount = amount - other.amount)
92
+ }
93
+
94
+ operator fun times(factor: Int): Money = copy(amount = amount * factor)
95
+
96
+ fun isZero(): Boolean = amount == 0L
97
+ fun isPositive(): Boolean = amount > 0L
98
+
99
+ companion object {
100
+ fun zero(currency: Currency) = Money(0L, currency)
101
+ fun ofRials(amount: Long) = Money(amount, Currency.IRR)
102
+ }
103
+ }
104
+
105
+ data class Address(
106
+ val street: String,
107
+ val city: String,
108
+ val postalCode: PostalCode,
109
+ val country: Country
110
+ ) {
111
+ init {
112
+ require(street.isNotBlank()) { "Street cannot be blank" }
113
+ require(city.isNotBlank()) { "City cannot be blank" }
114
+ }
115
+
116
+ fun formatted(): String = "$street, $city, ${postalCode.value}, ${country.displayName}"
117
+ }
118
+
119
+ data class DateRange(
120
+ val start: LocalDate,
121
+ val end: LocalDate
122
+ ) {
123
+ init {
124
+ require(!end.isBefore(start)) { "End date must not be before start date" }
125
+ }
126
+
127
+ fun contains(date: LocalDate): Boolean =
128
+ !date.isBefore(start) && !date.isAfter(end)
129
+
130
+ fun overlaps(other: DateRange): Boolean =
131
+ !start.isAfter(other.end) && !end.isBefore(other.start)
132
+
133
+ fun durationDays(): Long = ChronoUnit.DAYS.between(start, end)
134
+ }
135
+ ```
136
+
137
+ ---
138
+
139
+ ## Safe Construction with Result
140
+
141
+ ```kotlin
142
+ // ✅ When validation failure should be handled gracefully (not crash)
143
+ @JvmInline
144
+ value class Username private constructor(val value: String) {
145
+ companion object {
146
+ fun create(value: String): Result<Username> = runCatching {
147
+ require(value.length in 3..20) { "Username must be 3-20 characters" }
148
+ require(value.all { it.isLetterOrDigit() || it == '_' }) {
149
+ "Username can only contain letters, digits, and underscores"
150
+ }
151
+ Username(value)
152
+ }
153
+ }
154
+ }
155
+
156
+ // Usage
157
+ val result = Username.create(input)
158
+ result.fold(
159
+ onSuccess = { username -> proceed(username) },
160
+ onFailure = { error -> showValidationError(error.message) }
161
+ )
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Storage Mapping
167
+
168
+ ```kotlin
169
+ // ✅ TypeConverter for Value Objects in Room
170
+ class ValueObjectConverters {
171
+
172
+ @TypeConverter
173
+ fun fromEmail(email: Email?): String? = email?.value
174
+
175
+ @TypeConverter
176
+ fun toEmail(value: String?): Email? = value?.let { Email(it) }
177
+
178
+ @TypeConverter
179
+ fun fromMoney(money: Money?): String? =
180
+ money?.let { "${it.amount}:${it.currency.name}" }
181
+
182
+ @TypeConverter
183
+ fun toMoney(value: String?): Money? = value?.let {
184
+ val (amount, currency) = it.split(":")
185
+ Money(amount.toLong(), Currency.valueOf(currency))
186
+ }
187
+ }
188
+ ```
189
+
190
+ ---
191
+
192
+ ## Serialization Mapping
193
+
194
+ ```kotlin
195
+ // ✅ Never serialize Value Objects directly — map at the boundary
196
+ data class UserDto(
197
+ @SerialName("id") val id: String,
198
+ @SerialName("email") val email: String, // raw String in DTO
199
+ )
200
+
201
+ // ✅ Map to domain Value Object in mapper
202
+ fun UserDto.toDomain(): User = User(
203
+ id = UserId(id),
204
+ email = Email(email), // construct Value Object here
205
+ ...
206
+ )
207
+ ```
208
+
209
+ ---
210
+
211
+ ## Anti-Patterns
212
+
213
+ - Passing raw `String` for email, phone, ID across the codebase — use Value Objects
214
+ - Validating the same constraint in multiple places — validation belongs in the Value Object constructor
215
+ - Mutable Value Objects (`var` fields) — Value Objects must be immutable
216
+ - Using `@JvmInline value class` for multi-field objects — value classes only support one property
217
+ - Throwing generic `IllegalArgumentException` without a message — always include context in the message
218
+
219
+ ---
220
+
221
+ ## Related Skills
222
+
223
+ - `domain-modeling` — where Value Objects are used within the domain
224
+ - `entity-design` — mapping Value Objects to database columns
225
+ - `dto-mapping` — mapping between raw DTO strings and Value Objects
226
+ - `immutability` — immutability principles in Kotlin