feat(android): survive backgrounding — reconnect on resume + optional foreground service#117
Conversation
… foreground service The app lost its gateway connection whenever Android froze the process (cached-app freezer / Doze): heartbeats stopped, sockets died silently, the automatic reconnect budget burned out while frozen, and nothing recovered on refocus. Notifications also never showed on Android 13+ because POST_NOTIFICATIONS was never declared or requested. accordkit: - GatewaySocket.ensureConnected(): revives a dead socket with a fresh backoff budget (resuming the session when one is held), and probes a nominally-connected socket with an out-of-band heartbeat, force-closing it into the reconnect path when the ACK never arrives. Forwarded on AccordClient; covered by six new gateway tests. app: - AppLifecycleListener in MainWindow calls AccordAuth.ensureConnectedAll() on resume, so every server connection is verified/revived on refocus. - A re-READY (fresh identify gets no event replay) now refetches the history of every open message pane so panes catch up on what was missed. - POST_NOTIFICATIONS declared + requested at startup (Android 13+). background connection (new, opt-in setting): - BackgroundConnectionService: a dataSync foreground service that keeps the process out of the cached-app freezer while backgrounded, so the gateway stays connected and mention notifications keep firing. Driven by a new 'Background connection' settings toggle (Android only) via a Riverpod controller and a small platform channel in MainActivity. Partially addresses #81 (the app-alive half; closed-app push delivery remains a server-side follow-up). https://claude.ai/code/session_01DG54V8d6B9scQEvcU168RQ
…dd tests Four correctness issues found in code review: 1. _probeLiveness() race with heartbeat timer (HIGH): the probe reset _heartbeatAckReceived before awaiting probeTimeout; if the heartbeat timer fired in that window it would see false and close a live connection before the probe ACK arrived. Fix: stop the heartbeat timer at probe start; restart it only if the connection is confirmed alive. Also guard ensureConnected() to skip the probe when still mid-handshake (connecting/resuming) — sending an OOB heartbeat before IDENTIFY/RESUME completes is undefined behaviour. 2. unawaited future (LOW): ensureConnected() discarded the Future returned by _probeLiveness() without unawaited(). 3. BackgroundConnectionService START_STICKY → START_NOT_STICKY (MEDIUM): START_STICKY told Android to auto-restart the foreground service if the process was killed, but without the Flutter engine running there is no gateway to keep alive. START_NOT_STICKY matches the intent: the Riverpod controller re-starts the service when the user returns. 4. reload() missing isLoadingOlder reset (LOW): a concurrent loadOlder could complete after reload and overwrite the fresh page with stale prepended data. Setting isLoadingOlder=false in reload() lets loadOlder see the invalidation. Tests: add 'is a no-op when the handshake is still in progress' and 'heartbeat timer does not race with probe during ack window'. https://claude.ai/code/session_01FKMGHWiXCR9ZtWpoH6J9jR
Code Review — PR #117Reviewed all 19 changed files. Four issues found and fixed in commit cb23c6d. Ranked by severity: 🔴 HIGH —
|
The app lost its gateway connection whenever Android froze the process
(cached-app freezer / Doze): heartbeats stopped, sockets died silently,
the automatic reconnect budget burned out while frozen, and nothing
recovered on refocus. Notifications also never showed on Android 13+
because POST_NOTIFICATIONS was never declared or requested.
accordkit:
backoff budget (resuming the session when one is held), and probes a
nominally-connected socket with an out-of-band heartbeat, force-closing
it into the reconnect path when the ACK never arrives. Forwarded on
AccordClient; covered by six new gateway tests.
app:
on resume, so every server connection is verified/revived on refocus.
history of every open message pane so panes catch up on what was missed.
background connection (new, opt-in setting):
the process out of the cached-app freezer while backgrounded, so the
gateway stays connected and mention notifications keep firing. Driven
by a new 'Background connection' settings toggle (Android only) via a
Riverpod controller and a small platform channel in MainActivity.
Partially addresses #81 (the app-alive half; closed-app push delivery
remains a server-side follow-up).
https://claude.ai/code/session_01DG54V8d6B9scQEvcU168RQ