Syncable
interface Syncable
Interface representing a syncable entity.
Repositories that implement this interface can be synchronized in the background by SyncWorker. The sync method should handle fetching remote data and updating the local database while emitting progress updates.
Implementation Pattern
override suspend fun sync(): Flow<SyncProgress> = flow {
emit(SyncProgress(total = 100, current = 0, message = "Starting sync"))
// Fetch from network
val data = networkDataSource.getData()
// Update local database
localDataSource.saveData(data)
emit(SyncProgress(total = 100, current = 100, message = "Sync complete"))
}Content copied to clipboard
See also
For progress reporting
For triggering sync operations
Samples
import dev.atick.core.preferences.data.UserPreferencesDataSource
import dev.atick.core.room.data.LocalDataSource
import dev.atick.core.room.model.SyncAction
import dev.atick.core.utils.suspendRunCatching
import dev.atick.data.model.home.Jetpack
import dev.atick.data.model.home.mapToJetpacks
import dev.atick.data.model.home.toFirebaseJetpack
import dev.atick.data.model.home.toJetpack
import dev.atick.data.model.home.toJetpackEntity
import dev.atick.data.utils.SyncManager
import dev.atick.data.utils.SyncProgress
import dev.atick.firebase.firestore.data.FirebaseDataSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import timber.log.Timber
import javax.inject.Inject
fun main() {
//sampleStart
return flow {
val userId = preferencesDataSource.getUserIdOrThrow()
// Unsynced jetpacks
val unsyncedJetpacks = localDataSource.getUnsyncedJetpacks(userId)
val totalUnsyncedJetpacks = unsyncedJetpacks.size
Timber.d("Syncing $totalUnsyncedJetpacks unsynced jetpacks")
// Push updates to remote
unsyncedJetpacks.forEachIndexed { index, unsyncedJetpack ->
when (unsyncedJetpack.syncAction) {
SyncAction.UPSERT -> {
Timber.d("Syncing create/update jetpack: ${unsyncedJetpack.id}")
firebaseDataSource.createOrUpdateJetpack(
unsyncedJetpack
.toFirebaseJetpack()
.copy(
lastSynced = System.currentTimeMillis(),
),
)
}
SyncAction.DELETE -> {
firebaseDataSource.deleteJetpack(
unsyncedJetpack
.toFirebaseJetpack()
.copy(
lastSynced = System.currentTimeMillis(),
),
)
}
SyncAction.NONE -> {
// Do nothing
}
}
localDataSource.markAsSynced(unsyncedJetpack.id)
emit(
SyncProgress(
total = totalUnsyncedJetpacks,
current = index + 1,
message = "Syncing jetpacks with the cloud",
),
)
}
// Remote jetpacks
val lastSynced = localDataSource.getLatestUpdateTimestamp(userId)
val remoteJetpacks = firebaseDataSource.pullJetpacks(userId, lastSynced)
val totalRemoteJetpacks = remoteJetpacks.size
Timber.d("Syncing $totalRemoteJetpacks remote jetpacks")
// Pull updates from remote
// We pull after pushing local changes to save the local changes to the cloud first
// and avoid accidentally overwriting them with stale data
remoteJetpacks.forEachIndexed { index, remoteJetpack ->
localDataSource.upsertJetpack(remoteJetpack.toJetpackEntity())
emit(
SyncProgress(
total = totalRemoteJetpacks,
current = index + 1,
message = "Fetching jetpacks from the cloud",
),
)
}
}
//sampleEnd
}