Skip to content

feat: lifecycle-aware streaming (hooks + opt-in Android connector) - #6

Merged
alexheidl merged 1 commit into
mainfrom
feat/lifecycle-aware-sync
Jun 10, 2026
Merged

feat: lifecycle-aware streaming (hooks + opt-in Android connector)#6
alexheidl merged 1 commit into
mainfrom
feat/lifecycle-aware-sync

Conversation

@alexheidl

Copy link
Copy Markdown
Contributor

Makes streaming behave well on mobile: the WebSocket is dropped when the app backgrounds or goes offline and re-established (with an immediate resync) on foreground/reconnect — so we don't burn battery reconnecting a socket the OS will kill, and flags are fresh the moment the user returns.

Implements Option A (hook-based core + thin Android glue): the core gains no new dependency.

Core (featbit-client, no new deps)

  • DataSynchronizer.pause()/resume() (default no-op); StreamingDataSynchronizer closes the socket + heartbeat on pause and reconnects + resyncs (sends last timestamp → server patch) on resume.
  • LifecycleController — state machine: active = foreground && online, with a debounced transition to inactive so brief app-switches/network blips don't churn. Transitions serialized on the client scope.
  • Neutral hooks: FBClient.setForeground(Boolean) / setNetworkAvailable(Boolean). Both default to active, so a client that never calls them streams exactly as before.
  • FBOptions.Builder.backgroundGracePeriod(Duration) (default 20s).

Opt-in module (featbit-client-android)

  • FBLifecycleConnector(context, client) wires the hooks to ProcessLifecycleOwner + ConnectivityManager. Apps that don't use it pay nothing on the core. One-liner:
    FBLifecycleConnector(this, fbClient).start() // e.g. Application.onCreate

Tests

  • LifecycleControllerTest (JVM, virtual time): grace debounce, quick-return-no-churn, pause→resume, network loss/regain. 4/4.
  • FeatBitStreamingE2ETest extended (real FeatBit via Testcontainers): background pauses (a server-side toggle is not received while paused), foreground reconnects + resyncs. Verified locally (~5s test body).
  • Full suite: 36 unit tests, 2 E2E skipped without Docker, 0 failures.

Notes

  • Lifecycle gating primarily benefits streaming; for polling, pause/resume are no-ops (it keeps polling at its interval).
  • FBLifecycleConnector is thin Android glue (registers observers, calls hooks) — verified by compilation + the core path is covered by the E2E via the hooks; Robolectric/instrumented coverage of the connector itself could be a follow-up.

🤖 Generated with Claude Code

Drops the streaming WebSocket when the app backgrounds or goes offline and
re-establishes it (with an immediate resync) on foreground/reconnect, so the SDK
behaves well on mobile without wasting battery on background reconnects.

Core (no new dependencies):
- DataSynchronizer.pause()/resume() (default no-op); StreamingDataSynchronizer
  closes the socket/heartbeat on pause and reconnects + resyncs on resume
- LifecycleController: active = foreground && online, with a debounced (grace)
  transition to inactive; unit-tested on the JVM with virtual time
- Neutral hooks FBClient.setForeground()/setNetworkAvailable() (default active, so
  clients that never call them are unaffected)
- FBOptions.backgroundGracePeriod(Duration) (default 20s)

New opt-in module featbit-client-android:
- FBLifecycleConnector wires the hooks to ProcessLifecycleOwner + ConnectivityManager

Verified against a real FeatBit server: streaming pauses on background (server-side
change not received while paused) and resyncs on foreground.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@alexheidl
alexheidl merged commit ca9f3cd into main Jun 10, 2026
2 checks passed
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