Skip to content

Conversation

@alex-vt
Copy link
Contributor

@alex-vt alex-vt commented Jan 16, 2026

JIRA ticket
Will be released in: 2026.2.0

Notable changes

Goal: sync revamp, with reactive usecases.

Reasons:

  • ObserveSyncInfoUseCase (sync info UI computation) is convoluted and has many suspension points, despite being driven by combined flows. This also adds jitter to visualized sync process.
  • EventSyncManager and SyncOrchestrator are large, broadly scoped, essentially each contain loosely related use cases. They are neither repositories nor usecases.
  • Accessing sync state & times is complicated.
  • Gathering counts for syncable entities, such as events and images, is complicated, and there's no reactive observation support for most.

Boundaries for the changes - what's intentionally out of scope:

  • WorkManager and workers. This is already the optimal foundation for sync.
  • UI/UX. Nothing visually new.

Main changes:

  • ObserveSyncInfoUseCase made fully reactive - without suspension points.
  • SyncUseCase, a unified (events + images) sync status reactive observation use case introduced. It is a StateFlow, with up-to-date sync status available as .value syncronously from anywhere.
  • CountSyncableUseCase, a unified (events + images) counters reactive observation use case introduced.

Additional effort (ongoing, partial):

  • EventSyncManager and SyncOrchestrator slimmed down as some of their functions were moved to usecases.
  • ObserveSyncInfoUseCase computation logic is simplified only as far as direct application of the new usecases goes. This is intentional as a waypoint in this ongoing work, to limit the size of changes and for easier code review.
  • SyncUseCase now only observes the sync process. This is intentional, to limit the size of changes. Sync controls are still in the SyncOrchestrator.

Special note about CommCare:

  • Periodic counting is additionally introduced to count enrolment records for CommCare quasi-reactively. It is there to bypass CommCare-side restrictions of how it updates data changes (it doesn't). Comments about that are added in the CommCareCandidateRecordDataSource code. A fix PR can be offered on the CommCare repo to have it notify the observer abount changes, as one of the options. This may need further evaluation of importance, considering that CommCareCandidateRecordDataSource isn't used anyway for UI-visible counters right now.

Not yet done (keeping scope limited) - also marked as todo MS-1278 in the codebase:

  • Move sync controls from SyncOrchestrator to SyncUseCase (with helper internal usecases where they fit)
  • Split the rest of SyncOrchestrator into more specific and focused use cases.
  • Split the rest of EventSyncManager into more specific and focused use cases.
  • Simplify code around the calling points of new use cases where it's not simplified yet.

Testing guidance

  • Sync counters and buttons on dashboard, sync info and logout screens should work correctly.
  • Some usecase calls were changes in other places - full regression testing recommended when the work is completed.

Additional work checklist

  • Effect on other features and security has been considered
  • Design document marked as "In development" (if applicable)
  • External (Gitbook) and internal (Confluence) Documentation is up to date (or ticket created)
  • Test cases in Testiny are up to date (or ticket created)
  • Other teams notified about the changes (if applicable)

…untEventsUseCase; crude replacements of counter usage
Note: reactive count is candidate record data sources are not implemented yet.
…rd data sources. CommCare limitations outlined in a comment.
… records from CommCare candidate data source
…rker, to prevent event sync flow from getting stuck
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 64 out of 64 changed files in this pull request and generated no new comments.

) : ImageLocalDataSource {
private val imageRootPath = "${ctx.filesDir}/$IMAGES_FOLDER"

private val observedImageRefListInvalidation = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is personal preference but I find this name a little confusing. Maybe something like imageRefsChanged?

override suspend fun getNumberOfImagesToUpload(projectId: String): Int = localDataSource.listImages(projectId).count()

override suspend fun observeNumberOfImagesToUpload(projectId: String): Flow<Int> =
localDataSource.observeImageCounts(projectId).distinctUntilChanged()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that it hurts but the local data source already has .distinctUntilChanged()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just a guarantee on this level of abstraction.

import kotlinx.coroutines.flow.map
import javax.inject.Inject

internal class CountEnrolmentRecordsUseCase @Inject constructor(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CountEnrolmentRecordsUseCase() suggests it does counting on demand, not that it creates a flow.
Do we even need a dedicated use case for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to ObserveEnrolmentRecordsCountUseCase. And yes - its specialized purpose is to flatMapLatest the counts for a project ID when the project ID changes. It abstracts the query away from the callers.

) {
internal operator fun invoke(): Flow<Int> = configRepository
.observeProjectConfiguration()
.map { it.projectId }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.
Also, projectId is unused, do we need to "track" it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing as above. The image repository's function takes Project ID, but we are hiding it from the caller's concern, and switching automatically when it changes (tracking).

import kotlinx.coroutines.flow.distinctUntilChanged
import javax.inject.Inject

internal class EventSyncUseCase @Inject constructor(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the above - name would suggest something else (I'd think it starts syncing). And here, too - I'm not convinced the one-liner merits extraction in a use case.

import kotlinx.coroutines.flow.transformLatest
import javax.inject.Inject

internal class ImageSyncUseCase @Inject constructor(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like ObserveImageSyncStatus would convey the job it does better IMHO.

import javax.inject.Singleton

@Singleton
class EventDownSyncCountsRepository @Inject internal constructor(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a repository. More like CountEventsToDownloadUseCase()

* together in a reactive way.
*/
@Singleton
class CountSyncableUseCase @Inject internal constructor(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe ObserveSyncableCountsUseCase would better convey the meaning?

@alex-vt alex-vt requested a review from BurningAXE January 22, 2026 10:58
@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

... do not merge Pull requests that are intentionally on hold

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants