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"))
}

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
}

Inheritors

Functions

Link copied to clipboard
abstract suspend fun sync(): Flow<SyncProgress>

Synchronizes data and returns a Flow emitting the progress of the sync operation.