Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3b9c269
MS-1299 Sync revamp: SyncUseCase input/output signature finalized
alex-vt Jan 26, 2026
1dbb6e2
MS-1299 Sync revamp: SyncCommandsTest
alex-vt Jan 27, 2026
c76c593
MS-1299 Sync revamp: ExecuteSyncCommandUseCase with logic ported from…
alex-vt Jan 27, 2026
6a1df96
MS-1299 Sync revamp: SyncUseCase replaces most of SyncOrchestrator at…
alex-vt Jan 27, 2026
898a74c
MS-1299 Sync revamp: SyncUseCase replaces most of SyncOrchestrator at…
alex-vt Jan 27, 2026
9757289
MS-1299 Sync revamp: RunBlockingEventSyncUseCase adjusted for safer l…
alex-vt Jan 27, 2026
0f97645
MS-1299 Sync revamp: SyncUseCase replaces most of SyncOrchestrator at…
alex-vt Jan 27, 2026
79d3809
MS-1299 Sync revamp: SyncOrchestrator unused functions removed - they…
alex-vt Jan 27, 2026
91d645f
MS-1299 Sync revamp: Explicit matching for command types in SyncUseCase
alex-vt Jan 27, 2026
a3c6f36
MS-1299 Sync revamp: Sync command await restored where SyncOrchestrat…
alex-vt Jan 27, 2026
dac2706
MS-1299 Sync revamp: IO dispatcher for sync command & init block running
alex-vt Jan 27, 2026
2b4912d
MS-1299 Sync revamp: SyncCommandsTest duplication fixed
alex-vt Jan 27, 2026
7fdbe8f
MS-1299 Sync revamp: ktlint
alex-vt Jan 27, 2026
49a0b81
MS-1299 Sync revamp: sync use case accepts optional scope (added for …
alex-vt Jan 27, 2026
6aef01b
MS-1299 Sync revamp: SyncResponse.await() error handling
alex-vt Jan 27, 2026
4dab9c2
MS-1299 Sync revamp: ExecutableSyncCommand payload type hardening
alex-vt Jan 27, 2026
e01420e
MS-1299 Sync revamp: ktlint applied to branch's .kt files
alex-vt Jan 27, 2026
3acdd88
MS-1299 Sync revamp: SyncResponse.await() applied for its improved er…
alex-vt Jan 27, 2026
26dd08e
MS-1299 Sync revamp: SyncCommand type consistency
alex-vt Jan 28, 2026
ae64408
MS-1299 Sync revamp: test function typo cleanup
alex-vt Jan 28, 2026
869035d
Merge branch 'main' into task/sync-revamp-phase2-sync-command
alex-vt Feb 2, 2026
05d303e
MS-1299 Sync revamp: terminology cleanup
alex-vt Feb 2, 2026
67470fd
MS-1299 Sync revamp: observeSyncState, executeOneTime and executeSche…
alex-vt Feb 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepositor
import com.simprints.infra.events.EventRepository
import com.simprints.infra.eventsync.EventSyncManager
import com.simprints.infra.eventsync.status.models.EventSyncWorkerState
import com.simprints.infra.sync.SyncCommand
import com.simprints.infra.sync.OneTime
import com.simprints.infra.sync.ScheduleCommand
import com.simprints.infra.sync.SyncOrchestrator
import com.simprints.infra.sync.usecase.SyncUseCase
import com.simprints.infra.uibase.view.applySystemBarInsets
import com.simprints.infra.uibase.viewbinding.viewBinding
import dagger.hilt.android.AndroidEntryPoint
Expand All @@ -35,14 +35,11 @@ import javax.inject.Inject
@AndroidEntryPoint
internal class DebugFragment : Fragment(R.layout.fragment_debug) {
@Inject
lateinit var sync: SyncUseCase
lateinit var syncOrchestrator: SyncOrchestrator

@Inject
lateinit var eventSyncManager: EventSyncManager

@Inject
lateinit var syncOrchestrator: SyncOrchestrator

@Inject
lateinit var authStore: AuthStore

Expand All @@ -67,7 +64,8 @@ internal class DebugFragment : Fragment(R.layout.fragment_debug) {
super.onViewCreated(view, savedInstanceState)
applySystemBarInsets(view)

sync(eventSync = SyncCommand.ObserveOnly, imageSync = SyncCommand.ObserveOnly)
syncOrchestrator
.observeSyncState()
.map {
it.eventSyncState
}.asLiveData()
Expand All @@ -87,22 +85,18 @@ internal class DebugFragment : Fragment(R.layout.fragment_debug) {
)

binding.logs.append(ssb)
}
}

binding.syncStart.setOnClickListener {
lifecycleScope.launch(dispatcher) {
syncOrchestrator.startEventSync()
}
syncOrchestrator.executeOneTime(OneTime.Events.start())
}

binding.syncStop.setOnClickListener {
syncOrchestrator.stopEventSync()
syncOrchestrator.executeOneTime(OneTime.Events.stop())
}

binding.syncSchedule.setOnClickListener {
lifecycleScope.launch(dispatcher) {
syncOrchestrator.rescheduleEventSync()
}
syncOrchestrator.executeSchedulingCommand(ScheduleCommand.Events.reschedule())
}

binding.clearFirebaseToken.setOnClickListener {
Expand All @@ -127,8 +121,8 @@ internal class DebugFragment : Fragment(R.layout.fragment_debug) {

binding.cleanAll.setOnClickListener {
lifecycleScope.launch(dispatcher) {
syncOrchestrator.stopEventSync()
syncOrchestrator.cancelEventSync()
syncOrchestrator.executeOneTime(OneTime.Events.stop())
syncOrchestrator.executeSchedulingCommand(ScheduleCommand.Events.unschedule())

eventRepository.deleteAll()
eventSyncManager.resetDownSyncInfo()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import com.simprints.feature.dashboard.logout.usecase.LogoutUseCase
import com.simprints.infra.authstore.AuthStore
import com.simprints.infra.config.store.ConfigRepository
import com.simprints.infra.config.store.models.SettingsPasswordConfig
import com.simprints.infra.sync.SyncCommand
import com.simprints.infra.sync.usecase.SyncUseCase
import com.simprints.infra.sync.SyncOrchestrator
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
Expand All @@ -22,7 +21,7 @@ import javax.inject.Inject
@HiltViewModel
internal class LogoutSyncViewModel @Inject constructor(
private val configRepository: ConfigRepository,
sync: SyncUseCase,
syncOrchestrator: SyncOrchestrator,
authStore: AuthStore,
private val logoutUseCase: LogoutUseCase,
) : ViewModel() {
Expand All @@ -36,7 +35,8 @@ internal class LogoutSyncViewModel @Inject constructor(
.asLiveData(viewModelScope.coroutineContext)

val isLogoutWithoutSyncVisibleLiveData: LiveData<Boolean> =
sync(eventSync = SyncCommand.ObserveOnly, imageSync = SyncCommand.ObserveOnly)
syncOrchestrator
.observeSyncState()
.map { syncStatus ->
!syncStatus.eventSyncState.isSyncCompleted() || syncStatus.imageSyncStatus.isSyncing
}.debounce(timeoutMillis = ANTI_JITTER_DELAY_MILLIS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.simprints.core.DispatcherIO
import com.simprints.infra.authlogic.AuthManager
import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepository
import com.simprints.infra.enrolment.records.repository.local.migration.RealmToRoomMigrationFlagsStore
import com.simprints.infra.sync.ScheduleCommand
import com.simprints.infra.sync.SyncOrchestrator
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.runBlocking
Expand All @@ -19,7 +20,7 @@ internal class LogoutUseCase @Inject constructor(
// To prevent a race between wiping data and navigation, this use case must block the executing thread
operator fun invoke() = runBlocking(ioDispatcher) {
// Cancel all background sync
syncOrchestrator.cancelBackgroundWork()
syncOrchestrator.executeSchedulingCommand(ScheduleCommand.Everything.unschedule())
syncOrchestrator.deleteEventSyncInfo()
// sign out the user
authManager.signOut()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ import com.simprints.infra.config.store.ConfigRepository
import com.simprints.infra.config.store.models.ProjectState
import com.simprints.infra.config.store.models.isModuleSelectionAvailable
import com.simprints.infra.recent.user.activity.RecentUserActivityManager
import com.simprints.infra.sync.SyncCommand
import com.simprints.infra.sync.OneTime
import com.simprints.infra.sync.SyncOrchestrator
import com.simprints.infra.sync.usecase.SyncUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
Expand All @@ -43,11 +42,10 @@ import javax.inject.Inject
internal class SyncInfoViewModel @Inject constructor(
private val configRepository: ConfigRepository,
private val authStore: AuthStore,
private val syncOrchestrator: SyncOrchestrator,
private val recentUserActivityManager: RecentUserActivityManager,
private val timeHelper: TimeHelper,
observeSyncInfo: ObserveSyncInfoUseCase,
private val sync: SyncUseCase,
private val syncOrchestrator: SyncOrchestrator,
private val logoutUseCase: LogoutUseCase,
@param:DispatcherIO private val ioDispatcher: CoroutineDispatcher,
) : ViewModel() {
Expand All @@ -57,7 +55,7 @@ internal class SyncInfoViewModel @Inject constructor(
get() = _loginNavigationEventLiveData
private val _loginNavigationEventLiveData = MutableLiveData<LoginParams>()

private val syncStatusFlow = sync(eventSync = SyncCommand.ObserveOnly, imageSync = SyncCommand.ObserveOnly)
private val syncStatusFlow = syncOrchestrator.observeSyncState()
private val eventSyncStateFlow =
syncStatusFlow.map { it.eventSyncState }
private val imageSyncStatusFlow =
Expand Down Expand Up @@ -146,21 +144,19 @@ internal class SyncInfoViewModel @Inject constructor(
}
}

syncOrchestrator.stopEventSync()

val isDownSyncAllowed = !isPreLogoutUpSync && configRepository.getProject()?.state == ProjectState.RUNNING
syncOrchestrator.startEventSync(isDownSyncAllowed)
syncOrchestrator.executeOneTime(OneTime.Events.restart(isDownSyncAllowed))
}
}

fun toggleImageSync() {
viewModelScope.launch {
val isImageSyncing = imageSyncStatusFlow.firstOrNull()?.isSyncing == true
if (isImageSyncing) {
syncOrchestrator.stopImageSync()
syncOrchestrator.executeOneTime(OneTime.Images.stop())
} else {
imageSyncButtonClickFlow.emit(Unit)
syncOrchestrator.startImageSync()
syncOrchestrator.executeOneTime(OneTime.Images.start())
}
}
}
Expand Down Expand Up @@ -218,7 +214,7 @@ internal class SyncInfoViewModel @Inject constructor(
.distinctUntilChanged()
.collect { isEventSyncCompleted ->
if (isEventSyncCompleted) {
syncOrchestrator.startImageSync()
syncOrchestrator.executeOneTime(OneTime.Images.start())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.simprints.infra.config.store.ConfigRepository
import com.simprints.infra.config.store.models.SettingsPasswordConfig
import com.simprints.infra.config.store.models.TokenKeyType
import com.simprints.infra.config.store.tokenization.TokenizationProcessor
import com.simprints.infra.sync.OneTime
import com.simprints.infra.sync.SyncOrchestrator
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -114,8 +115,7 @@ internal class ModuleSelectionViewModel @Inject constructor(
module.copy(name = encryptedName)
}
moduleRepository.saveModules(modules)
syncOrchestrator.stopEventSync()
syncOrchestrator.startEventSync()
syncOrchestrator.executeOneTime(OneTime.Events.restart())
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ import com.simprints.infra.config.store.models.isSimprintsEventDownSyncAllowed
import com.simprints.infra.eventsync.permission.CommCarePermissionChecker
import com.simprints.infra.eventsync.status.models.DownSyncCounts
import com.simprints.infra.network.ConnectivityTracker
import com.simprints.infra.sync.SyncCommand
import com.simprints.infra.sync.SyncOrchestrator
import com.simprints.infra.sync.usecase.ObserveSyncableCountsUseCase
import com.simprints.infra.sync.usecase.SyncUseCase
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
Expand All @@ -45,7 +44,7 @@ internal class ObserveSyncInfoUseCase @Inject constructor(
private val commCarePermissionChecker: CommCarePermissionChecker,
private val observeConfigurationFlow: ObserveConfigurationChangesUseCase,
private val observeSyncableCounts: ObserveSyncableCountsUseCase,
private val sync: SyncUseCase,
private val syncOrchestrator: SyncOrchestrator,
@param:DispatcherBG private val dispatcher: CoroutineDispatcher,
) {
// Since we are not using distinctUntilChanged any emission from combined flows will trigger the main flow as well
Expand All @@ -58,7 +57,7 @@ internal class ObserveSyncInfoUseCase @Inject constructor(
operator fun invoke(isPreLogoutUpSync: Boolean = false): Flow<SyncInfo> = combine(
combinedRefreshSignals(),
authStore.observeSignedInProjectId(),
sync(eventSync = SyncCommand.ObserveOnly, imageSync = SyncCommand.ObserveOnly),
syncOrchestrator.observeSyncState(),
observeSyncableCounts(),
observeConfigurationFlow(),
) { isOnline, projectId, (eventSyncState, imageSyncStatus), counts, (isRefreshing, isProjectRunning, moduleCounts, projectConfig) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import com.simprints.infra.config.store.models.SettingsPasswordConfig
import com.simprints.infra.eventsync.status.models.EventSyncState
import com.simprints.infra.sync.ImageSyncStatus
import com.simprints.infra.sync.SyncStatus
import com.simprints.infra.sync.usecase.SyncUseCase
import com.simprints.infra.sync.SyncOrchestrator
import com.simprints.testtools.common.coroutines.TestCoroutineRule
import com.simprints.testtools.common.livedata.getOrAwaitValue
import io.mockk.*
Expand All @@ -31,7 +31,7 @@ internal class LogoutSyncViewModelTest {
lateinit var configRepository: ConfigRepository

@MockK
lateinit var sync: SyncUseCase
lateinit var syncOrchestrator: SyncOrchestrator

@MockK
lateinit var authStore: AuthStore
Expand Down Expand Up @@ -140,12 +140,12 @@ internal class LogoutSyncViewModelTest {
imageSyncStatus: ImageSyncStatus,
) {
val statusFlow = MutableStateFlow(SyncStatus(eventSyncState = eventSyncState, imageSyncStatus = imageSyncStatus))
every { sync.invoke(any(), any()) } returns statusFlow
every { syncOrchestrator.observeSyncState() } returns statusFlow
}

private fun createViewModel() = LogoutSyncViewModel(
configRepository = configRepository,
sync = sync,
syncOrchestrator = syncOrchestrator,
authStore = authStore,
logoutUseCase = logoutUseCase,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.simprints.infra.authlogic.AuthManager
import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepository
import com.simprints.infra.enrolment.records.repository.local.migration.RealmToRoomMigrationFlagsStore
import com.simprints.infra.sync.ScheduleCommand
import com.simprints.infra.sync.SyncOrchestrator
import com.simprints.testtools.common.coroutines.TestCoroutineRule
import io.mockk.MockKAnnotations
import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.verify
import kotlinx.coroutines.Job
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
Expand Down Expand Up @@ -37,6 +41,7 @@ class LogoutUseCaseTest {
@Before
fun setUp() {
MockKAnnotations.init(this, relaxed = true)
every { syncOrchestrator.executeSchedulingCommand(any()) } returns Job().apply { complete() }

useCase = LogoutUseCase(
syncOrchestrator = syncOrchestrator,
Expand All @@ -51,8 +56,8 @@ class LogoutUseCaseTest {
fun `Fully logs out when called`() = runTest {
useCase.invoke()

verify { syncOrchestrator.executeSchedulingCommand(ScheduleCommand.Everything.unschedule()) }
coVerify {
syncOrchestrator.cancelBackgroundWork()
syncOrchestrator.deleteEventSyncInfo()
authManager.signOut()
flagsStore.clearMigrationFlags()
Expand Down
Loading
Loading