Repository navigation
feat(sync): add syncNow that waits for the run it starts - #387
Merged
Merged
Conversation
QuranDataService.syncNow() runs the same manual sync as triggerSync(immediately = true) and suspends until the user can stop waiting for it, so an app can show a pull-to-refresh or "Sync now" spinner for exactly as long as the sync runs. - Returns SyncNowResult: Ended(run) when the run ends, Retrying when its attempt failed and it waits to retry (offline), SignedOut when nothing could sync, Unavailable during a logout, reset or after clear(). - A sync already running runs to its end first; the call waits for the run after it, and calls before that run starts share it. - An immediate trigger now replaces an automatic run that is due but hasn't fired, so the waited run is the manual one; it only shares an immediate run already due, such as a sign-in's. - The scheduler hands each call's waiter to the next run's first attempt; cancel, close and logout complete waiters with Unavailable. Cancelling the caller stops only the wait. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 7, 2026
Review follow-ups for syncNow. - SyncScheduler: every attempt now sends Finished from its finally, so an attempt that throws, or ends with a stray CancellationException, no longer leaves the scheduler stuck "running". Before, later syncNow calls waited for a follow-up run that never started and periodic sync stopped. A cancelled attempt's Finished carries the old generation and is ignored; after close() the trySend fails quietly. - QuranDataService.syncNow: a launch-recovery failure is logged and rethrown, like every other caller of awaitInitialization, instead of being reported as Unavailable. - Tests: a throwing and a stray-cancelled attempt; a sync now during an attempt that will retry gets the follow-up run; a failed and a cancelled launch recovery. reportOf now encodes the trigger, so result assertions tell runs apart. - Docs: Ended never pulls only, SignedOut covers an unknown account id, Unavailable vs Ended(CANCELLED) for a clear is spelled out, and AGENTS.md lists SyncNowResult. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comments and docs only in production code: public KDoc and README keep the contract, internal comments keep only the non-obvious parts (waiter hand-off, first report wins, the finally fallback, the due-timer replacement). The PR's edits to triggerSync's and MANUAL's KDoc are reverted. Tests drop cases that another test already proves; one test per behaviour stays, including the logout-during-request and clear-during-run waiter paths and the throwing-attempt regressions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mohamede1945
added a commit
to quran/mobile-sync-spm
that referenced
this pull request
Nov 7, 2026
0.1.57 adds QuranDataService.syncNow (quran/mobile-sync#387), which the syncNow wrapper calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mohamede1945
added a commit
to quran/mobile-sync-spm
that referenced
this pull request
Nov 7, 2026
* feat: wrap syncNow for a pull-to-refresh that waits for the sync MobileSyncDataService.syncNow() async throws -> MobileSyncNowResult wraps mobile-sync's QuranDataService.syncNow(): it runs the manual sync triggerSync(immediately: true) starts and returns once the user can stop waiting. - MobileSyncNowResult: ended(MobileSyncRun), retrying(failedAt:error: nextAttemptAt:), signedOut, unavailable. It reuses MobileSyncRun and MobileSyncRunError; no Kotlin types are exposed. - Task cancellation throws CancellationError and stops only the wait; a result this package doesn't know throws MobileSyncDataError. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: align syncNow docs with mobile-sync's review fixes - ended never carries a pulled-only run: syncNow's run always pushes. - signedOut also covers an unknown signed-in account id. - unavailable: MobileSync stopped before the run started; stopping during the run ends it cancelled instead. - syncNow() throws MobileSyncDataError (usually .failed(underlying:)) when launch recovery failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: trim syncNow docs and tests to what's needed Docs keep the contract, one line per MobileSyncNowResult case. The failed-run mapping test is dropped: the MobileSyncRun tests cover it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * build: use mobile-sync 0.1.57 0.1.57 adds QuranDataService.syncNow (quran/mobile-sync#387), which the syncNow wrapper calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
mohamede1945
added a commit
to quran/quran-ios
that referenced
this pull request
Nov 7, 2026
mobile-sync-spm 0.4.13 (quran/mobile-sync-spm#53) adds MobileSyncDataService.syncNow on mobile-sync 0.1.57 (quran/mobile-sync#387), which pull-to-refresh calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mohamede1945
added a commit
to quran/quran-ios
that referenced
this pull request
Nov 7, 2026
* feat(sync): add pull-to-refresh to Bookmarks and Notes In the sync build, pulling down on the Bookmarks list or the Notes list runs a sync, and the spinner stays up until it's over (it ended, or it failed and waits to retry). It awaits MobileSync's new syncNow(). - Offered only when signed in; it stays in edit mode so Edit/Done don't rebuild the list. Both view models follow authenticationStateChanges, so a sign-in or sign-out elsewhere updates the control and the banner. - An empty Notes list is pullable when signed in. - Failures are only logged; Settings > Sync shows the details. - NoorUI gains refreshable(isEnabled:action:), documented to be tied only to rarely changing state because toggling it replaces the view. - The no-sync build is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(sync): trim pull-to-refresh comments and tests Comments and tests only: shorter docs on the view models and NoorUI's refreshable(isEnabled:) (keeping the identity caveat and placement rule), and leaner view-model tests with the same coverage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * build(deps): update mobile-sync-spm to 0.4.13 mobile-sync-spm 0.4.13 (quran/mobile-sync-spm#53) adds MobileSyncDataService.syncNow on mobile-sync 0.1.57 (quran/mobile-sync#387), which pull-to-refresh calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
triggerSync(immediately = true)doesn't wait. An app that wants a pull-to-refresh or "Sync now" spinner can't tell when the sync it asked for is over. WatchingsyncStatusdoesn't settle it either: a sync already running finishes first and a follow-up run comes after it, and offline the status keeps alternating between retrying and syncing. quran-ios needs this for pull-to-refresh on Bookmarks and Notes.Change
@NativeCoroutines suspend fun QuranDataService.syncNow(): SyncNowResult. It starts the same manual run astriggerSync(immediately = true): it pushes even while pushes back off, replaces a run waiting to retry, and fetches the profile now. It returns once the caller can stop waiting:Ended(run): the run succeeded, failed with no retries left, or was cancelled. It never only pulls, because the manual run always pushes.runis the same entrysyncHistoryrecords.Retrying(failedAt, error, nextAttemptAt): the attempt failed and the run will retry on its own (offline, say), so the caller isn't held through the backoff.SignedOut: no one is signed in, the signed-in account id is unknown, or the session ended before the run reached the server.Unavailable: a logout or reset is running or began during the run, orclear()ran before it started. Aclear()during the run returnsEndedwith aCANCELLEDrun instead.syncNow()logs the failure and rethrows it, like the other callers ofawaitInitialization.SyncSchedulergives each call a waiter, which it hands to the next run's first attempt. Cancel, close and logout complete every pending waiter withUnavailable. Only the scheduler's single command loop touches the waiters.Finishedfrom itsfinally. Before, such an attempt left the scheduler "running" for good. Periodic sync stopped, and a latersyncNow()would wait for a follow-up run that never started. A cancelled attempt'sFinishedcarries the old generation, so it's ignored.SyncClient.ktis now 533 lines, over the ~500-line guideline. I didn't split it.Tests
SyncClientSyncNowTest(new, 9 tests, virtual time, real engine on an in-memory database, mock HTTP):Ended, and an attempt that will retry →Retrying;SignedOut;Unavailable;Ended(CANCELLED);SyncSchedulerTest, 9 new:Unavailable;finallyfallback.QuranDataServiceLifecycleTest:Unavailable;clear()before the run →Unavailable.OBS-05: a sync now returns the succeeded run, matching the history and the server../gradlew :sync-pipelines:allTests :umbrella:assembleSharedDebugXCFrameworkpasses: 453 JVM, 150 Android host and 150 iOS simulator tests.Two independent Opus reviews ran: one on the first version, one on the follow-up fixes. Neither left a must-fix or should-fix. A third review checked the final trim of comments and tests.
Consumers
MobileSyncDataService.syncNow() async throws -> MobileSyncNowResult. It needs the release that includes this change.syncNow. The only behaviour change is the timer replacement above, which affectstriggerSync(immediately = true)only when an automatic run is already due.🤖 Generated with Claude Code