Skip to content

fix: refresh preferences and the visible level on foreground, like iOS - #134

Merged
GianniCarlo merged 3 commits into
developfrom
fix/preferences-pull-ios-parity
Sep 30, 2026
Merged

GianniCarlo merged 3 commits into
developfrom
fix/preferences-pull-ios-parity

Conversation

@GianniCarlo

Copy link
Copy Markdown
Contributor

iOS parity for when the phone refreshes synced sort preferences and the visible library level.

What was different

iOS Android phone (before)
Regular preferences pull every list sync, 60 s cooldown every library level visited, 60 s cooldown (since #132)
Forced pull (skips the cooldown) launch, every account update (login, free → paid), every foreground none
Visible level's contents on foreground synced (scenePhase == .active) not fetched

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

  1. fix: pull synced sort preferences on foreground, login and upgrade, like iOS
    • Forced pull whenever the app comes to the foreground (ProcessLifecycleOwner, launch included — adds lifecycle-process to :app, already in the catalog and used by :wear), while cloud sync is on.
    • Forced pull when the signed-in account or its tier changes to one with cloud sync — 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.
    • A forced pull starts the 60 s cooldown (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: PreferenceFetchProcessor already leaves every key with a queued upload alone.
  2. fix: re-sync the visible library level when the app returns to the foreground — LibraryScreen runs 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_preferences stays 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:

Scenario Preferences pull Contents fetch (root)
Cold launch 1 (the level visit's regular pull skipped by the cooldown the forced pull started) 1 (launch and foreground effects share the throttle)
Foreground 17 s later 1 none (throttled)
Foreground 72 s after launch 1 1

The login/upgrade trigger couldn't be exercised in-app (Room doesn't observe rows written from outside the process); PreferencesPullTriggersTest covers it.

Tests. ./gradlew assembleDevDebug testDevDebugUnitTest :core:testDebugUnitTest lintDevDebug green (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.

…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.
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

✅ Claude PR Review — PASS

The PR matches iOS on when the phone refreshes synced sort preferences and the visible library level. BookPlayerApplication forces a preferences pull whenever the app comes to the foreground, and when the account or its tier changes to LITE/PRO (PreferencesPullTriggers, a Compose-free :core helper with unit tests). LibraryScreen re-runs its throttled contents fetch on the same foreground signal. A forced pull now starts the 60 s cooldown (SyncStatusManager.markFetchPreferences), which also affects the watch app's forced pull-to-refresh; that is harmless and consistent. Skipping the queued-upload check is safe because PreferenceFetchProcessor still leaves any key with a queued upload alone. The trigger only passes LITE/PRO (TaskAccessPolicy.canAccessSyncService), work runs on appScope with IO dispatch, there is no module-boundary problem, and the earlier inaccurate LifecycleEventEffect comment has been reworded to describe what the effect actually does.

Findings: no findings

Previously raised

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.
@GianniCarlo
GianniCarlo merged commit 4de5500 into develop Sep 30, 2026
3 checks passed

This branch was successfully deployed

1 active deployment
reviewer — 349f3bb7 Deployed Sep 30, 2026 by GianniCarlo via review #456
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant