feat(react-native): capture warm-start push opens on Android - #4858
feat(react-native): capture warm-start push opens on Android#4858turnipdabeets wants to merge 1 commit into
Conversation
A tap that arrives while the process is alive is delivered to Activity.onNewIntent, which ActivityLifecycleCallbacks does not expose — so posthog-android's integration cannot see it and only the cold-start tap the plugin already reads by hand was captured. ReactActivity forwards onNewIntent to its ActivityEventListeners, so the plugin can observe it directly and hosts need no code of their own. The entry point it forwards to landed in posthog-android 3.62.0, so the floor moves with it; it is deduplicated by google.message_id, so it cannot double-count against the cold-start path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012txiHBCZRkShMdE7V25Jrd
f9e400f to
460dcc2
Compare
posthog-node Compliance ReportDate: 2026-09-09 02:47:26 UTC ✅ All Tests Passed!46/46 tests passed Capture Tests✅ 29/29 tests passed View Details
Feature_Flags Tests✅ 17/17 tests passed View Details
|
posthog-js Compliance ReportDate: 2026-09-09 02:52:02 UTC ✅ All Tests Passed!26/26 tests passed Capture Tests✅ 26/26 tests passed View Details
|
Prompt To Fix All With AI### Issue 1
packages/react-native-plugin/android/src/main/java/com/posthogreactnativeplugin/PosthogReactNativePluginModule.kt:48-50
**Warm-start path lacks tests**
The new listener registration, cleanup, and `onNewIntent` forwarding path have no automated coverage. The existing Android module test only exercises the unrelated `getBoolean` helper, so a future lifecycle or forwarding regression could silently restore the missing warm-start events. Please add a focused test covering registration, intent forwarding, and removal.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "feat(react-native): capture warm-start p..." | Re-trigger Greptile |
| override fun onNewIntent(intent: Intent?) { | ||
| PostHogAndroid.capturePushNotificationOpened(intent) | ||
| } |
There was a problem hiding this comment.
The new listener registration, cleanup, and onNewIntent forwarding path have no automated coverage. The existing Android module test only exercises the unrelated getBoolean helper, so a future lifecycle or forwarding regression could silently restore the missing warm-start events. Please add a focused test covering registration, intent forwarding, and removal.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/react-native-plugin/android/src/main/java/com/posthogreactnativeplugin/PosthogReactNativePluginModule.kt
Line: 48-50
Comment:
**Warm-start path lacks tests**
The new listener registration, cleanup, and `onNewIntent` forwarding path have no automated coverage. The existing Android module test only exercises the unrelated `getBoolean` helper, so a future lifecycle or forwarding regression could silently restore the missing warm-start events. Please add a focused test covering registration, intent forwarding, and removal.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
marandaneto
left a comment
There was a problem hiding this comment.
left a comment otherwise lgtm
| } | ||
|
|
||
| override fun onActivityResult( | ||
| activity: Activity?, |
There was a problem hiding this comment.
blocking: Match React Native’s non-null callback signatures — React Native 0.80+ defines ActivityEventListener in Kotlin with non-null Activity and Intent parameters. Consequently, activity: Activity? here and intent: Intent? on line 48 override neither method, breaking Android compilation for consumers on these versions. Both parameters should be non-null; this also remains compatible with RN 0.79.6. Reproduction: reproduced — python3 check-listener-compat.py compiled the reviewed callback signatures against the published RN 0.86.2 artifact and failed with “overrides nothing” for both methods; making both parameters non-null compiled successfully against RN 0.79.6 and 0.86.2.
Problem
Closes #4857.
On Android, React Native captures
$push_notification_openedfor a cold-start tray tap but never for a tap that arrives while the app is already running. That tap is delivered toActivity.onNewIntent, whichActivityLifecycleCallbacksdoes not expose, so posthog-android's integration cannot see it — and the plugin's own launch-intent read (captureColdStartPushOpenIfNeeded) only ever looks at the intent the Activity was created with. Those opens are silently missing.Changes
ActivityEventListenerand forwardsonNewIntenttoPostHogAndroid.capturePushNotificationOpened(intent).ReactActivityalready forwardsonNewIntentto its listeners, so hosts need no code of their own — unlike a plain Android app, which has to add the call to its own activity.com.posthog:posthog-androidfloor from 3.61.0 to 3.62.0, which is wherePostHogAndroid.capturePushNotificationOpened(intent)landed (feat(push): capture a launch intent the SDK was installed too late to read posthog-android#753). At 3.61.0 the function does not exist.initialize()and removed ininvalidate().Deduplication is posthog-android's, keyed on
google.message_id, so the new path cannot double-count against the cold-start one.Worth a reviewer's attention: the RN example app's
MainActivityusesandroid:launchMode="singleTask"(the React Native template default), which deliversonNewIntentjust assingleTopdoes. So thesingleToprequirement documented for plain Android hosts does not apply to a stock RN app.Testing
Verified on a Pixel 9 emulator with
examples/example-rn-native-plugin, pointed at a local stand-in for the ingestion host so each assertion is made against the exact/batchbody the SDK sent:google.message_idonNewIntent)Falsified rather than just confirmed: with only the
onNewIntentbody reverted and everything else identical, the warm tap captures nothing; restoring it captures once again. The warm event carries the new intent's payload, not a re-read of the cold one.No unit test. The existing
PosthogReactNativePluginModuleTesthasjunitonly — no Robolectric or mocking framework — so covering this would mean pulling one in to fakeReactApplicationContextandActivity. That felt like more surface than the change warrants; happy to add it if you'd rather have it.Release info Sub-libraries affected
Libraries affected
Checklist
If releasing new changes
pnpm changesetto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Built with Claude Code (session), driven by @turnipdabeets. Found while correcting the push-notification open-coverage docs across SDKs (PostHog/posthog.com#19905) — the React Native page says "on Android only cold-start taps are captured", and checking whether that was still true turned up the gap rather than a stale sentence.
Two things were measured rather than assumed:
captureColdStartPushOpenIfNeededalready solves it, with a comment naming the same late-install problem. The docs sentence was accurate; only the warm half was missing.onNewIntentline, as plain Android apps do. They do not:ReactActivityforwards toActivityEventListener, so the plugin can register itself. That is why this closes the gap without a docs-only workaround.Related, same gap closed elsewhere: PostHog/posthog-android#753 (the entry point), PostHog/posthog-flutter#557 (Flutter's
NewIntentListener), PostHog/posthog-ios#792 (the iOS cold-start half).🤖 Generated with Claude Code
https://claude.ai/code/session_012txiHBCZRkShMdE7V25Jrd