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,268 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sync-engine
|
|
3
|
+
description: >
|
|
4
|
+
Data synchronization engine design for Android offline-first apps.
|
|
5
|
+
Load this skill when designing how local data syncs with a remote server,
|
|
6
|
+
handling bidirectional sync, managing sync state, or scheduling background sync.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Sync Engine
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
A sync engine coordinates the flow of data between local storage (Room) and a remote server. In offline-first apps, local data is always the source of truth for the UI — the sync engine ensures local and remote stay consistent. It handles connectivity awareness, retry logic, conflict resolution, and sync state tracking.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Core Principles
|
|
17
|
+
|
|
18
|
+
- **Local first, sync second** — UI reads from Room, sync runs in background
|
|
19
|
+
- Sync is **idempotent** — running it twice produces the same result
|
|
20
|
+
- Track sync state explicitly — show sync status in UI where relevant
|
|
21
|
+
- Use **WorkManager** for background sync — survives process death and reboots
|
|
22
|
+
- Handle conflicts explicitly — don't silently overwrite data
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Sync Architecture
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
UI → ViewModel → Repository (Room) ← Sync Engine → Remote API
|
|
30
|
+
↑
|
|
31
|
+
[Source of Truth]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Sync State Tracking
|
|
37
|
+
|
|
38
|
+
```kotlin
|
|
39
|
+
// ✅ Track sync state per entity or globally
|
|
40
|
+
enum class SyncStatus {
|
|
41
|
+
IDLE,
|
|
42
|
+
SYNCING,
|
|
43
|
+
SUCCESS,
|
|
44
|
+
FAILED
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
data class SyncState(
|
|
48
|
+
val status: SyncStatus = SyncStatus.IDLE,
|
|
49
|
+
val lastSyncTime: Instant? = null,
|
|
50
|
+
val error: String? = null
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
// ✅ Persist sync metadata
|
|
54
|
+
@Entity(tableName = "sync_metadata")
|
|
55
|
+
data class SyncMetadataEntity(
|
|
56
|
+
@PrimaryKey val entityType: String, // "users", "orders", etc.
|
|
57
|
+
val lastSyncTime: Long,
|
|
58
|
+
val lastSyncSuccess: Boolean,
|
|
59
|
+
val pendingChanges: Int = 0
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Sync Engine Implementation
|
|
66
|
+
|
|
67
|
+
```kotlin
|
|
68
|
+
class UserSyncEngine @Inject constructor(
|
|
69
|
+
private val userApi: UserApi,
|
|
70
|
+
private val userDao: UserDao,
|
|
71
|
+
private val syncMetadataDao: SyncMetadataDao,
|
|
72
|
+
private val mapper: UserMapper,
|
|
73
|
+
private val clock: Clock = Clock.systemUTC()
|
|
74
|
+
) {
|
|
75
|
+
companion object {
|
|
76
|
+
const val ENTITY_TYPE = "users"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ✅ Pull sync — fetch remote, update local
|
|
80
|
+
suspend fun syncFromRemote(): SyncResult {
|
|
81
|
+
return try {
|
|
82
|
+
val lastSync = syncMetadataDao.getLastSyncTime(ENTITY_TYPE)
|
|
83
|
+
val remoteUsers = if (lastSync != null) {
|
|
84
|
+
userApi.getUsersModifiedSince(lastSync) // delta sync
|
|
85
|
+
} else {
|
|
86
|
+
userApi.getAllUsers() // full sync on first run
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
userDao.upsertAll(remoteUsers.map(mapper::toEntity))
|
|
90
|
+
|
|
91
|
+
val deletedIds = userApi.getDeletedSince(lastSync)
|
|
92
|
+
userDao.deleteByIds(deletedIds)
|
|
93
|
+
|
|
94
|
+
syncMetadataDao.updateSyncTime(ENTITY_TYPE, clock.instant().toEpochMilli())
|
|
95
|
+
SyncResult.Success(remoteUsers.size)
|
|
96
|
+
} catch (e: IOException) {
|
|
97
|
+
SyncResult.NetworkError(e)
|
|
98
|
+
} catch (e: Exception) {
|
|
99
|
+
SyncResult.UnknownError(e)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ✅ Push sync — send local changes to remote
|
|
104
|
+
suspend fun syncToRemote(): SyncResult {
|
|
105
|
+
return try {
|
|
106
|
+
val pendingChanges = userDao.getPendingSync()
|
|
107
|
+
|
|
108
|
+
pendingChanges.forEach { entity ->
|
|
109
|
+
when (entity.syncState) {
|
|
110
|
+
SyncState.CREATED -> userApi.createUser(mapper.toDto(entity))
|
|
111
|
+
SyncState.UPDATED -> userApi.updateUser(entity.id, mapper.toDto(entity))
|
|
112
|
+
SyncState.DELETED -> userApi.deleteUser(entity.id)
|
|
113
|
+
}
|
|
114
|
+
userDao.markSynced(entity.id)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
SyncResult.Success(pendingChanges.size)
|
|
118
|
+
} catch (e: Exception) {
|
|
119
|
+
SyncResult.UnknownError(e)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
sealed class SyncResult {
|
|
125
|
+
data class Success(val count: Int) : SyncResult()
|
|
126
|
+
data class NetworkError(val cause: IOException) : SyncResult()
|
|
127
|
+
data class UnknownError(val cause: Exception) : SyncResult()
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Tracking Local Changes
|
|
134
|
+
|
|
135
|
+
```kotlin
|
|
136
|
+
// ✅ Add sync state column to entity
|
|
137
|
+
@Entity(tableName = "users")
|
|
138
|
+
data class UserEntity(
|
|
139
|
+
@PrimaryKey val id: String,
|
|
140
|
+
val name: String,
|
|
141
|
+
val email: String,
|
|
142
|
+
@ColumnInfo(name = "sync_state") val syncState: String = "SYNCED"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
enum class SyncState { SYNCED, CREATED, UPDATED, DELETED }
|
|
146
|
+
|
|
147
|
+
// ✅ Mark as pending on local write
|
|
148
|
+
@Dao
|
|
149
|
+
interface UserDao {
|
|
150
|
+
@Query("UPDATE users SET sync_state = 'UPDATED' WHERE id = :id")
|
|
151
|
+
suspend fun markPendingUpdate(id: String)
|
|
152
|
+
|
|
153
|
+
@Query("UPDATE users SET sync_state = 'DELETED' WHERE id = :id")
|
|
154
|
+
suspend fun markPendingDelete(id: String)
|
|
155
|
+
|
|
156
|
+
@Query("SELECT * FROM users WHERE sync_state != 'SYNCED'")
|
|
157
|
+
suspend fun getPendingSync(): List<UserEntity>
|
|
158
|
+
|
|
159
|
+
@Query("UPDATE users SET sync_state = 'SYNCED' WHERE id = :id")
|
|
160
|
+
suspend fun markSynced(id: String)
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Background Sync with WorkManager
|
|
167
|
+
|
|
168
|
+
```kotlin
|
|
169
|
+
// ✅ Periodic background sync
|
|
170
|
+
class SyncWorker(
|
|
171
|
+
context: Context,
|
|
172
|
+
params: WorkerParameters
|
|
173
|
+
) : CoroutineWorker(context, params) {
|
|
174
|
+
|
|
175
|
+
@Inject lateinit var syncEngine: UserSyncEngine
|
|
176
|
+
|
|
177
|
+
override suspend fun doWork(): Result {
|
|
178
|
+
return try {
|
|
179
|
+
syncEngine.syncFromRemote()
|
|
180
|
+
syncEngine.syncToRemote()
|
|
181
|
+
Result.success()
|
|
182
|
+
} catch (e: Exception) {
|
|
183
|
+
if (runAttemptCount < 3) Result.retry() else Result.failure()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ✅ Schedule periodic sync
|
|
189
|
+
fun schedulePeriodic(context: Context) {
|
|
190
|
+
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
|
|
191
|
+
"periodic_sync",
|
|
192
|
+
ExistingPeriodicWorkPolicy.KEEP,
|
|
193
|
+
PeriodicWorkRequestBuilder<SyncWorker>(15, TimeUnit.MINUTES)
|
|
194
|
+
.setConstraints(
|
|
195
|
+
Constraints.Builder()
|
|
196
|
+
.setRequiredNetworkType(NetworkType.CONNECTED)
|
|
197
|
+
.build()
|
|
198
|
+
)
|
|
199
|
+
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
|
|
200
|
+
.build()
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ✅ Trigger immediate sync on app foreground
|
|
205
|
+
fun scheduleImmediateSync(context: Context) {
|
|
206
|
+
WorkManager.getInstance(context).enqueueUniqueWork(
|
|
207
|
+
"immediate_sync",
|
|
208
|
+
ExistingWorkPolicy.REPLACE,
|
|
209
|
+
OneTimeWorkRequestBuilder<SyncWorker>()
|
|
210
|
+
.setConstraints(
|
|
211
|
+
Constraints.Builder()
|
|
212
|
+
.setRequiredNetworkType(NetworkType.CONNECTED)
|
|
213
|
+
.build()
|
|
214
|
+
)
|
|
215
|
+
.build()
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Connectivity-Aware Sync
|
|
223
|
+
|
|
224
|
+
```kotlin
|
|
225
|
+
// ✅ Observe connectivity and trigger sync when online
|
|
226
|
+
class ConnectivitySyncTrigger @Inject constructor(
|
|
227
|
+
private val context: Context,
|
|
228
|
+
private val workManager: WorkManager
|
|
229
|
+
) {
|
|
230
|
+
fun observe(): Flow<Boolean> = callbackFlow {
|
|
231
|
+
val cm = context.getSystemService(ConnectivityManager::class.java)
|
|
232
|
+
val callback = object : ConnectivityManager.NetworkCallback() {
|
|
233
|
+
override fun onAvailable(network: Network) {
|
|
234
|
+
trySend(true)
|
|
235
|
+
workManager.enqueueUniqueWork(
|
|
236
|
+
"connectivity_sync",
|
|
237
|
+
ExistingWorkPolicy.REPLACE,
|
|
238
|
+
OneTimeWorkRequestBuilder<SyncWorker>().build()
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
override fun onLost(network: Network) { trySend(false) }
|
|
242
|
+
}
|
|
243
|
+
cm.registerDefaultNetworkCallback(callback)
|
|
244
|
+
awaitClose { cm.unregisterNetworkCallback(callback) }
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Anti-Patterns
|
|
252
|
+
|
|
253
|
+
- Syncing on the main thread — causes ANR
|
|
254
|
+
- No retry logic — transient network errors permanently break sync
|
|
255
|
+
- Overwriting local changes with remote on every sync — data loss
|
|
256
|
+
- No sync state tracking — UI can't show sync status or errors
|
|
257
|
+
- Not handling delete propagation — deleted items reappear after sync
|
|
258
|
+
- Full sync every time — use delta sync (modified_since) for large datasets
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Related Skills
|
|
263
|
+
- `conflict-resolution` — handling data conflicts during sync
|
|
264
|
+
- `merge-strategy` — merging local and remote changes
|
|
265
|
+
- `workmanager` — durable background sync scheduling
|
|
266
|
+
- `room` — local data source for offline-first
|
|
267
|
+
- `offline-first` — full offline-first architecture
|
|
268
|
+
- `repository-pattern` — sync engine integration in repository
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dagger
|
|
3
|
+
description: >
|
|
4
|
+
Dagger 2 dependency injection for Android without Hilt.
|
|
5
|
+
Load this skill when working on a project that uses raw Dagger (not Hilt),
|
|
6
|
+
understanding Hilt's internals, building custom components/subcomponents,
|
|
7
|
+
or migrating from Dagger to Hilt.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Dagger 2
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Dagger 2 is a compile-time DI framework that generates Java code for dependency injection. Hilt is built on top of Dagger and covers most Android use cases. Use raw Dagger when you need custom component hierarchies, non-standard scopes, or are working on a project that predates Hilt.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Dagger generates all DI code at **compile time** — no reflection
|
|
20
|
+
- **Components** are the bridge between the object graph and the consumer
|
|
21
|
+
- **Modules** provide dependencies the constructor can't supply
|
|
22
|
+
- **Subcomponents** inherit the parent's bindings and add their own scope
|
|
23
|
+
- Prefer Hilt for new Android projects — raw Dagger only when needed
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Basic Setup
|
|
28
|
+
|
|
29
|
+
```toml
|
|
30
|
+
[versions]
|
|
31
|
+
dagger = "2.51.1"
|
|
32
|
+
|
|
33
|
+
[libraries]
|
|
34
|
+
dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" }
|
|
35
|
+
dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" }
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```kotlin
|
|
39
|
+
dependencies {
|
|
40
|
+
implementation(libs.dagger)
|
|
41
|
+
ksp(libs.dagger.compiler)
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Component
|
|
48
|
+
|
|
49
|
+
```kotlin
|
|
50
|
+
// ✅ Application-level component
|
|
51
|
+
@Singleton
|
|
52
|
+
@Component(modules = [
|
|
53
|
+
NetworkModule::class,
|
|
54
|
+
DatabaseModule::class,
|
|
55
|
+
RepositoryModule::class
|
|
56
|
+
])
|
|
57
|
+
interface AppComponent {
|
|
58
|
+
|
|
59
|
+
// Expose for injection into Application
|
|
60
|
+
fun inject(app: MyApplication)
|
|
61
|
+
|
|
62
|
+
// Factory — preferred over Builder for constructor params
|
|
63
|
+
@Component.Factory
|
|
64
|
+
interface Factory {
|
|
65
|
+
fun create(@BindsInstance @ApplicationContext context: Context): AppComponent
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ✅ Initialize in Application
|
|
70
|
+
class MyApplication : Application() {
|
|
71
|
+
val appComponent: AppComponent by lazy {
|
|
72
|
+
DaggerAppComponent.factory().create(this)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override fun onCreate() {
|
|
76
|
+
super.onCreate()
|
|
77
|
+
appComponent.inject(this)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Modules
|
|
85
|
+
|
|
86
|
+
```kotlin
|
|
87
|
+
// ✅ @Module — provides dependencies
|
|
88
|
+
@Module
|
|
89
|
+
object NetworkModule {
|
|
90
|
+
|
|
91
|
+
@Provides
|
|
92
|
+
@Singleton
|
|
93
|
+
fun provideOkHttpClient(): OkHttpClient =
|
|
94
|
+
OkHttpClient.Builder().build()
|
|
95
|
+
|
|
96
|
+
@Provides
|
|
97
|
+
@Singleton
|
|
98
|
+
fun provideRetrofit(client: OkHttpClient): Retrofit =
|
|
99
|
+
Retrofit.Builder()
|
|
100
|
+
.baseUrl("https://api.example.com/")
|
|
101
|
+
.client(client)
|
|
102
|
+
.build()
|
|
103
|
+
|
|
104
|
+
@Provides
|
|
105
|
+
@Singleton
|
|
106
|
+
fun provideUserApi(retrofit: Retrofit): UserApi =
|
|
107
|
+
retrofit.create(UserApi::class.java)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ✅ @Binds — for interface → implementation binding (abstract module)
|
|
111
|
+
@Module
|
|
112
|
+
abstract class RepositoryModule {
|
|
113
|
+
|
|
114
|
+
@Binds
|
|
115
|
+
@Singleton
|
|
116
|
+
abstract fun bindUserRepository(impl: UserRepositoryImpl): UserRepository
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Scopes
|
|
123
|
+
|
|
124
|
+
```kotlin
|
|
125
|
+
// ✅ Define custom scopes
|
|
126
|
+
@Scope
|
|
127
|
+
@Retention(AnnotationRetention.RUNTIME)
|
|
128
|
+
annotation class ActivityScope
|
|
129
|
+
|
|
130
|
+
@Scope
|
|
131
|
+
@Retention(AnnotationRetention.RUNTIME)
|
|
132
|
+
annotation class FragmentScope
|
|
133
|
+
|
|
134
|
+
// ✅ Use on component and provided dependency
|
|
135
|
+
@ActivityScope
|
|
136
|
+
@Subcomponent(modules = [ActivityModule::class])
|
|
137
|
+
interface ActivityComponent {
|
|
138
|
+
fun inject(activity: MainActivity)
|
|
139
|
+
|
|
140
|
+
@Subcomponent.Factory
|
|
141
|
+
interface Factory {
|
|
142
|
+
fun create(): ActivityComponent
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Subcomponents
|
|
150
|
+
|
|
151
|
+
```kotlin
|
|
152
|
+
// ✅ Subcomponent inherits parent bindings, adds its own scope
|
|
153
|
+
@ActivityScope
|
|
154
|
+
@Subcomponent(modules = [ActivityModule::class])
|
|
155
|
+
interface ActivityComponent {
|
|
156
|
+
fun inject(activity: MainActivity)
|
|
157
|
+
|
|
158
|
+
@Subcomponent.Factory
|
|
159
|
+
interface Factory {
|
|
160
|
+
fun create(): ActivityComponent
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ✅ Register subcomponent in parent
|
|
165
|
+
@Module(subcomponents = [ActivityComponent::class])
|
|
166
|
+
object AppSubcomponents
|
|
167
|
+
|
|
168
|
+
// ✅ Create subcomponent from parent
|
|
169
|
+
class MainActivity : AppCompatActivity() {
|
|
170
|
+
private lateinit var activityComponent: ActivityComponent
|
|
171
|
+
|
|
172
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
173
|
+
activityComponent = (application as MyApplication)
|
|
174
|
+
.appComponent
|
|
175
|
+
.activityComponentFactory()
|
|
176
|
+
.create()
|
|
177
|
+
activityComponent.inject(this)
|
|
178
|
+
super.onCreate(savedInstanceState)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Constructor Injection
|
|
186
|
+
|
|
187
|
+
```kotlin
|
|
188
|
+
// ✅ Same as Hilt — @Inject on constructor
|
|
189
|
+
class UserRepository @Inject constructor(
|
|
190
|
+
private val userDao: UserDao,
|
|
191
|
+
private val userApi: UserApi
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
class UserMapper @Inject constructor()
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Qualifiers
|
|
200
|
+
|
|
201
|
+
```kotlin
|
|
202
|
+
// ✅ Distinguish multiple bindings of the same type
|
|
203
|
+
@Qualifier
|
|
204
|
+
@Retention(AnnotationRetention.BINARY)
|
|
205
|
+
annotation class BaseUrl
|
|
206
|
+
|
|
207
|
+
@Module
|
|
208
|
+
object NetworkModule {
|
|
209
|
+
|
|
210
|
+
@Provides
|
|
211
|
+
@Singleton
|
|
212
|
+
@BaseUrl
|
|
213
|
+
fun provideBaseUrl(): String = "https://api.example.com/"
|
|
214
|
+
|
|
215
|
+
@Provides
|
|
216
|
+
@Singleton
|
|
217
|
+
fun provideRetrofit(@BaseUrl baseUrl: String): Retrofit =
|
|
218
|
+
Retrofit.Builder().baseUrl(baseUrl).build()
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Lazy and Provider Injection
|
|
225
|
+
|
|
226
|
+
```kotlin
|
|
227
|
+
// ✅ Lazy<T> — defer initialization until first use
|
|
228
|
+
class HeavyFeature @Inject constructor(
|
|
229
|
+
private val heavyDep: Lazy<HeavyDependency>
|
|
230
|
+
) {
|
|
231
|
+
fun use() {
|
|
232
|
+
heavyDep.get().doWork() // initialized on first call
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ✅ Provider<T> — get a new instance each time
|
|
237
|
+
class RequestFactory @Inject constructor(
|
|
238
|
+
private val requestProvider: Provider<NetworkRequest>
|
|
239
|
+
) {
|
|
240
|
+
fun createRequest(): NetworkRequest = requestProvider.get()
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Migration to Hilt
|
|
247
|
+
|
|
248
|
+
```kotlin
|
|
249
|
+
// Hilt replaces most Dagger boilerplate:
|
|
250
|
+
|
|
251
|
+
// Dagger → Hilt equivalents
|
|
252
|
+
@Component(modules = [...])
|
|
253
|
+
interface AppComponent → @HiltAndroidApp on Application
|
|
254
|
+
|
|
255
|
+
@ActivityScope @Subcomponent(...)
|
|
256
|
+
interface ActivityComponent → @AndroidEntryPoint on Activity
|
|
257
|
+
|
|
258
|
+
appComponent.inject(this) → Removed (automatic)
|
|
259
|
+
|
|
260
|
+
@Component.Factory / @Component.Builder → Removed (Hilt manages)
|
|
261
|
+
|
|
262
|
+
// Keep raw Dagger when:
|
|
263
|
+
// - Custom component hierarchy beyond what Hilt provides
|
|
264
|
+
// - Non-Android modules (pure Kotlin/Java libraries)
|
|
265
|
+
// - Existing codebase that's too large to migrate at once
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Anti-Patterns
|
|
271
|
+
|
|
272
|
+
- Field injection in non-Android classes — use constructor injection
|
|
273
|
+
- Creating components outside of Application — breaks the object graph
|
|
274
|
+
- Providing mutable singletons — hidden shared state causes bugs
|
|
275
|
+
- Deep subcomponent hierarchies — prefer Hilt's flat component model
|
|
276
|
+
- Not using `@Binds` for interface bindings — `@Provides` works but is less efficient
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Related Skills
|
|
281
|
+
- `hilt` — Hilt (recommended for Android)
|
|
282
|
+
- `koin` — alternative runtime DI
|
|
283
|
+
- `annotation-processing` — KSP for Dagger code generation
|