fix(push): capture $push_notification_opened on Android cold and warm start - #557
Draft
turnipdabeets wants to merge 5 commits into
Draft
fix(push): capture $push_notification_opened on Android cold and warm start#557turnipdabeets wants to merge 5 commits into
turnipdabeets wants to merge 5 commits into
Conversation
Plugin registration runs inside Activity.onCreate, but the native SDK reads the tray intent in onActivityCreated — which the framework has already dispatched by then. onStart and onResume have also run before Dart reaches setup(), so no lifecycle callback for the launch Activity is ever observed. Hands the Activity's intent to PostHogAndroid.capturePushNotificationOpened from both setup() and onAttachedToActivity: neither hook alone covers both configurations, since onAttachedToEngine precedes onAttachedToActivity. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012txiHBCZRkShMdE7V25Jrd
5 tasks
Contributor
posthog-flutter Compliance ReportDate: 2026-09-02 15:52:18 UTC ✅ All Tests Passed!45/45 tests passed Capture Tests✅ 29/29 tests passed View Details
Feature_Flags Tests✅ 16/16 tests passed View Details
|
A tap on a live process is delivered to Activity.onNewIntent, which ActivityLifecycleCallbacks does not expose — so posthog-android cannot observe it and this plugin is the only layer that can. Registers a NewIntentListener on attach and re-attach, removed on both detach paths. The native message-id dedupe keeps a redelivered intent from counting twice. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012txiHBCZRkShMdE7V25Jrd
ktlint requires lexicographic import order; the new import broke it. The cold- and warm-start fixes ship together, so they read as one changelog entry rather than two adjacent bullets about the same event. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012txiHBCZRkShMdE7V25Jrd
The plugin now captures them, and the manual API is not deduplicated against it — so an app following the documented onMessageOpenedApp pattern would count every warm tap twice. Bumped to minor: existing integrations must remove that call. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012txiHBCZRkShMdE7V25Jrd
…Message The snippet referenced an undeclared `notification` whose `payload` would not type-check. getInitialMessage is the terminated-state companion to onMessageOpenedApp and double-counts the same way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012txiHBCZRkShMdE7V25Jrd
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.
💡 Motivation and Context
Fixes #558 —
$push_notification_openedis never captured on a cold launch from a notification tap. (#556 fixes the iOS half, #555; the two have separate native release gates, so they ship separately.)The native SDK reads the tray intent in
onActivityCreated, but plugin registration happens insideActivity.onCreate, after the framework has already dispatched that callback. Measured in the example app:Every lifecycle callback for the launch Activity is missed.
AUTO_INITdoes not rescue it either — registration is still insideonCreate— which is where this differs from iOS, whereAUTO_INITdoes win the race.posthog-android3.62.0 forPostHogAndroid.capturePushNotificationOpened(intent). The floor here is already raised to[3.62.0,4.0.0), so merging before that releases breaks every user's Android build at dependency resolution. Draft until then.Changes
Activity.onNewIntent, whichActivityLifecycleCallbacksdoes not expose — so the native SDK cannot see it and this plugin is the only layer that can. ANewIntentListeneris registered on attach and re-attach, removed on both detach paths.capturePushNotificationOpenedfromFirebaseMessaging.onMessageOpenedApp/getInitialMessage(); that call is not deduplicated against the automatic capture, so keeping it now double-counts. Hence theminorbump.setupPostHog()andonAttachedToActivity, because neither alone covers both configurations:onAttachedToEngine(which runsinitPlugin) always precedesonAttachedToActivity, so theAUTO_INITpath has no Activity yet, while on the Dart path the Activity is attached long beforesetup()runs. Whichever precondition is satisfied last does the work; the native message-id dedupe makes the double call safe.posthog-androidfloor to[3.62.0,4.0.0).💚 How did you test it?
On device (Pixel 9 emulator, example app against a local
posthog-androidbuild):google.message_idextra → 1 capture (was 0).AUTO_INITapp (Dartsetup()commented out) — 0 captures without theonAttachedToActivityhook, 1 with it. This is why the hook is called from both places; an earlier test that left the Dartsetup()in place passed either way and masked the gap.flutter analyzeclean. The unit tests live in PostHog/posthog-android#753.Testing
Verified on a Pixel 9 emulator against a local
posthog-androidbuild, with events confirmed in a real PostHog project (not just device logs):AUTO_INITapp, no Dartsetup()onAttachedToActivityhook, 1 with itThe
AUTO_INITrow is why the hand-over is called from two places: an earlier test that left the Dartsetup()in place passed either way and masked the gap.📝 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. Nearly all the logic is in PostHog/posthog-android#753; this side is the trigger.
No unit test here:
PostHogAndroid.capturePushNotificationOpenedis a companion function without@JvmStatic, somockStaticcannot intercept it and asserting the hand-over would need a new production test seam. TheAUTO_INITdevice run above is the honest verification.Also closes the warm-start gap: a tap that arrives while the app is running goes to
Activity.onNewIntent, whichActivityLifecycleCallbacksdoes not expose — so the native SDK cannot see it and this plugin is the only layer that can. ANewIntentListeneris registered on attach and re-attach and removed on both detach paths.minor: apps following the previously documentedFirebaseMessaging.onMessageOpenedApp→capturePushNotificationOpened(...)pattern must remove that call. The plugin now captures that tap and the manual API is not deduplicated against it, so it would count twice. The changeset and the Dart docs both say so.🤖 Generated with Claude Code
https://claude.ai/code/session_012txiHBCZRkShMdE7V25Jrd