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,261 @@
1
+ ---
2
+ name: cache-strategy
3
+ description: >
4
+ Caching patterns and strategies for Android apps — in-memory cache,
5
+ disk cache, network cache, and cache invalidation.
6
+ Load this skill when designing data caching, deciding cache lifetime,
7
+ implementing offline-first patterns, or managing stale data.
8
+ ---
9
+
10
+ # Cache Strategy
11
+
12
+ ## Overview
13
+
14
+ Caching stores copies of data closer to where it's needed to reduce latency, network usage, and battery drain. A good cache strategy balances freshness (up-to-date data) with performance (fast reads). The right strategy depends on how often data changes and how critical freshness is.
15
+
16
+ ---
17
+
18
+ ## Core Principles
19
+
20
+ - Cache data at the **repository layer** — not in ViewModel or UI
21
+ - Define explicit **TTL (time-to-live)** for every cache — stale data is a bug
22
+ - **Room is the cache** in offline-first architecture — network fills it, UI reads from it
23
+ - Never cache **sensitive data** unencrypted
24
+ - Provide a way to **force-refresh** — users expect a pull-to-refresh to work
25
+
26
+ ---
27
+
28
+ ## Cache Strategies
29
+
30
+ | Strategy | When to Use |
31
+ | -------------------------- | ---------------------------------------------------------- |
32
+ | **Cache-first** | Offline-first, data changes infrequently |
33
+ | **Network-first** | Data must be fresh, connectivity is reliable |
34
+ | **Cache-then-network** | Show cached data immediately, update when network responds |
35
+ | **TTL-based** | Cache valid for N minutes, refresh after expiry |
36
+ | **Stale-while-revalidate** | Show stale data, fetch fresh in background |
37
+
38
+ ---
39
+
40
+ ## Offline-First with Room as Cache
41
+
42
+ ```kotlin
43
+ // ✅ Room is the single source of truth — network populates it
44
+ class UserRepository @Inject constructor(
45
+ private val userDao: UserDao,
46
+ private val userApi: UserApi,
47
+ private val userMapper: UserMapper
48
+ ) {
49
+
50
+ // UI always reads from Room — reactive, always fresh
51
+ fun observeUsers(): Flow<List<User>> =
52
+ userDao.observeAll().map { it.map(userMapper::toDomain) }
53
+
54
+ // Refresh from network → saves to Room → Room emits new value
55
+ suspend fun refreshUsers(): Result<Unit> = runCatching {
56
+ val remote = userApi.getUsers()
57
+ userDao.upsertAll(remote.map(userMapper::toEntity))
58
+ }
59
+ }
60
+ ```
61
+
62
+ ---
63
+
64
+ ## TTL-Based Cache
65
+
66
+ ```kotlin
67
+ // ✅ Track last fetch time — refresh only when stale
68
+ class UserRepository @Inject constructor(
69
+ private val userDao: UserDao,
70
+ private val userApi: UserApi,
71
+ private val prefs: UserPreferencesRepository,
72
+ private val clock: Clock = Clock.systemUTC()
73
+ ) {
74
+ companion object {
75
+ val CACHE_TTL = Duration.ofMinutes(15)
76
+ }
77
+
78
+ fun observeUsers(): Flow<List<User>> = userDao.observeAll()
79
+ .map { it.map(userMapper::toDomain) }
80
+
81
+ suspend fun refreshIfStale() {
82
+ val lastSync = prefs.getLastSyncTime()
83
+ val isStale = lastSync == null ||
84
+ Duration.between(lastSync, clock.instant()) > CACHE_TTL
85
+
86
+ if (isStale) {
87
+ refreshUsers()
88
+ }
89
+ }
90
+
91
+ private suspend fun refreshUsers() {
92
+ val remote = userApi.getUsers()
93
+ userDao.upsertAll(remote.map(userMapper::toEntity))
94
+ prefs.setLastSyncTime(clock.instant())
95
+ }
96
+ }
97
+ ```
98
+
99
+ ---
100
+
101
+ ## In-Memory Cache
102
+
103
+ ```kotlin
104
+ // ✅ Simple in-memory LRU cache for expensive computations
105
+ class ImageThumbnailCache {
106
+ private val cache = object : LruCache<String, Bitmap>(
107
+ (Runtime.getRuntime().maxMemory() / 1024 / 8).toInt() // 1/8 of available memory
108
+ ) {
109
+ override fun sizeOf(key: String, value: Bitmap): Int =
110
+ value.byteCount / 1024
111
+ }
112
+
113
+ fun get(key: String): Bitmap? = cache.get(key)
114
+ fun put(key: String, bitmap: Bitmap) = cache.put(key, bitmap)
115
+ fun evict(key: String) = cache.remove(key)
116
+ fun clear() = cache.evictAll()
117
+ }
118
+
119
+ // ✅ Simple in-memory cache with TTL
120
+ class InMemoryCache<K, V>(private val ttlMs: Long = 5 * 60 * 1000L) {
121
+ private data class Entry<V>(val value: V, val timestamp: Long)
122
+ private val store = ConcurrentHashMap<K, Entry<V>>()
123
+
124
+ fun get(key: K): V? {
125
+ val entry = store[key] ?: return null
126
+ return if (System.currentTimeMillis() - entry.timestamp < ttlMs) {
127
+ entry.value
128
+ } else {
129
+ store.remove(key)
130
+ null
131
+ }
132
+ }
133
+
134
+ fun put(key: K, value: V) {
135
+ store[key] = Entry(value, System.currentTimeMillis())
136
+ }
137
+
138
+ fun invalidate(key: K) = store.remove(key)
139
+ fun invalidateAll() = store.clear()
140
+ }
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Network Cache with OkHttp
146
+
147
+ ```kotlin
148
+ // ✅ OkHttp disk cache — caches HTTP responses
149
+ val cacheDir = File(context.cacheDir, "http_cache")
150
+ val cache = Cache(cacheDir, 10L * 1024 * 1024) // 10 MB
151
+
152
+ val okHttpClient = OkHttpClient.Builder()
153
+ .cache(cache)
154
+ .addInterceptor { chain ->
155
+ val request = chain.request().newBuilder()
156
+ .header("Cache-Control", "max-age=300") // 5 min cache
157
+ .build()
158
+ chain.proceed(request)
159
+ }
160
+ .addNetworkInterceptor { chain ->
161
+ val response = chain.proceed(chain.request())
162
+ response.newBuilder()
163
+ .header("Cache-Control", "public, max-age=300")
164
+ .build()
165
+ }
166
+ .build()
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Cache Invalidation
172
+
173
+ ```kotlin
174
+ // ✅ Invalidate on write — always update cache on mutation
175
+ class UserRepository @Inject constructor(
176
+ private val userDao: UserDao,
177
+ private val userApi: UserApi
178
+ ) {
179
+ // After creating a user — refresh local cache
180
+ suspend fun createUser(user: User): Result<User> = runCatching {
181
+ val created = userApi.createUser(user.toDto())
182
+ userDao.upsert(created.toEntity()) // cache is updated
183
+ created.toDomain()
184
+ }
185
+
186
+ // After deleting — remove from cache
187
+ suspend fun deleteUser(userId: String): Result<Unit> = runCatching {
188
+ userApi.deleteUser(userId)
189
+ userDao.deleteById(userId) // remove from local cache
190
+ }
191
+ }
192
+
193
+ // ✅ Force refresh — ignore cache
194
+ suspend fun forceRefresh() {
195
+ val remote = userApi.getUsers()
196
+ userDao.deleteAll()
197
+ userDao.upsertAll(remote.map { it.toEntity() })
198
+ }
199
+ ```
200
+
201
+ ---
202
+
203
+ ## Pull-to-Refresh Pattern
204
+
205
+ ```kotlin
206
+ @HiltViewModel
207
+ class UserListViewModel @Inject constructor(
208
+ private val repository: UserRepository
209
+ ) : ViewModel() {
210
+
211
+ val users: StateFlow<List<User>> = repository.observeUsers()
212
+ .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
213
+
214
+ private val _isRefreshing = MutableStateFlow(false)
215
+ val isRefreshing: StateFlow<Boolean> = _isRefreshing.asStateFlow()
216
+
217
+ fun onRefresh() {
218
+ viewModelScope.launch {
219
+ _isRefreshing.value = true
220
+ repository.refreshUsers()
221
+ _isRefreshing.value = false
222
+ }
223
+ }
224
+ }
225
+
226
+ // Compose UI
227
+ @Composable
228
+ fun UserListScreen(viewModel: UserListViewModel = hiltViewModel()) {
229
+ val users by viewModel.users.collectAsStateWithLifecycle()
230
+ val isRefreshing by viewModel.isRefreshing.collectAsStateWithLifecycle()
231
+
232
+ SwipeRefresh(
233
+ state = rememberSwipeRefreshState(isRefreshing),
234
+ onRefresh = viewModel::onRefresh
235
+ ) {
236
+ UserList(users = users)
237
+ }
238
+ }
239
+ ```
240
+
241
+ ---
242
+
243
+ ## Anti-Patterns
244
+
245
+ - Caching in ViewModel — lost on rotation or process death
246
+ - No TTL on cache — serves stale data indefinitely
247
+ - Caching sensitive data (tokens, PII) in plaintext — use EncryptedSharedPreferences or keystore
248
+ - No cache invalidation on write — UI shows stale data after mutations
249
+ - Caching everything — large objects in memory cause OOM
250
+ - No force-refresh mechanism — users can't recover from stale state
251
+
252
+ ---
253
+
254
+ ## Related Skills
255
+
256
+ - `repository-pattern` — cache lives in repository layer
257
+ - `room` — Room as the offline-first cache
258
+ - `datastore` — caching simple values across sessions
259
+ - `offline-first` — full offline-first architecture
260
+ - `okhttp` — HTTP-level caching
261
+ - `key-value-store-strategy` — choosing the right cache storage
@@ -0,0 +1,248 @@
1
+ ---
2
+ name: conflict-resolution
3
+ description: >
4
+ Conflict resolution strategies for Android offline-first sync.
5
+ Load this skill when local and remote versions of the same data diverge,
6
+ designing conflict detection, or choosing a resolution strategy.
7
+ ---
8
+
9
+ # Conflict Resolution
10
+
11
+ ## Overview
12
+ A conflict occurs when the same data is modified in both local storage and the remote server before a sync. Conflicts are inevitable in offline-first apps. A conflict resolution strategy defines which version wins — or how to merge them.
13
+
14
+ ---
15
+
16
+ ## Core Principles
17
+
18
+ - **Detect conflicts explicitly** — don't silently overwrite
19
+ - Choose a strategy appropriate to the data type — not all data needs the same policy
20
+ - **Last-Write-Wins (LWW)** is simple but lossy — use where data loss is acceptable
21
+ - **Server-wins** is safe for critical shared data — client changes are discarded
22
+ - **Merge** is complex but preserves both sides — use for collaborative data
23
+ - Always log conflicts — they reveal sync bugs and UX pain points
24
+
25
+ ---
26
+
27
+ ## Conflict Detection
28
+
29
+ ```kotlin
30
+ // ✅ Track version or timestamp on every entity
31
+ @Entity(tableName = "users")
32
+ data class UserEntity(
33
+ @PrimaryKey val id: String,
34
+ val name: String,
35
+ val email: String,
36
+ @ColumnInfo(name = "updated_at") val updatedAt: Long, // local modification time
37
+ @ColumnInfo(name = "server_version") val serverVersion: Int, // server's version counter
38
+ @ColumnInfo(name = "sync_state") val syncState: String = "SYNCED"
39
+ )
40
+
41
+ // ✅ Detect conflict: local changed AND server changed since last sync
42
+ data class ConflictCandidate(
43
+ val local: UserEntity,
44
+ val remote: UserDto
45
+ ) {
46
+ val isConflict: Boolean
47
+ get() = local.syncState == "UPDATED" &&
48
+ remote.serverVersion > local.serverVersion
49
+ }
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Strategy 1: Last-Write-Wins (LWW)
55
+
56
+ ```kotlin
57
+ // ✅ Simple — most recent timestamp wins
58
+ // Use for: user preferences, settings, non-critical data
59
+
60
+ class LWWConflictResolver {
61
+ fun resolve(local: UserEntity, remote: UserDto): UserEntity {
62
+ return if (remote.updatedAt >= local.updatedAt) {
63
+ // Remote wins — overwrite local
64
+ remote.toEntity()
65
+ } else {
66
+ // Local wins — keep local, mark for push
67
+ local.copy(syncState = "UPDATED")
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Strategy 2: Server-Wins
76
+
77
+ ```kotlin
78
+ // ✅ Server is always authoritative
79
+ // Use for: financial data, inventory, shared resources, compliance-sensitive data
80
+
81
+ class ServerWinsConflictResolver {
82
+ fun resolve(local: UserEntity, remote: UserDto): UserEntity {
83
+ // Always use server version — discard local changes
84
+ return remote.toEntity().copy(syncState = "SYNCED")
85
+ }
86
+ }
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Strategy 3: Client-Wins
92
+
93
+ ```kotlin
94
+ // ✅ Local changes always take priority — push to server
95
+ // Use for: user-authored content, personal notes, drafts
96
+
97
+ class ClientWinsConflictResolver {
98
+ fun resolve(local: UserEntity, remote: UserDto): UserEntity {
99
+ // Keep local — it will be pushed to server
100
+ return local.copy(
101
+ serverVersion = remote.serverVersion, // update version tracking
102
+ syncState = "UPDATED" // mark for push
103
+ )
104
+ }
105
+ }
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Strategy 4: Field-Level Merge
111
+
112
+ ```kotlin
113
+ // ✅ Merge non-overlapping field changes
114
+ // Use for: collaborative editing, profile data with independent fields
115
+
116
+ class FieldMergeConflictResolver {
117
+
118
+ fun resolve(base: UserEntity, local: UserEntity, remote: UserDto): UserEntity {
119
+ // Three-way merge: base (last synced) + local changes + remote changes
120
+ return UserEntity(
121
+ id = local.id,
122
+ // If only one side changed a field, take that change
123
+ // If both changed the same field → apply strategy (LWW, server-wins, etc.)
124
+ name = mergeField(
125
+ base = base.name,
126
+ local = local.name,
127
+ remote = remote.name,
128
+ onConflict = { _, remoteVal -> remoteVal } // server-wins for name
129
+ ),
130
+ email = mergeField(
131
+ base = base.email,
132
+ local = local.email,
133
+ remote = remote.email,
134
+ onConflict = { localVal, _ -> localVal } // client-wins for email
135
+ ),
136
+ updatedAt = maxOf(local.updatedAt, remote.updatedAt),
137
+ serverVersion = remote.serverVersion,
138
+ syncState = "SYNCED"
139
+ )
140
+ }
141
+
142
+ private fun <T> mergeField(
143
+ base: T,
144
+ local: T,
145
+ remote: T,
146
+ onConflict: (T, T) -> T
147
+ ): T {
148
+ val localChanged = local != base
149
+ val remoteChanged = remote != base
150
+ return when {
151
+ localChanged && !remoteChanged -> local
152
+ !localChanged && remoteChanged -> remote
153
+ localChanged && remoteChanged -> onConflict(local, remote)
154
+ else -> base // neither changed
155
+ }
156
+ }
157
+ }
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Strategy 5: User-Prompted Resolution
163
+
164
+ ```kotlin
165
+ // ✅ Let the user decide — for important data where automated resolution is risky
166
+
167
+ sealed class ConflictResolutionChoice {
168
+ object KeepLocal : ConflictResolutionChoice()
169
+ object KeepRemote : ConflictResolutionChoice()
170
+ data class Merge(val merged: User) : ConflictResolutionChoice()
171
+ }
172
+
173
+ // In ViewModel — emit conflict event
174
+ data class ConflictData(val local: User, val remote: User)
175
+
176
+ sealed class SyncEvent {
177
+ data class ConflictDetected(val conflict: ConflictData) : SyncEvent()
178
+ object SyncComplete : SyncEvent()
179
+ }
180
+
181
+ @HiltViewModel
182
+ class SyncViewModel @Inject constructor(
183
+ private val syncEngine: UserSyncEngine
184
+ ) : ViewModel() {
185
+
186
+ private val _events = MutableSharedFlow<SyncEvent>()
187
+ val events: SharedFlow<SyncEvent> = _events.asSharedFlow()
188
+
189
+ fun resolveConflict(conflict: ConflictData, choice: ConflictResolutionChoice) {
190
+ viewModelScope.launch {
191
+ syncEngine.applyResolution(conflict, choice)
192
+ }
193
+ }
194
+ }
195
+ ```
196
+
197
+ ---
198
+
199
+ ## Conflict Logging
200
+
201
+ ```kotlin
202
+ // ✅ Log every conflict for debugging and analytics
203
+ class ConflictLogger @Inject constructor(private val logger: Logger) {
204
+
205
+ fun log(local: UserEntity, remote: UserDto, resolution: String) {
206
+ logger.info(
207
+ tag = "ConflictResolution",
208
+ message = buildString {
209
+ append("Conflict for user ${local.id}: ")
210
+ append("local updated_at=${local.updatedAt}, ")
211
+ append("remote updated_at=${remote.updatedAt}, ")
212
+ append("resolution=$resolution")
213
+ }
214
+ )
215
+ }
216
+ }
217
+ ```
218
+
219
+ ---
220
+
221
+ ## Strategy Selection Guide
222
+
223
+ | Data Type | Recommended Strategy |
224
+ |-----------|---------------------|
225
+ | User settings / preferences | Last-Write-Wins |
226
+ | Financial transactions | Server-wins |
227
+ | User-authored content (notes, posts) | Client-wins or User-prompted |
228
+ | Shared collaborative data | Field-level merge |
229
+ | Inventory / stock levels | Server-wins |
230
+ | Profile with independent fields | Field-level merge |
231
+
232
+ ---
233
+
234
+ ## Anti-Patterns
235
+
236
+ - No conflict detection — silently overwriting data, data loss goes unnoticed
237
+ - Always server-wins for user-authored content — users lose their work
238
+ - Always client-wins for shared data — other users' changes are lost
239
+ - Resolving conflicts on the main thread — always use Dispatchers.IO
240
+ - Not logging conflicts — impossible to diagnose sync bugs in production
241
+
242
+ ---
243
+
244
+ ## Related Skills
245
+ - `sync-engine` — sync architecture and scheduling
246
+ - `merge-strategy` — merging list/collection changes
247
+ - `room` — local storage for conflict tracking
248
+ - `workmanager` — background sync execution