fix: refresh preferences and the visible level on foreground, like iOS - #134
Conversation
…ike iOS The phone only pulled preferences when a library level was visited, throttled to once per 60 s, so a sort change made on another device showed up only after navigating — and not at all on returning to the app, signing in, or upgrading to a sync tier. iOS forces a pull past its cooldown on each of those (PreferencesSyncService: bootstrap on launch and every account update, and every foreground). The app now forces a pull whenever it comes to the foreground (ProcessLifecycleOwner, launch included) and whenever the signed-in account or its tier changes to one with cloud sync (PreferencesPullTriggers: login, free → LITE/PRO, a different account; never the value at launch, a downgrade, a logout or a token refresh). A forced pull starts the 60 s cooldown, as a successful pull does on iOS, so the library visit right after it doesn't pull again; PreferenceFetchProcessor still leaves every key with a queued upload alone, so forcing past the queued-upload check is safe.
…reground iOS syncs the visible list's contents when the scene becomes active; Android only fetched a level on navigation or an account change, so a position or change made on another device stayed stale on the phone until the user moved between levels. LibraryScreen now runs the same throttled fetch (60 s per level, skipped while sync jobs are queued) when the process comes to the foreground. It observes the process lifecycle, not the screen's, so returning from another screen inside the app doesn't trigger it.
✅ Claude PR Review —
|
| Finding | Status |
|---|---|
app/src/main/java/com/tortugapower/audiobookplayer/ui/screens/library/LibraryScreen.kt:168 (info) |
✅ verified fixed in 349f3bb |
Converged: nothing new this round, and every earlier finding is settled.
Model claude-opus-5-5 · run log · 0 new · 0 carried over · 1 verified closed · 0 resolved · advisory (a human should still review). Findings are de-duplicated across pushes; an earlier finding closes only when the verification pass judges it against the current code — fixed, no longer applicable, accepted by a maintainer, or a duplicate of a finding reported on this push.
The foreground effect's comment now says it also fires once when LibraryScreen enters composition (addObserver replays ON_START); that only repeats the LaunchedEffect's fetch, absorbed by the shared per-level throttle.
iOS parity for when the phone refreshes synced sort preferences and the visible library level.
What was different
scenePhase == .active)So a sort change or position made on another device stayed stale on the phone until the user moved between levels — not on returning to the app, signing in, or upgrading.
Change
fix: pull synced sort preferences on foreground, login and upgrade, like iOSProcessLifecycleOwner, launch included — addslifecycle-processto:app, already in the catalog and used by:wear), while cloud sync is on.PreferencesPullTriggers.onSyncAccountChange: login, free → LITE/PRO, a different account; never the value at launch (the foreground pull covers it), a downgrade, a logout, PLUS, or a token refresh.SyncStatusManager.markFetchPreferences), as a successful pull does on iOS, so the library visit right after it doesn't pull again. Forcing past the queued-upload check is safe:PreferenceFetchProcessoralready leaves every key with a queued upload alone.fix: re-sync the visible library level when the app returns to the foreground—LibraryScreenruns its existing throttled fetch (60 s per level, skipped while sync jobs are queued) when the process comes to the foreground. Returning from another screen inside the app doesn't trigger it. At the root this includes the existing last-played reconciliation, as a launch already did.Not changed: failed pulls. On Android a failed
fetch_preferencesstays queued and is retried by the engine, so it isn't lost (iOS instead only counts successful pulls toward its cooldown).Verification
Emulator (bp-api36), a build pointed at a local logging stub API (a throwaway worktree with its own
local.properties; RevenueCat's tier reset disabled in that build only, since the emulator has no billing), LITE test account:The login/upgrade trigger couldn't be exercised in-app (Room doesn't observe rows written from outside the process);
PreferencesPullTriggersTestcovers it.Tests.
./gradlew assembleDevDebug testDevDebugUnitTest :core:testDebugUnitTest lintDevDebuggreen (772 tests: 224 app, 68 wear, 480 core). New: a forced pull runs inside the cooldown and with an upload queued, and starts the cooldown (SyncTaskFactoryTest); which account changes trigger (PreferencesPullTriggersTest). Dropping the cooldown mark or the launch-value skip fails exactly the matching tests.