fix(push): require posthog-android 3.64.0 for push-open dedupe - #578
Draft
turnipdabeets wants to merge 6 commits into
Draft
fix(push): require posthog-android 3.64.0 for push-open dedupe#578turnipdabeets wants to merge 6 commits into
turnipdabeets wants to merge 6 commits into
Conversation
Contributor
posthog-flutter Compliance ReportDate: 2026-09-12 19:55:47 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
|
turnipdabeets
marked this pull request as ready for review
September 11, 2026 14:39
Prompt To Fix All With AI### Issue 1
posthog_flutter/android/src/test/kotlin/com/posthog/flutter/AutoCapturedPushOpensTest.kt:12-18
**Tests are not parameterised**
These tests repeat the same setup, remember, and membership checks across separate methods and inline input lists. This violates the repository directive to prefer parameterised tests and works against its OnceAndOnlyOnce rule. Please consolidate the matching, non-matching, and invalid-entry cases into parameterised tests before merging.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "fix(push): key the push-open dedupe on i..." | Re-trigger Greptile |
turnipdabeets
marked this pull request as draft
September 11, 2026 15:09
5 tasks
This was referenced Sep 11, 2026
Open
… made Before posthog.com#19905 the Flutter docs told apps to call capturePushNotificationOpened from FirebaseMessaging.onMessageOpenedApp on Android. Since 5.40.0 the plugin captures that tap itself, so an app that kept the snippet sends two $push_notification_opened events per tap, and the server turns each one into a push_opened metric with no dedupe. The plugin now remembers the posthog.invocation_id of each tap it hands to PostHogAndroid, and a manual call carrying a remembered id is skipped with a warning. PostHogAndroid returns no result, so its no-op gates are mirrored to avoid remembering a tap it did not capture. Payloads without a posthog entry behave as before. Claude-Session: https://claude.ai/code/session_01UJgnRvz58rzgFVCiUfjxkL
Every step of one workflow run shares the run's invocation_id, and push-notification.service.ts stamps each push with its step's action_id. Keyed on invocation_id alone, the automatic capture of one step's push swallowed a legitimate manual capture of the next step's push. Keys on "$invocationId/$actionId" (action_id optional), matching the React Native plugin. Claude-Session: https://claude.ai/code/session_01UJgnRvz58rzgFVCiUfjxkL
posthog-android#783 moves the dedupe into PostHog.capturePushNotificationOpened, which every capture path reaches, including this plugin's launch-intent and NewIntentListener paths and its manual method-channel call. A repeat of a PostHog-sent notification (same invocation_id and action_id) within 5 minutes is skipped there, so the plugin-level copy would only stack a second layer. Removes AutoCapturedPushOpens, its tests and the org.json test dependency, raises the posthog-android floor to 3.64.0, and rewrites the capturePushNotificationOpened doc to match. Claude-Session: https://claude.ai/code/session_01UJgnRvz58rzgFVCiUfjxkL
turnipdabeets
force-pushed
the
fix/push-open-manual-dedupe
branch
from
September 12, 2026 16:26
26b7faf to
47b9f13
Compare
…onOpened Say that only PostHog-sent notifications carry the ids the 5-minute window matches on, and that the Android automatic capture has no notification text a manual re-report could add. Lead the changeset with the fixed behavior.
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
Before PostHog/posthog.com#19905 (merged 2026-09-10), the Flutter push docs told apps to capture Android opens themselves:
Since 5.40.0 (#557) the plugin captures every Android tap itself, so an app that kept this snippet sends two
$push_notification_openedevents per tap. The server'spush-open-tracking.tsturns each one into apush_openedapp metric with no dedupe, so Workflows open rates are inflated too.Cold taps are affected as well as warm ones. On Android,
firebase_messagingalso emitsonMessageOpenedAppfor the tap that launched the app, because itsonAttachedToActivitycallshandleNotificationIntent(activity.getIntent()). So one cold-start tap is reported to bothonMessageOpenedAppandgetInitialMessage(): the old snippet alone gives 2 events on a cold tap, and 3 if the app also wiredgetInitialMessage().This PR first fixed that inside the plugin. PostHog/posthog-android#783 now moves the dedupe into
PostHog.capturePushNotificationOpenedin posthog-android core, and PostHog/posthog-ios#828 applies the same rule in posthog-ios. It skips a repeat of a PostHog-sent notification (sameposthog.invocation_id+action_id) captured within 5 minutes, whichever path reported it first. Every path in this plugin reaches that method: the launch-intent andNewIntentListenerpaths throughPostHogAndroid.capturePushNotificationOpened(intent), and the manual method-channel call. A match doesn't consume the key, so the one automatic capture of a cold tap skips both manual calls. Keeping the plugin's own dedupe would stack two layers, so this PR is now just the version floors.Changes
AutoCapturedPushOpens,captureAutomaticPushNotificationOpened, the skip andLog.won the manual path,AutoCapturedPushOpensTest, and theorg.jsontest dependency.PosthogFlutterPlugin.ktis back tomain.[3.65.0,4.0.0), the release planned for #783. The example'ssettings.gradle.ktshook doesn't pin a version, so nothing else changes.>= 3.75.0, < 4.0.0indarwin/posthog_flutter.podspecanddarwin/posthog_flutter/Package.swift, the release planned for feat(push): capture each PostHog push open once across automatic and manual paths posthog-ios#828. The old>= 3.74.0floor let a fresh install resolve 3.75.0 but left a locked one on 3.74.x without the fix.capturePushNotificationOpenedDart doc. It still says not to wire the call toonMessageOpenedApp/getInitialMessage(). On Android and iOS, a repeat of a PostHog-sent notification captured in the last 5 minutes is now skipped; for pushes not sent by PostHog the open is still counted twice.patch).Behavior differences from the earlier plugin-level version of this PR:
debugis on.CI fails until posthog-android 3.65.0 and posthog-ios 3.75.0 are published (3.64.0 shipped
captureTouches, not this fix): Gradle can't resolve[3.65.0,4.0.0)and CocoaPods/SwiftPM can't resolve>= 3.75.0yet. Merge this after both releases. It stays a draft until then.Related: PostHog/posthog-android#783 (the core dedupe), PostHog/posthog-js#4919 (the React Native plugin's equivalent, being repurposed the same way), PostHog/posthog.com#19905 (the docs change), #557 (automatic Android capture).
💚 How did you test it?
Device run: fresh Pixel 6 AVD instance (Android 17, API 37) on its own port. This branch's example app was built against #783 at
f7ae7aeb, published to mavenLocal asposthog-android:3.65.0-local-flutter/posthog:6.36.0-local-flutterand forced in through the gitignoredexample/android/local.settings.gradle.kts(dependencyInsightconfirmed3.65.0-local-flutter (forced)). The harness is the same one as before and was not committed:firebase_core+firebase_messaging16.6.0 with placeholder Firebase options, and the old docs snippet pasted in verbatim.am broadcast com.google.android.c2dm.intent.RECEIVE, so the FCM SDK posts a real tray notification. Theposthogentry was{"workflow_id":"wf-1","invocation_id":"inv-fl-1","action_id":"step-1"}.am killafter the notification was posted (cold).$push_notification_openedevents in the SDK's/batchbodies, sent to a local mock server. App data was cleared before every row.getInitialMessage()handler, cold tapposthogentryThe "before" column is the measurement from #783's device table. In every deduped row, the event that survives is the automatic one, carrying
$notification_invocation_idand$notification_action_id. Logcat shows each manual call arriving (onMessageOpenedApp, andgetInitialMessagein row 3), each followed by core'sSkipped $push_notification_opened: notification inv-fl-1/step-1 was already captured.Also ran:
flutter test: 348 passed.dart format,dart analyze, ktlint and the Dart public API snapshot check: all clean. The public API doesn't change../gradlew :posthog_flutter:testDebugUnitTestagainst the local 3.65.0 build: 34 passed.Not verified:
posthogJSON shape the server sends.📝 Checklist
No new tests here: the dedupe and its tests live in posthog-android#783.
If releasing new changes
pnpm changesetto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Built with Claude Code, driven by @turnipdabeets. The first two commits added a plugin-level dedupe matching PostHog/posthog-js#4919. The two implementations had already diverged (consume-once vs. not) and each one mirrored the SDK's skip gates, so the logic moved into posthog-android core in #783. The last commit removes the plugin copy and raises the floor.
Related PRs
One push-open capture effort across the mobile SDKs: count every notification tap exactly once, and stop losing taps the SDK starts too late to see.
firebase_messagingMerge order: posthog-android#783 and posthog-ios#828 first, then their releases. #4921, #4929 and #579 are independent and can go any time. #4919 and #578 go green once posthog-android 3.65.0 and posthog-ios 3.75.0 are published. Docs: #20114 can go now; #20102 last, after the releases.
Earlier work this builds on: PostHog/posthog-android#753, PostHog/posthog-ios#792, PostHog/posthog-js#4858, #556, #557, PostHog/posthog.com#19905.