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.
- package/dist/index.js +143 -0
- package/package.json +27 -0
- package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
- package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
- package/skills/Android Platform/Configuration/SKILL.md +201 -0
- package/skills/Android Platform/Filesystem/SKILL.md +216 -0
- package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
- package/skills/Android Platform/Manifest/SKILL.md +226 -0
- package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
- package/skills/Android Platform/Resources/SKILL.md +234 -0
- package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
- package/skills/Android Platform/State Restoration/SKILL.md +210 -0
- package/skills/Architecture/Bounded Context/SKILL.md +207 -0
- package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
- package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
- package/skills/Architecture/Entity Design/SKILL.md +243 -0
- package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
- package/skills/Architecture/MVI/SKILL.md +224 -0
- package/skills/Architecture/MVVM/SKILL.md +198 -0
- package/skills/Architecture/Modularization/SKILL.md +194 -0
- package/skills/Architecture/Offline First/SKILL.md +249 -0
- package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
- package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
- package/skills/Architecture/State Management/SKILL.md +229 -0
- package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
- package/skills/Architecture/Use Case Design/SKILL.md +244 -0
- package/skills/Architecture/Value Object/SKILL.md +226 -0
- package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
- package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
- package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
- package/skills/Build System/Build Cache/SKILL.md +233 -0
- package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
- package/skills/Build System/Build Variant/SKILL.md +215 -0
- package/skills/Build System/Convention Plugin/SKILL.md +288 -0
- package/skills/Build System/Dependency Management/SKILL.md +261 -0
- package/skills/Build System/Gradle/SKILL.md +284 -0
- package/skills/Build System/Incremental Build/SKILL.md +199 -0
- package/skills/Build System/KAPT/SKILL.md +198 -0
- package/skills/Build System/KSP/SKILL.md +263 -0
- package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
- package/skills/Build System/Specialized/C++/SKILL.md +308 -0
- package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
- package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
- package/skills/Build System/Version Catalog/SKILL.md +304 -0
- package/skills/Concurrency/Background Processing/SKILL.md +185 -0
- package/skills/Concurrency/Channel/SKILL.md +207 -0
- package/skills/Concurrency/Coroutine/SKILL.md +200 -0
- package/skills/Concurrency/Flow/SKILL.md +179 -0
- package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
- package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
- package/skills/Concurrency/StateFlow/SKILL.md +175 -0
- package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
- package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
- package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
- package/skills/Core Language/DSL/SKILL.md +186 -0
- package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
- package/skills/Core Language/Immutability/SKILL.md +156 -0
- package/skills/Core Language/KMP/SKILL.md +182 -0
- package/skills/Core Language/Kotlin/SKILL.md +187 -0
- package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
- package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
- package/skills/Core Language/Serialization/SKILL.md +191 -0
- package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
- package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
- package/skills/Data Layer/DAO/SKILL.md +225 -0
- package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
- package/skills/Data Layer/DataStore/SKILL.md +264 -0
- package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
- package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
- package/skills/Data Layer/File Storage/SKILL.md +247 -0
- package/skills/Data Layer/Indexing/SKILL.md +184 -0
- package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
- package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
- package/skills/Data Layer/Migration/SKILL.md +243 -0
- package/skills/Data Layer/Paging/SKILL.md +264 -0
- package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
- package/skills/Data Layer/Room/SKILL.md +244 -0
- package/skills/Data Layer/SQLite/SKILL.md +255 -0
- package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
- package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
- package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
- package/skills/Dependency Injection/Koin/SKILL.md +282 -0
- package/skills/Developer Experience/Detekt/SKILL.md +272 -0
- package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
- package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
- package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
- package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
- package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
- package/skills/Media/Audio/SKILL.md +257 -0
- package/skills/Media/Camera/SKILL.md +229 -0
- package/skills/Media/CameraX/SKILL.md +295 -0
- package/skills/Media/ExoPlayer/SKILL.md +258 -0
- package/skills/Media/Video/SKILL.md +228 -0
- package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
- package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
- package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
- package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
- package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
- package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
- package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
- package/skills/Navigation/Navigation/SKILL.md +215 -0
- package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
- package/skills/Networking/API Contract/SKILL.md +220 -0
- package/skills/Networking/Authentication/SKILL.md +210 -0
- package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
- package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
- package/skills/Networking/Ktor/SKILL.md +219 -0
- package/skills/Networking/Multipart Upload/SKILL.md +213 -0
- package/skills/Networking/OkHttp/SKILL.md +193 -0
- package/skills/Networking/REST/SKILL.md +178 -0
- package/skills/Networking/Rate Limiting/SKILL.md +170 -0
- package/skills/Networking/Retrofit/SKILL.md +241 -0
- package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
- package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
- package/skills/Networking/WebSocket/SKILL.md +224 -0
- package/skills/Observability/Crash Reporting/SKILL.md +219 -0
- package/skills/Observability/Logging/SKILL.md +168 -0
- package/skills/Observability/Metrics/SKILL.md +227 -0
- package/skills/Observability/Structured Logging/SKILL.md +234 -0
- package/skills/Performance/ANR Prevention/SKILL.md +192 -0
- package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
- package/skills/Performance/App Startup/SKILL.md +183 -0
- package/skills/Performance/Baseline Profile/SKILL.md +205 -0
- package/skills/Performance/Battery Optimization/SKILL.md +192 -0
- package/skills/Performance/Benchmark/SKILL.md +182 -0
- package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
- package/skills/Performance/Compose Optimization/SKILL.md +187 -0
- package/skills/Performance/Heap Management/SKILL.md +184 -0
- package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
- package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
- package/skills/Performance/Rendering Performance/SKILL.md +205 -0
- package/skills/Performance/Startup Optimization/SKILL.md +219 -0
- package/skills/Security/Biometric/SKILL.md +224 -0
- package/skills/Security/Certificate Transparency/SKILL.md +158 -0
- package/skills/Security/Cryptography/SKILL.md +244 -0
- package/skills/Security/Encrypted Storage/SKILL.md +273 -0
- package/skills/Security/Frida Detection/SKILL.md +230 -0
- package/skills/Security/Hook Detection/SKILL.md +197 -0
- package/skills/Security/Keystore/SKILL.md +272 -0
- package/skills/Security/Network Security Config/SKILL.md +186 -0
- package/skills/Security/Obfuscation/SKILL.md +226 -0
- package/skills/Security/Proguard/SKILL.md +202 -0
- package/skills/Security/R8/SKILL.md +234 -0
- package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
- package/skills/Security/Root Detection/SKILL.md +220 -0
- package/skills/Security/Secure Networking/SKILL.md +220 -0
- package/skills/System Integration/AlarmManager/SKILL.md +182 -0
- package/skills/System Integration/App Widget/SKILL.md +182 -0
- package/skills/System Integration/Deep Link/SKILL.md +187 -0
- package/skills/System Integration/Foreground Service/SKILL.md +212 -0
- package/skills/System Integration/Notification/SKILL.md +237 -0
- package/skills/System Integration/WorkManager/SKILL.md +256 -0
- package/skills/System Integration/clipboard/SKILL.md +155 -0
- package/skills/System Integration/share-intent/SKILL.md +182 -0
- package/skills/Testing/Compose Testing/SKILL.md +296 -0
- package/skills/Testing/Espresso/SKILL.md +292 -0
- package/skills/Testing/Fake Data/SKILL.md +245 -0
- package/skills/Testing/Integration Testing/SKILL.md +288 -0
- package/skills/Testing/Mocking/SKILL.md +229 -0
- package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
- package/skills/Testing/UI Testing/SKILL.md +293 -0
- package/skills/Testing/Unit Testing/SKILL.md +309 -0
- package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
- package/skills/UI System/Compose/SKILL.md +296 -0
- package/skills/UI System/Compose Animation/SKILL.md +281 -0
- package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
- package/skills/UI System/Compose Navigation/SKILL.md +255 -0
- package/skills/UI System/Compose Performance/SKILL.md +274 -0
- package/skills/UI System/Design System/SKILL.md +217 -0
- package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
- package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
- package/skills/UI System/Loading Strategy/SKILL.md +254 -0
- package/skills/UI System/Material 3/SKILL.md +279 -0
- package/skills/UI System/RTL/SKILL.md +179 -0
- package/src/index.ts +182 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dao
|
|
3
|
+
description: >
|
|
4
|
+
Room DAO design — queries, inserts, updates, deletes, transactions, and Flow.
|
|
5
|
+
Load this skill when writing DAO interfaces, designing SQL queries,
|
|
6
|
+
handling reactive queries with Flow, or performing batch operations.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# DAO (Data Access Object)
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
A DAO is the interface through which the app accesses the Room database. It defines the contract between the app and the database — what queries to run, what data to return, and how writes happen. DAOs are generated at compile time and SQL is verified at compile time.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Core Principles
|
|
17
|
+
|
|
18
|
+
- DAOs are **interfaces or abstract classes** — never concrete classes
|
|
19
|
+
- Use **Flow** for observable queries — use **suspend** for one-shot operations
|
|
20
|
+
- Prefer **upsert** over separate insert + update logic
|
|
21
|
+
- Keep queries **focused** — one query, one purpose
|
|
22
|
+
- Use **@Transaction** for operations that must be atomic
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Basic DAO Structure
|
|
27
|
+
|
|
28
|
+
```kotlin
|
|
29
|
+
@Dao
|
|
30
|
+
interface UserDao {
|
|
31
|
+
|
|
32
|
+
// ✅ Observable query — Flow
|
|
33
|
+
@Query("SELECT * FROM users ORDER BY full_name ASC")
|
|
34
|
+
fun observeAll(): Flow<List<UserEntity>>
|
|
35
|
+
|
|
36
|
+
// ✅ Observable single item
|
|
37
|
+
@Query("SELECT * FROM users WHERE id = :id")
|
|
38
|
+
fun observeById(id: String): Flow<UserEntity?>
|
|
39
|
+
|
|
40
|
+
// ✅ One-shot query
|
|
41
|
+
@Query("SELECT * FROM users WHERE id = :id")
|
|
42
|
+
suspend fun getById(id: String): UserEntity?
|
|
43
|
+
|
|
44
|
+
// ✅ Upsert — insert or replace on conflict
|
|
45
|
+
@Upsert
|
|
46
|
+
suspend fun upsert(user: UserEntity)
|
|
47
|
+
|
|
48
|
+
@Upsert
|
|
49
|
+
suspend fun upsertAll(users: List<UserEntity>)
|
|
50
|
+
|
|
51
|
+
// ✅ Insert with conflict strategy
|
|
52
|
+
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
53
|
+
suspend fun insertIfNotExists(user: UserEntity)
|
|
54
|
+
|
|
55
|
+
// ✅ Update
|
|
56
|
+
@Update
|
|
57
|
+
suspend fun update(user: UserEntity)
|
|
58
|
+
|
|
59
|
+
// ✅ Delete by entity
|
|
60
|
+
@Delete
|
|
61
|
+
suspend fun delete(user: UserEntity)
|
|
62
|
+
|
|
63
|
+
// ✅ Delete by ID
|
|
64
|
+
@Query("DELETE FROM users WHERE id = :id")
|
|
65
|
+
suspend fun deleteById(id: String)
|
|
66
|
+
|
|
67
|
+
// ✅ Delete all
|
|
68
|
+
@Query("DELETE FROM users")
|
|
69
|
+
suspend fun deleteAll()
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Filtered and Sorted Queries
|
|
76
|
+
|
|
77
|
+
```kotlin
|
|
78
|
+
@Dao
|
|
79
|
+
interface UserDao {
|
|
80
|
+
|
|
81
|
+
// ✅ Filter by column
|
|
82
|
+
@Query("SELECT * FROM users WHERE status = :status ORDER BY full_name ASC")
|
|
83
|
+
fun observeByStatus(status: String): Flow<List<UserEntity>>
|
|
84
|
+
|
|
85
|
+
// ✅ Search with LIKE
|
|
86
|
+
@Query("SELECT * FROM users WHERE full_name LIKE '%' || :query || '%' OR email LIKE '%' || :query || '%'")
|
|
87
|
+
fun search(query: String): Flow<List<UserEntity>>
|
|
88
|
+
|
|
89
|
+
// ✅ Filter with IN clause
|
|
90
|
+
@Query("SELECT * FROM users WHERE id IN (:ids)")
|
|
91
|
+
suspend fun getByIds(ids: List<String>): List<UserEntity>
|
|
92
|
+
|
|
93
|
+
// ✅ Count
|
|
94
|
+
@Query("SELECT COUNT(*) FROM users WHERE status = :status")
|
|
95
|
+
fun observeCountByStatus(status: String): Flow<Int>
|
|
96
|
+
|
|
97
|
+
// ✅ Exists check
|
|
98
|
+
@Query("SELECT EXISTS(SELECT 1 FROM users WHERE email = :email)")
|
|
99
|
+
suspend fun existsByEmail(email: String): Boolean
|
|
100
|
+
|
|
101
|
+
// ✅ Pagination with LIMIT and OFFSET
|
|
102
|
+
@Query("SELECT * FROM users ORDER BY created_at DESC LIMIT :limit OFFSET :offset")
|
|
103
|
+
suspend fun getPage(limit: Int, offset: Int): List<UserEntity>
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Joins and Relations
|
|
110
|
+
|
|
111
|
+
```kotlin
|
|
112
|
+
@Dao
|
|
113
|
+
interface UserDao {
|
|
114
|
+
|
|
115
|
+
// ✅ Join query — returns a POJO (not entity)
|
|
116
|
+
@Query("""
|
|
117
|
+
SELECT u.*, o.id as order_id, o.total as order_total
|
|
118
|
+
FROM users u
|
|
119
|
+
INNER JOIN orders o ON u.id = o.user_id
|
|
120
|
+
WHERE u.id = :userId
|
|
121
|
+
""")
|
|
122
|
+
suspend fun getUserWithOrders(userId: String): UserWithOrdersEntity?
|
|
123
|
+
|
|
124
|
+
// ✅ @Transaction for relation queries
|
|
125
|
+
@Transaction
|
|
126
|
+
@Query("SELECT * FROM users WHERE id = :userId")
|
|
127
|
+
suspend fun getUserWithProfile(userId: String): UserWithProfileEntity?
|
|
128
|
+
|
|
129
|
+
// ✅ @Transaction for all @Relation queries
|
|
130
|
+
@Transaction
|
|
131
|
+
@Query("SELECT * FROM users")
|
|
132
|
+
fun observeAllWithOrders(): Flow<List<UserWithOrdersEntity>>
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Transactions
|
|
139
|
+
|
|
140
|
+
```kotlin
|
|
141
|
+
@Dao
|
|
142
|
+
interface UserDao {
|
|
143
|
+
|
|
144
|
+
// ✅ @Transaction — atomic multi-step operation
|
|
145
|
+
@Transaction
|
|
146
|
+
suspend fun replaceAll(users: List<UserEntity>) {
|
|
147
|
+
deleteAll()
|
|
148
|
+
upsertAll(users)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@Transaction
|
|
152
|
+
suspend fun transferOrders(fromUserId: String, toUserId: String) {
|
|
153
|
+
updateOrderOwner(fromUserId, toUserId)
|
|
154
|
+
updateUserOrderCount(fromUserId)
|
|
155
|
+
updateUserOrderCount(toUserId)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@Query("UPDATE orders SET user_id = :toUserId WHERE user_id = :fromUserId")
|
|
159
|
+
suspend fun updateOrderOwner(fromUserId: String, toUserId: String)
|
|
160
|
+
|
|
161
|
+
@Query("UPDATE users SET order_count = (SELECT COUNT(*) FROM orders WHERE user_id = :userId) WHERE id = :userId")
|
|
162
|
+
suspend fun updateUserOrderCount(userId: String)
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Return Types Reference
|
|
169
|
+
|
|
170
|
+
| Operation | Return Type |
|
|
171
|
+
|-----------|------------|
|
|
172
|
+
| Observable query (list) | `Flow<List<Entity>>` |
|
|
173
|
+
| Observable query (single) | `Flow<Entity?>` |
|
|
174
|
+
| Observable count | `Flow<Int>` |
|
|
175
|
+
| One-shot query | `suspend fun`: `Entity?`, `List<Entity>` |
|
|
176
|
+
| Insert | `suspend fun`: `Long` (row id) or `Unit` |
|
|
177
|
+
| Upsert | `suspend fun`: `Unit` |
|
|
178
|
+
| Update | `suspend fun`: `Int` (rows affected) or `Unit` |
|
|
179
|
+
| Delete | `suspend fun`: `Int` (rows affected) or `Unit` |
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## POJOs for Custom Queries
|
|
184
|
+
|
|
185
|
+
```kotlin
|
|
186
|
+
// ✅ POJO for projection queries — not an @Entity
|
|
187
|
+
data class UserSummary(
|
|
188
|
+
val id: String,
|
|
189
|
+
@ColumnInfo(name = "full_name") val name: String,
|
|
190
|
+
@ColumnInfo(name = "order_count") val orderCount: Int
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
@Dao
|
|
194
|
+
interface UserDao {
|
|
195
|
+
@Query("""
|
|
196
|
+
SELECT u.id, u.full_name, COUNT(o.id) as order_count
|
|
197
|
+
FROM users u
|
|
198
|
+
LEFT JOIN orders o ON u.id = o.user_id
|
|
199
|
+
GROUP BY u.id
|
|
200
|
+
ORDER BY order_count DESC
|
|
201
|
+
""")
|
|
202
|
+
fun observeUserSummaries(): Flow<List<UserSummary>>
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Anti-Patterns
|
|
209
|
+
|
|
210
|
+
- Returning entities directly from Repository — always map to domain models
|
|
211
|
+
- Using `LiveData` instead of `Flow` in new code — Flow is preferred
|
|
212
|
+
- Missing `@Transaction` on `@Relation` queries — causes inconsistent data reads
|
|
213
|
+
- Using `suspend` for observable queries — use `Flow` instead
|
|
214
|
+
- Writing business logic in SQL queries — keep queries data-focused
|
|
215
|
+
- Not using `@Upsert` when insert-or-update is needed — avoids conflict handling mistakes
|
|
216
|
+
- Large `IN` clauses with dynamic lists — SQLite has limits; batch if needed
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Related Skills
|
|
221
|
+
- `room` — Room database setup and configuration
|
|
222
|
+
- `migration` — schema changes and migrations
|
|
223
|
+
- `dto-mapping` — Entity ↔ Domain model mapping
|
|
224
|
+
- `repository-pattern` — Repository wrapping DAOs
|
|
225
|
+
- `coroutine` — Dispatchers.IO for DAO operations
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dto-mapping
|
|
3
|
+
description: >
|
|
4
|
+
DTO to domain model mapping patterns for Android.
|
|
5
|
+
Load this skill when writing mappers between API responses (DTOs),
|
|
6
|
+
database entities, and domain models — or when designing the data
|
|
7
|
+
layer boundary in a clean architecture project.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# DTO Mapping
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
DTO (Data Transfer Object) mapping is the translation between data representations across layer boundaries. API responses (DTOs), database rows (Entities), and domain models are separate types — mapping between them keeps each layer independent and testable.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- **Domain models are the language of business logic** — pure Kotlin, no framework annotations
|
|
20
|
+
- **DTOs mirror the API contract** — annotated with `@Serializable`, snake_case fields
|
|
21
|
+
- **Entities mirror the DB schema** — annotated with `@Entity`, `@ColumnInfo`
|
|
22
|
+
- Mapping always happens at the **repository layer** — never in ViewModel or UseCase
|
|
23
|
+
- Mappers are **pure functions** — no side effects, no dependencies
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Layer Boundaries
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
Network (JSON) DTO @Serializable
|
|
31
|
+
↓ mapper
|
|
32
|
+
Domain Layer Domain Model Pure Kotlin data class
|
|
33
|
+
↓ mapper
|
|
34
|
+
Database (SQLite) Entity @Entity, @ColumnInfo
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Domain Model
|
|
40
|
+
|
|
41
|
+
```kotlin
|
|
42
|
+
// ✅ Pure Kotlin — no framework dependencies
|
|
43
|
+
data class User(
|
|
44
|
+
val id: String,
|
|
45
|
+
val name: String,
|
|
46
|
+
val email: String,
|
|
47
|
+
val status: UserStatus,
|
|
48
|
+
val createdAt: Instant
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
enum class UserStatus { ACTIVE, INACTIVE, PENDING }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## DTO (API Response)
|
|
57
|
+
|
|
58
|
+
```kotlin
|
|
59
|
+
// ✅ Mirrors API contract — annotated for serialization
|
|
60
|
+
@Serializable
|
|
61
|
+
data class UserDto(
|
|
62
|
+
@SerialName("user_id") val id: String,
|
|
63
|
+
@SerialName("full_name") val name: String,
|
|
64
|
+
@SerialName("email_address") val email: String,
|
|
65
|
+
@SerialName("account_status") val status: String,
|
|
66
|
+
@SerialName("created_timestamp") val createdAt: Long
|
|
67
|
+
)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Entity (Database Row)
|
|
73
|
+
|
|
74
|
+
```kotlin
|
|
75
|
+
// ✅ Mirrors DB schema — annotated for Room
|
|
76
|
+
@Entity(tableName = "users")
|
|
77
|
+
data class UserEntity(
|
|
78
|
+
@PrimaryKey val id: String,
|
|
79
|
+
@ColumnInfo(name = "full_name") val name: String,
|
|
80
|
+
@ColumnInfo(name = "email") val email: String,
|
|
81
|
+
@ColumnInfo(name = "status") val status: String,
|
|
82
|
+
@ColumnInfo(name = "created_at") val createdAt: Long
|
|
83
|
+
)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Mapper — Extension Functions (Simple)
|
|
89
|
+
|
|
90
|
+
```kotlin
|
|
91
|
+
// ✅ Extension function mapper — concise for simple cases
|
|
92
|
+
fun UserDto.toDomain(): User = User(
|
|
93
|
+
id = id,
|
|
94
|
+
name = name,
|
|
95
|
+
email = email,
|
|
96
|
+
status = UserStatus.valueOf(status.uppercase()),
|
|
97
|
+
createdAt = Instant.ofEpochMilli(createdAt)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
fun UserEntity.toDomain(): User = User(
|
|
101
|
+
id = id,
|
|
102
|
+
name = name,
|
|
103
|
+
email = email,
|
|
104
|
+
status = UserStatus.valueOf(status),
|
|
105
|
+
createdAt = Instant.ofEpochMilli(createdAt)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
fun User.toEntity(): UserEntity = UserEntity(
|
|
109
|
+
id = id,
|
|
110
|
+
name = name,
|
|
111
|
+
email = email,
|
|
112
|
+
status = status.name,
|
|
113
|
+
createdAt = createdAt.toEpochMilli()
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
fun User.toDto(): UserDto = UserDto(
|
|
117
|
+
id = id,
|
|
118
|
+
name = name,
|
|
119
|
+
email = email,
|
|
120
|
+
status = status.name.lowercase(),
|
|
121
|
+
createdAt = createdAt.toEpochMilli()
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Mapper — Class-Based (Testable, Injectable)
|
|
128
|
+
|
|
129
|
+
```kotlin
|
|
130
|
+
// ✅ Class mapper — preferred when mapping has dependencies or is complex
|
|
131
|
+
class UserMapper @Inject constructor(
|
|
132
|
+
private val clock: Clock = Clock.systemUTC()
|
|
133
|
+
) {
|
|
134
|
+
fun toDomain(dto: UserDto): User = User(
|
|
135
|
+
id = dto.id,
|
|
136
|
+
name = dto.name.trim(),
|
|
137
|
+
email = dto.email.lowercase(),
|
|
138
|
+
status = mapStatus(dto.status),
|
|
139
|
+
createdAt = Instant.ofEpochMilli(dto.createdAt)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
fun toDomain(entity: UserEntity): User = User(
|
|
143
|
+
id = entity.id,
|
|
144
|
+
name = entity.name,
|
|
145
|
+
email = entity.email,
|
|
146
|
+
status = UserStatus.valueOf(entity.status),
|
|
147
|
+
createdAt = Instant.ofEpochMilli(entity.createdAt)
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
fun toEntity(domain: User): UserEntity = UserEntity(
|
|
151
|
+
id = domain.id,
|
|
152
|
+
name = domain.name,
|
|
153
|
+
email = domain.email,
|
|
154
|
+
status = domain.status.name,
|
|
155
|
+
createdAt = domain.createdAt.toEpochMilli()
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
private fun mapStatus(raw: String): UserStatus = when (raw.lowercase()) {
|
|
159
|
+
"active" -> UserStatus.ACTIVE
|
|
160
|
+
"inactive" -> UserStatus.INACTIVE
|
|
161
|
+
else -> UserStatus.PENDING
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Repository Using Mapper
|
|
169
|
+
|
|
170
|
+
```kotlin
|
|
171
|
+
class UserRepository @Inject constructor(
|
|
172
|
+
private val userApi: UserApi,
|
|
173
|
+
private val userDao: UserDao,
|
|
174
|
+
private val userMapper: UserMapper
|
|
175
|
+
) {
|
|
176
|
+
|
|
177
|
+
fun observeUsers(): Flow<List<User>> =
|
|
178
|
+
userDao.observeAll()
|
|
179
|
+
.map { entities -> entities.map(userMapper::toDomain) }
|
|
180
|
+
|
|
181
|
+
suspend fun refreshUsers() {
|
|
182
|
+
val dtos = userApi.getUsers()
|
|
183
|
+
val entities = dtos.map(userMapper::toEntity)
|
|
184
|
+
userDao.upsertAll(entities)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
suspend fun createUser(user: User): User {
|
|
188
|
+
val dto = userApi.createUser(user.toDto())
|
|
189
|
+
val entity = userMapper.toEntity(userMapper.toDomain(dto))
|
|
190
|
+
userDao.upsert(entity)
|
|
191
|
+
return userMapper.toDomain(dto)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Handling Nullable / Missing Fields
|
|
199
|
+
|
|
200
|
+
```kotlin
|
|
201
|
+
// ✅ Provide sensible defaults — never return null for required domain fields
|
|
202
|
+
fun UserDto.toDomain(): User = User(
|
|
203
|
+
id = id,
|
|
204
|
+
name = name.takeIf { it.isNotBlank() } ?: "Unknown",
|
|
205
|
+
email = email,
|
|
206
|
+
status = runCatching { UserStatus.valueOf(status.uppercase()) }
|
|
207
|
+
.getOrDefault(UserStatus.PENDING),
|
|
208
|
+
createdAt = if (createdAt > 0) Instant.ofEpochMilli(createdAt) else Instant.now()
|
|
209
|
+
)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Testing Mappers
|
|
215
|
+
|
|
216
|
+
```kotlin
|
|
217
|
+
// ✅ Mappers are pure — easy to unit test without mocks
|
|
218
|
+
class UserMapperTest {
|
|
219
|
+
|
|
220
|
+
private val mapper = UserMapper()
|
|
221
|
+
|
|
222
|
+
@Test
|
|
223
|
+
fun `DTO maps to domain correctly`() {
|
|
224
|
+
val dto = UserDto(
|
|
225
|
+
id = "1",
|
|
226
|
+
name = "Ali Rezaei",
|
|
227
|
+
email = "ali@example.com",
|
|
228
|
+
status = "active",
|
|
229
|
+
createdAt = 1_700_000_000_000L
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
val domain = mapper.toDomain(dto)
|
|
233
|
+
|
|
234
|
+
assertEquals("1", domain.id)
|
|
235
|
+
assertEquals("Ali Rezaei", domain.name)
|
|
236
|
+
assertEquals(UserStatus.ACTIVE, domain.status)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
@Test
|
|
240
|
+
fun `domain maps to entity and back`() {
|
|
241
|
+
val user = User("1", "Ali", "ali@example.com", UserStatus.ACTIVE, Instant.now())
|
|
242
|
+
val entity = mapper.toEntity(user)
|
|
243
|
+
val restored = mapper.toDomain(entity)
|
|
244
|
+
|
|
245
|
+
assertEquals(user.id, restored.id)
|
|
246
|
+
assertEquals(user.status, restored.status)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Anti-Patterns
|
|
254
|
+
|
|
255
|
+
- Using DTOs directly in ViewModel or Compose — couples UI to API contract
|
|
256
|
+
- Using Entities in domain or UI layer — couples business logic to DB schema
|
|
257
|
+
- Mapping in ViewModel — mapping belongs in repository/data layer
|
|
258
|
+
- Not handling unknown enum values — crashes when API adds a new status
|
|
259
|
+
- Giant mapper files — split by domain entity
|
|
260
|
+
- Returning `null` for required domain fields — use defaults or `Result<T>`
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Related Skills
|
|
265
|
+
- `serialization` — DTO serialization setup
|
|
266
|
+
- `room` — Entity design and Room setup
|
|
267
|
+
- `repository-pattern` — repository using mappers
|
|
268
|
+
- `entity-design` — designing domain entities
|
|
269
|
+
- `domain-modeling` — domain model design principles
|