feat: lifecycle-aware streaming (hooks + opt-in Android connector) - #6
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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);StreamingDataSynchronizercloses the socket + heartbeat on pause and reconnects + resyncs (sends last timestamp → serverpatch) 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.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 toProcessLifecycleOwner+ConnectivityManager. Apps that don't use it pay nothing on the core. One-liner:Tests
LifecycleControllerTest(JVM, virtual time): grace debounce, quick-return-no-churn, pause→resume, network loss/regain. 4/4.FeatBitStreamingE2ETestextended (real FeatBit via Testcontainers): background pauses (a server-side toggle is not received while paused), foreground reconnects + resyncs. Verified locally (~5s test body).Notes
pause/resumeare no-ops (it keeps polling at its interval).FBLifecycleConnectoris 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