fix(android): hysteresis on /healthz reachable signal #82
Reference in New Issue
Block a user
Delete Branch "dev"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Hotfix for the false-positive "Server unreachable" banner that landed in v2026.06.04's re-cut.
The single-failure flip-to-false produced two false-positive cases:
AuthStore.baseUrlloads from Room asynchronously, so the firstrunOnce()can fire againstAuthStore.DEFAULT_BASE_URL(http://localhost:8080) before the real server URL has hydrated. One failure was enough to lock the banner on for the next 5 minutes./api/*to the Go server —/healthznever reaches the handler, so polls fail forever even though every real/api/*call succeeds. User sees "Server unreachable" permanently andOfflineGatedDataSourcestarts throwingOfflineExceptionon every audio cache miss, silently breaking playback of uncached tracks.Fix: require 3 consecutive failures (~15 min at the 5-min poll cadence) before flipping
reachable=false; any single success resets the counter. AddsTimber.w/iat the flip transitions so operator logcat can diagnose genuine outages.Test plan
Offlinebanner shows (driven byConnectivityObserver, instant)./healthzis permanently unreachable but/api/*works → banner appears only after ~15 minutes of confirmed failures; logcat showsTimber.w "flipping reachable=false"at the transition.The single-failure flip-to-false produced two false-positive permanent banner cases: 1. Startup race — AuthStore.baseUrl loads from Room asynchronously, so the first runOnce() can fire against AuthStore.DEFAULT_BASE_URL ("http://localhost:8080") before the real server URL has hydrated. One failure was enough to lock the banner on for the next 5 minutes. 2. Deployments whose reverse proxy routes only /api/* to the Go server — /healthz never reaches the handler, so /healthz polls fail forever even though every real /api/* call succeeds. User sees "Server unreachable" permanently and OfflineGatedDataSource starts throwing OfflineException on every audio cache miss, silently breaking playback of uncached tracks. Now we require 3 consecutive failures (~15 min at the 5-min poll cadence) before flipping reachable=false, and any single success resets the counter. Adds Timber.w/i at the flip transitions so operator logcat can diagnose genuine outages.