feat: [SDK-2121] implement session replay for react native android#456
feat: [SDK-2121] implement session replay for react native android#456
Conversation
| mobileKey = mobileKey, | ||
| options = ObservabilityOptions( | ||
| serviceName = serviceName, | ||
| sessionBackgroundTimeout = 10.minutes, |
There was a problem hiding this comment.
It should be internal default.
For next PRs: SessionReplay inherit some settings from ObservabilityOptions:
resourceAttributes
sessionBackgroundTimeout
backendUrl
they should be transfered here
There was a problem hiding this comment.
I removed the hard-coded 10.minutes so it will use the internal default, and added a TODO to pass through the other observability options.
|
|
||
| // The context key is a placeholder — the LDClient is offline and never sends it to | ||
| // LaunchDarkly servers. The React Native LDClient manages the real user context. | ||
| val context = LDContext.builder(ContextKind.DEFAULT, "12345").build() |
There was a problem hiding this comment.
Comment is not completelly true
There was a problem hiding this comment.
SessionReplay uses data from context to send data to server. Do you have other way of passing context currently implemented.
There was a problem hiding this comment.
This is a little tricky. Unlike most of the other LD client SDKs, the React Native SDK does not get a context before plugins are registered. That is intended to be changed in the future, but there is not concrete timeline. However, I have added a hook so that when the context changes in JS, it will change here as well. I did this only in Android -- we can fix iOS later. So we have three options:
- Start SessionReplay here, and accept that it will initially have a placeholder value for the context.
- Wait to start SessionReplay until
identifyhas been called. Would we lose replay data before that? - Put this on hold until we refactor the LD RN SDK.
I went with option 1 for now. If you prefer 2, we could do it in this PR or a later one.
| // calls lifecycle.addObserver() internally, which requires the main thread. | ||
| // Using timeout=0 so we don't block the UI — onPluginsReady() fires | ||
| // synchronously during LDClient.init() regardless of the flag-fetch timeout. | ||
| Handler(Looper.getMainLooper()).post { |
There was a problem hiding this comment.
Not sure why State Machine this logic exists here, I hope it wasn't here.
but if needed it has some race conditions.
- currentState is checked outside lock
- later state is being updated outside lock
- state is being modified without @volatile
There was a problem hiding this comment.
Good call. Since the OTel APIs have to be called on the UI Thread anyway, it makes sense to just post these calls there and no state machine is needed.
I believe the locking behavior is now correct. It holds the lock just to grab local copies of the options, and then passes those through as captured variables in the posted lambdas.
There was a problem hiding this comment.
I can try to remove the iOS state machine in a future PR.
...sion-replay/android/src/main/java/com/sessionreplayreactnative/SessionReplayClientAdapter.kt
Outdated
Show resolved
Hide resolved
...sion-replay/android/src/main/java/com/sessionreplayreactnative/SessionReplayClientAdapter.kt
Outdated
Show resolved
Hide resolved
...sion-replay/android/src/main/java/com/sessionreplayreactnative/SessionReplayClientAdapter.kt
Outdated
Show resolved
Hide resolved
abelonogov-ld
left a comment
There was a problem hiding this comment.
Android launching looks good!
...sion-replay/android/src/main/java/com/sessionreplayreactnative/SessionReplayClientAdapter.kt
Show resolved
Hide resolved
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 294a5d5. Configure here.

Summary
This is a fairly straightforward adaptation of the iOS version of the plugin.
How did you test this change?
There are some unit tests, but the primary test was by creating a react native test app, configuring it with my own LD key, and verifying that Android session replay worked the same as iOS session replay.
Are there any deployment considerations?
No.
Note
High Risk
Introduces Android session replay capture and initialization logic (including observability/OTel and replay start/stop), which is privacy- and data-collection-sensitive and could impact app runtime behavior on the main thread.
Overview
Adds Android support for session replay in
@launchdarkly/react-native-ld-session-replay, replacing the previous Android “not supported” stubs with realconfigure,startSessionReplay, andstopSessionReplayimplementations.Introduces a
SessionReplayClientAdapterthat initializes an offlineLDClientwith theObservabilityandSessionReplayplugins, applies replay enable/disable on the main thread, and maps JS options (e.g.,maskTextInputs,maskWebViews,maskLabels,maskImages,serviceName) toReplayOptions/PrivacyProfile.Updates the Android Gradle build to pull in LaunchDarkly Observability + Android SDK deps, adds JUnit5/MockK unit test support, and adds new unit tests for option mapping and pre-config start failure; also replaces the placeholder JS test with assertions that plugin registration calls native
configure+startSessionReplay.Reviewed by Cursor Bugbot for commit a6fc982. Bugbot is set up for automated code reviews on this repo. Configure here.
Related Jira issue: SDK-2121: Implement Session Replay for React Native Android.