-
Notifications
You must be signed in to change notification settings - Fork 2
[MS-1278] Spike: Sync architecture revamp, part 1 #1537
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…tSyncState - tests
…untEventsUseCase; crude replacements of counter usage
…e counter to be included)
Note: reactive count is candidate record data sources are not implemented yet.
…ocal data sources
…rd data sources. CommCare limitations outlined in a comment.
…n points eliminated in its main block
…nstream users in SyncInfoViewModel
… records from CommCare candidate data source
…rker, to prevent event sync flow from getting stuck
…ount details commented
…on't use the image refs)
There was a problem hiding this 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) |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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()
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 } |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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?
|



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.EventSyncManagerandSyncOrchestratorare large, broadly scoped, essentially each contain loosely related use cases. They are neither repositories nor usecases.Boundaries for the changes - what's intentionally out of scope:
WorkManagerand workers. This is already the optimal foundation for sync.Main changes:
ObserveSyncInfoUseCasemade fully reactive - without suspension points.SyncUseCase, a unified (events + images) sync status reactive observation use case introduced. It is aStateFlow, with up-to-date sync status available as.valuesyncronously from anywhere.CountSyncableUseCase, a unified (events + images) counters reactive observation use case introduced.Additional effort (ongoing, partial):
EventSyncManagerandSyncOrchestratorslimmed down as some of their functions were moved to usecases.ObserveSyncInfoUseCasecomputation 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.SyncUseCasenow 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:
CommCareCandidateRecordDataSourcecode. 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 thatCommCareCandidateRecordDataSourceisn't used anyway for UI-visible counters right now.Not yet done (keeping scope limited) - also marked as
todo MS-1278in the codebase:SyncOrchestratortoSyncUseCase(with helper internal usecases where they fit)SyncOrchestratorinto more specific and focused use cases.EventSyncManagerinto more specific and focused use cases.Testing guidance
Additional work checklist