Skip to content

feat!: remove PostHogConfig.onFeatureFlags in favor of the listener - #695

Merged
turnipdabeets merged 12 commits into
feat/v6-on-feature-flags-listenerfrom
feat/v6-remove-config-on-feature-flags
Nov 3, 2026
Merged

turnipdabeets merged 12 commits into
feat/v6-on-feature-flags-listenerfrom
feat/v6-remove-config-on-feature-flags

Conversation

@turnipdabeets

@turnipdabeets turnipdabeets commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

Stacked on #670. Ships with PostHog/posthog-android#918, which removes the field natively. #918 is merged into posthog-android's chore/v4-remove-deprecated but not yet feat/v4-capture-v1. This PR still builds against feat/v4-capture-v1, because the plugin only stops reading the field (built on Nov 3 through #699, which contains this PR). Makes Posthog().onFeatureFlags(callback) the only flag-load listener, matching iOS 4, Android 4 and posthog-js.

Breaking: removes PostHogConfig.onFeatureFlags. Migration: call Posthog().onFeatureFlags((loaded) { ... }) before setup().

  • The Android plugin now subscribes to PostHog.onFeatureFlags on engine attach and unsubscribes on detach, as the iOS/macOS plugin does.
  • That listener also fires after a manual reload, so the Dart-side Android-only reload delivery is gone. It also fixes the "not called after reload" Android caveat.

💚 How did you test it?

  • flutter analyze, flutter test (1318 passed, 1 skipped), scripts/check-api-dart.sh.
  • flutter test --platform chrome on the two web files: the 3 app version tests fail, as reported on the base; the rest pass.

Ran through the Flutter example with local posthog-android and posthog-ios against a local mock server (Android emulator API 37, iOS 26.4 simulator). Temporary overrides lived in a throwaway worktree.

Question Android iOS
Listener registered before setup() gets the first load (cold start) {known-on, checkout}, errorsLoading=false same
Bootstrap values reach it, before the network load {boot-flag, boot-variant}, then the /flags values same
/flags 500 on relaunch last known flags, errorsLoading=true same
Late listener gets current flags once once, then once per reload same

🧪 How to test locally (for agents and reviewers)

CI's build-android and build-apple jobs can't pass until posthog-ios and posthog-android 4.0 are released, because no 4.x version resolves yet. To test now, build against the native feat/v4-capture-v1 branches. Don't commit these overrides.

Android: check out feat/v4-capture-v1 of posthog-android, then:

sed -i '' -E 's/^coreVersion=.*/coreVersion=7.99.0/; s/^androidVersion=.*/androidVersion=4.99.0/' gradle.properties
./gradlew :posthog:publishToMavenLocal :posthog-android:publishToMavenLocal -x signMavenPublication

Add mavenLocal() as the first repository in example/android/build.gradle.kts. Use a new version for each republish, because Gradle can reuse a stale one.

iOS/macOS: check out feat/v4-capture-v1 of posthog-ios and point posthog_flutter/darwin/posthog_flutter/Package.swift at it:

.package(name: "posthog-ios", path: "/absolute/path/to/posthog-ios"),

Build: from example/, with Swift Package Manager enabled (flutter config --enable-swift-package-manager), run flutter build apk --debug and flutter build ios --simulator --debug.

Local server: set config.host to http://127.0.0.1:<port>.

  • Android: add android:usesCleartextTraffic="true" to <application> and run adb reverse tcp:<port> tcp:<port>.
  • iOS: set NSAppTransportSecurity > NSAllowsArbitraryLoads in Runner/Info.plist.
  • Server: use HTTP/1.1, and return {"hasFeatureFlags": true} from /array/<token>/config. Otherwise Android skips /flags and reports empty flags.

Run tests on a device with flutter test integration_test/<file>.dart -d <device>.

What to check on this PR: the same listener scenarios as #670, using its test and server, with listeners replacing config.onFeatureFlags.

📝 Checklist

  • All commits are signed (see Commit signing)
  • 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)

🤖 Generated with Claude Code

Posthog().onFeatureFlags() can be registered before setup() and removed, so the
config field no longer adds anything. Android now drives Dart from the
posthog-android listener like iOS does, which also fires after a manual reload,
so the Dart-side Android reload delivery goes away.
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

posthog-flutter Compliance Report

Date: 2026-11-03T18:47:01.670156+00:00
Duration: 97125ms

✅ All Tests Passed!

47/47 tests passed


Capture Tests

✅ 30/30 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 129ms
Format Validation.Event Has Uuid ✅ 114ms
Format Validation.Event Has Lib Properties ✅ 111ms
Format Validation.Distinct Id Is String ✅ 110ms
Format Validation.Token Is Present ✅ 111ms
Format Validation.Custom Properties Preserved ✅ 119ms
Format Validation.Event Has Timestamp ✅ 112ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 111ms
Retry Behavior.Retries On 503 ✅ 5327ms
Retry Behavior.Does Not Retry On 400 ✅ 2113ms
Retry Behavior.Does Not Retry On 401 ✅ 2114ms
Retry Behavior.Respects Retry After Header ✅ 8121ms
Retry Behavior.Implements Backoff ✅ 15439ms
Retry Behavior.Retries On 500 ✅ 5222ms
Retry Behavior.Retries On 502 ✅ 5220ms
Retry Behavior.Retries On 504 ✅ 5220ms
Retry Behavior.Max Retries Respected ✅ 15430ms
Deduplication.Generates Unique Uuids ✅ 116ms
Deduplication.Preserves Uuid On Retry ✅ 5219ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10331ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5223ms
Deduplication.No Duplicate Events In Batch ✅ 116ms
Deduplication.Different Events Have Different Uuids ✅ 112ms
Compression.Sends Gzip When Enabled ✅ 111ms
Batch Format.Uses Proper Batch Structure ✅ 111ms
Batch Format.Flush With No Events Sends Nothing ✅ 107ms
Batch Format.Multiple Events Batched Together ✅ 117ms
Error Handling.Does Not Retry On 403 ✅ 2112ms
Error Handling.Does Not Retry On 413 ✅ 2113ms
Error Handling.Retries On 408 ✅ 5221ms

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 9ms
Request Payload.Flags Request Uses V2 Query Param ✅ 6ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 16ms
Request Payload.Flags Request Omits Authorization Header ✅ 19ms
Request Payload.Token In Flags Body Matches Init ✅ 7ms
Request Payload.Groups Round Trip ✅ 7ms
Request Payload.Groups Default To Empty Object ✅ 22ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 20ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 16ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 7ms
Request Lifecycle.No Flags Request On Init Alone ✅ 2ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 109ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 11ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 6ms
Retry Behavior.Retries Flags On 502 ✅ 114ms
Retry Behavior.Retries Flags On 504 ✅ 117ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 113ms

@marandaneto marandaneto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated advisory code review.

Comment thread example/lib/main.dart Resolved
@marandaneto
marandaneto requested a review from a team November 2, 2026 15:25
Comment thread posthog_flutter/MIGRATION.md Resolved
@turnipdabeets turnipdabeets mentioned this pull request Nov 3, 2026
5 of 6 tasks
…-feature-flags

Keeps 2cd9136's stop when a listener's reset() or close() clears a load.
deliver() returns void again, since no config callback reads its result.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
turnipdabeets added a commit that referenced this pull request Nov 3, 2026
…eset

Folds in the MIGRATION.md and changeset text of #682, #698, #703, #670,
#695 and #699 so landing this PR last is "take this side".

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@turnipdabeets
turnipdabeets merged commit e9a2ab0 into feat/v6-on-feature-flags-listener Nov 3, 2026
20 of 23 checks passed
@turnipdabeets
turnipdabeets deleted the feat/v6-remove-config-on-feature-flags branch November 3, 2026 20:50
turnipdabeets added a commit that referenced this pull request Nov 3, 2026
* chore!: adopt posthog-ios 4.0

- Require posthog-ios 4.x.
- Adapt to the reloadFeatureFlags(_:) callback that now passes a
  PostHogFeatureFlagsLoaded (PostHog/posthog-ios#913).
- Note the payload decoding changes from PostHog/posthog-ios#903.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* docs(changeset): note posthog-ios 4.0 drops lifecycle version and build

PostHog/posthog-ios#905 removes the legacy version/build properties from
lifecycle events in 4.0, so Flutter iOS and macOS apps stop sending them.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* chore(ios): drive onFeatureFlags from the posthog-ios onFeatureFlags listener

posthog-ios 4.0 removes the didReceiveFeatureFlags notification.

* chore!: adopt posthog-android 4.0 (#663)

* chore!: adopt posthog-android 4.0

* chore!: pass the loaded flags to onFeatureFlags and reloadFeatureFlags (#664)

* chore!: pass the loaded flags to onFeatureFlags and reloadFeatureFlags

onFeatureFlags and reloadFeatureFlags now carry a PostHogFeatureFlagsLoaded with the enabled flags, their values and whether loading failed, matching posthog-ios 4.0 and posthog-android 4.0. On web, reloadFeatureFlags completes right away with the flags loaded so far, because posthog-js doesn't report when a reload finishes.

* fix: complete the web reloadFeatureFlags with the reloaded flags

Wait for the next posthog-js onFeatureFlags call instead of returning the flags already loaded, so a failed reload reports errorsLoading. Complete right away when flags are disabled, and after twice the flags request timeout when posthog-js skips the request without calling back.

* chore: use PostHogFeatureFlagsCallback from posthog-android 4.0

* chore: fix ktlint function-signature

* fix: await posthog-js reloadFeatureFlagsAsync on web

The next onFeatureFlags callback can carry a response from a request already in flight, so reloadFeatureFlags() could complete with flags from before the reload. posthog-js 1.438.2+ settles reloadFeatureFlagsAsync() after this reload's own request; use it when present and keep the callback path for older posthog-js.

* docs: pass the loaded flags in the setup example

* docs: drop best-effort web note from the person and group property methods

* fix(web): wait for posthog-js flags retries before reporting a reload failure

posthog-js retries a timed-out /flags request once by default, 500 ms later, inside the same reload. The older-JS deadline of twice the request timeout reported cached flags with errorsLoading set before a successful retry landed.

* docs: note where onFeatureFlags and errorsLoading differ on Android and web

* docs: describe the older posthog-js reload timeout as the full retry budget

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

* docs(changeset): fold the posthog-android 4.0 and flags-loaded entries into one changeset

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* feat: add an onFeatureFlags listener you can unsubscribe

* fix: isolate failing flag listeners, skip web registration without onFeatureFlags, update API snapshot

* fix: deliver flag loads to onFeatureFlags listeners before the config callback

A config callback that threw kept listeners from getting the load, and a
reset() or close() it started was undone when the load was cached after it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(web): stop flag registrations left by close() or the posthog-js loader

A registration whose unsubscribe isn't stored yet (close() during the synchronous replay) or never arrives (the loader snippet's stub) now goes inert once setup() or close() runs again.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix: drop native flag loads sent before a reset or close reached native

Dart bumps a generation when reset() or close() clears the listener registry and sends it with setup, reset and close. Android and iOS tag each onFeatureFlags notification with the generation they had when it fired, and Dart drops ones from an earlier generation, so the previous user's flags can't repopulate the replay cache.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix: start receiving native flag loads when the first listener is added

With native auto-init, Posthog().onFeatureFlags() can be the app's only
Dart SDK call, and nothing created the platform that installs the channel
handler, so native loads never reached the listener.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix: deliver Android reloadFeatureFlags results to onFeatureFlags listeners

posthog-android passes a manual reload only to its completion, so no
onFeatureFlagsCallback arrives and listeners missed the load while new
ones replayed the older snapshot. A reload that finishes after a reset or
close is still dropped.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* chore: stop passing RRStyle to replay screenshot frames (#684)

posthog-android 4.0 drops RRStyle and the wireframe tree fields from the
internal RRWireframe; screenshot frames don't need a style.

Co-authored-by: Claude Opus 5.5 <[email protected]>

* fix(web): clear replayed flags when a queued reset reaches posthog-js

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs: fold #670 release notes into the 6.0 docs

* fix: don't throw when SDK calls run before the Flutter binding exists

Posthog() creates PosthogFlutterIO, whose constructor set the channel handler
and aborted without a binding. Retry the install instead, and fail native calls
with a PlatformException so each caller's existing fallback applies.

* fix: stop delivering a flags load that a listener's reset() or close() cleared

The listeners after it and the config callback no longer get the old flags.

Claude-Session: https://claude.ai/code/session_019xLynws83apuXa9LfRy9Aq

* feat!: remove PostHogConfig.onFeatureFlags in favor of the listener (#695)

* feat!: remove PostHogConfig.onFeatureFlags in favor of the listener

Posthog().onFeatureFlags() can be registered before setup() and removed, so the
config field no longer adds anything. Android now drives Dart from the
posthog-android listener like iOS does, which also fires after a manual reload,
so the Dart-side Android reload delivery goes away.

* docs: fold #695 release notes into the 6.0 docs

* test: use the onFeatureFlags listener in desktop tests from the base merge

* fix(example): initialize the binding before registering the flags listener

* docs: show the removed onFeatureFlags constructor argument in the migration guide

* test: use the flags listener in the #694 desktop tests

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
turnipdabeets added a commit that referenced this pull request Nov 3, 2026
… platform (#701)

* docs: link the capture v1 SDK upgrade guide

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs: cover capture v1 on Windows and Linux and align the 6.0 guide with the backend SDK guides

Claude-Session: https://claude.ai/code/session_019xLynws83apuXa9LfRy9Aq

* docs: lone surrogates on Windows and Linux (#703)

Claude-Session: https://claude.ai/code/session_019xLynws83apuXa9LfRy9Aq

* docs: identify ignores a distinct ID with a lone surrogate (#703)

Claude-Session: https://claude.ai/code/session_019xLynws83apuXa9LfRy9Aq

* docs: complete the platform interface changes and the upgrade checklist

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs: document the final 6.0 state in the guide and one grouped changeset

Folds in the MIGRATION.md and changeset text of #682, #698, #703, #670,
#695 and #699 so landing this PR last is "take this side".

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs: describe lone surrogates as repaired on Windows and Linux

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs: cover the /flags request in the lone surrogate changeset entry

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs: note the /flags request in the lone surrogate guide line

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs: say the SDK keeps lone-surrogate ids as passed and repairs what it sends

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs: drop the confirmed capture v1 default TODOs from the guide

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs: say $locale follows a per-app language picked in macOS Settings too

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
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.

3 participants