Skip to content

feat: send $flutter_version only on Application Installed/Updated - #651

Merged
turnipdabeets merged 1 commit into
mainfrom
feat/flutter-version-on-lifecycle
Oct 8, 2026
Merged

turnipdabeets merged 1 commit into
mainfrom
feat/flutter-version-on-lifecycle

Conversation

@turnipdabeets

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

We're cutting per-event properties whose value never changes within a build, since they cost storage on every event (Slack, RFC #1263). $flutter_version (added in #640, released in 5.52.0) is one: it's compiled into the build, so it can only change when the app is rebuilt. The iOS equivalent is PostHog/posthog-ios#923, which moves $app_build_xcode/$app_build_sdk to Application Installed/Application Updated.

On iOS, macOS and Android those lifecycle events are captured by the native SDKs and never pass through Dart, so Dart can't add properties to them. Both native SDKs already have a beforeSend chain on their config, and the plugin builds that config, so this needs no native SDK changes:

  • Dart sends the build's Flutter version (the FLUTTER_VERSION define, Flutter 3.32+) as a flutterVersion setup argument. It's not added to PostHogConfig.toMap(), so there's no public API change (make checkApiDart passes).
  • The Android and Apple plugins add a native beforeSend that sets $flutter_version on Application Installed and Application Updated only. The plugin didn't install a native event beforeSend before this, and customers' beforeSend callbacks run in Dart, so nothing gets overwritten.
  • capture, screen and captureException no longer add $flutter_version. This reverts feat: add $flutter_version to captured events #640's Dart wiring, along with the _capture split it needed for beforeSend removals to stick.
  • On web, $flutter_version is no longer sent at all. posthog-js has no install/update events to put it on.

Behavior change

Anyone filtering or breaking down on $flutter_version on regular events (shipped in 5.52.0, two days ago) has to switch to Application Installed/Application Updated. It also won't be sent:

  • when captureApplicationLifecycleEvents is off
  • on web
  • when the SDK is auto-initialized natively (Info.plist / AndroidManifest AUTO_INIT), because native setup runs before Dart can pass the version

A rebuild on a new Flutter version that keeps the same build number won't emit Application Updated. That's the same limitation posthog-ios#923 has.

💚 How did you test it?

  • flutter test (787 passed). flutter test --platform chrome on the CI file list: everything passes except 3 posthog_flutter_web_handler_test.dart "app version" cases, which fail the same way on origin/main locally (code this PR doesn't touch).
  • Android: ./gradlew :posthog_flutter:testDebugUnitTest (48/48), including the new setup_flutterVersion_addedOnlyToInstallAndUpdateEvents.
  • iOS: xcodebuild test ... -only-testing:RunnerTests (5/5), including the new testFlutterVersionAddedOnlyToInstallAndUpdateEvents, which runs the block through a real PostHogSDK capture.
  • Mutation checks: each new test fails when its wiring is removed (Dart setup arg dropped; Android flutterVersion key ignored or Application Updated dropped; iOS Application Updated dropped).
  • make checkFormatDart analyzeDart checkFormatKotlin checkFormatSwift checkApiDart all pass.
  • Not tested on a device yet.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Written with Claude Code. Options considered:

  • Native SDK changes adding a wrapper hook for lifecycle-event properties. Rejected: the existing native beforeSend already covers it, with no new native API.
  • Dart-side "send only when it changes", persisting the last-sent version. Rejected: it needs new persistence and attaches the property to an arbitrary event.
  • On web, dropping the property was picked over keeping it per-event, because there's no lifecycle event to carry it.

🤖 Generated with Claude Code

Move $flutter_version off every Dart-captured event. Dart passes the
build's Flutter version to the native plugins at setup, and each plugin
installs a native beforeSend that adds it to the Application Installed
and Application Updated events the native SDKs capture. Web no longer
sends it.
@turnipdabeets turnipdabeets self-assigned this Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

posthog-flutter Compliance Report

Date: 2026-10-08 17:50:32 UTC
Duration: 96739ms

✅ All Tests Passed!

45/45 tests passed


Capture Tests

✅ 29/29 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 138ms
Format Validation.Event Has Uuid ✅ 118ms
Format Validation.Event Has Lib Properties ✅ 115ms
Format Validation.Distinct Id Is String ✅ 113ms
Format Validation.Token Is Present ✅ 115ms
Format Validation.Custom Properties Preserved ✅ 115ms
Format Validation.Event Has Timestamp ✅ 116ms
Retry Behavior.Retries On 503 ✅ 5329ms
Retry Behavior.Does Not Retry On 400 ✅ 2117ms
Retry Behavior.Does Not Retry On 401 ✅ 2116ms
Retry Behavior.Respects Retry After Header ✅ 8125ms
Retry Behavior.Implements Backoff ✅ 15441ms
Retry Behavior.Retries On 500 ✅ 5225ms
Retry Behavior.Retries On 502 ✅ 5224ms
Retry Behavior.Retries On 504 ✅ 5222ms
Retry Behavior.Max Retries Respected ✅ 15443ms
Deduplication.Generates Unique Uuids ✅ 124ms
Deduplication.Preserves Uuid On Retry ✅ 5223ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10335ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5229ms
Deduplication.No Duplicate Events In Batch ✅ 120ms
Deduplication.Different Events Have Different Uuids ✅ 114ms
Compression.Sends Gzip When Enabled ✅ 115ms
Batch Format.Uses Proper Batch Structure ✅ 113ms
Batch Format.Flush With No Events Sends Nothing ✅ 108ms
Batch Format.Multiple Events Batched Together ✅ 124ms
Error Handling.Does Not Retry On 403 ✅ 2115ms
Error Handling.Does Not Retry On 413 ✅ 2115ms
Error Handling.Retries On 408 ✅ 5225ms

Feature_Flags Tests

✅ 16/16 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 12ms
Request Payload.Flags Request Uses V2 Query Param ✅ 8ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 8ms
Request Payload.Flags Request Omits Authorization Header ✅ 8ms
Request Payload.Token In Flags Body Matches Init ✅ 9ms
Request Payload.Groups Round Trip ✅ 9ms
Request Payload.Groups Default To Empty Object ✅ 9ms
Request Payload.Person Properties Distinct Id Auto Populated When Caller Omits It ✅ 8ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 8ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 9ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 8ms
Request Lifecycle.No Flags Request On Init Alone ✅ 3ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 111ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 14ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 9ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 113ms

@turnipdabeets
turnipdabeets marked this pull request as ready for review October 8, 2026 18:04
@turnipdabeets
turnipdabeets requested a review from a team as a code owner October 8, 2026 18:04
@turnipdabeets
turnipdabeets merged commit 0703fbb into main Oct 8, 2026
28 checks passed
@turnipdabeets
turnipdabeets deleted the feat/flutter-version-on-lifecycle branch October 8, 2026 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants