Repository navigation
feat: add an onFeatureFlags listener you can unsubscribe - #878
turnipdabeets wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
PostHog Review · Standard · reviewed b2fbd8b: 2 Should fix.
See this review and its findings in PostHog Review. Comment @posthog review --deep for a Deep review. A Deep review takes longer and looks deeper than a Standard review.
| private fun notifyFeatureFlagsListeners(errorsLoading: Boolean) { | ||
| val loaded = PostHogFeatureFlagsLoaded(remoteConfig?.getFeatureFlags() ?: emptyMap(), errorsLoading) | ||
| lastFeatureFlagsLoaded = loaded | ||
| featureFlagsListeners.forEach { deliverFeatureFlags(it, loaded) } |
There was a problem hiding this comment.
P2 · Discard notifications from loads invalidated by close
When close() runs during a flags request, its completion still calls notifyFeatureFlagsListeners, repopulates the cleared snapshot, and invokes subscribers while disabled. Late subscribers then receive that stale result too. Preserve lifecycle synchronization (AGENTS.md:28); reject notifications from an invalidated setup generation.
| val current = lastFeatureFlagsLoaded | ||
| if (current != null && current !== listener.lastDelivered) { | ||
| deliverFeatureFlags(listener, current) |
There was a problem hiding this comment.
P2 · Replay flags that have already loaded from persistent cache
With automatic flag loading disabled, getFeatureFlagResult() hydrates persisted flags and marks them loaded, but leaves lastFeatureFlagsLoaded null. A subsequent onFeatureFlags() subscriber receives nothing until an explicit reload, despite current flags being available. Initialize the replay snapshot from already loaded remote-config state.
posthog-server calls its own PostHogOnFeatureFlags directly, and the core stateless client never calls config.onFeatureFlags. Bump posthog-server to minor since it now depends on core 7.0, and document that a bootstrapped onFeatureFlags runs on the setup() thread.
Registers a PostHogFeatureFlagsCallback that runs after every feature flags load, replays the current flags to late subscribers, and returns a PostHogFeatureFlagsSubscription, matching posthog-ios onFeatureFlags(_:).
A load that started before reset() or close() could finish afterwards, reach listeners, and be replayed to late subscribers, including the previous user's flags after reset(). Each load now captures a generation that reset() and close() bump, and listener updates from an older generation are dropped.
b2fbd8b to
b328ebe
Compare
Note
Stacked on #877, which is stacked on the 4.0 integration PR #871. Merges into
chore/v4-flags-loaded-result.💡 Motivation and Context
Adds
onFeatureFlags(), matching posthog-ios 4.0onFeatureFlags(_:)and posthog-jsonFeatureFlags. Today the only way to hear about flag loads after setup is the singlePostHogConfig.onFeatureFlagscallback, which a screen or library can't add to or remove.PostHogFeatureFlagsLoadedas the config callback (chore!: pass the load result to the feature flags callback #877).PostHog.onFeatureFlags. Listeners stay registered acrossclose()andsetup().For reviewers:
PostHogInterfacegains an abstract method, which breaks third-party implementations of the interface. That's acceptable in 4.0.reset()orclose()but finishes after is dropped for listeners and not replayed, so listeners never get the previous user's flags afterreset(). Each load captures a generation thatreset()andclose()bump.💚 How did you test it?
./gradlew spotlessCheck apiCheck :posthog:test :posthog-server:test :posthog-android:testDebugUnitTestpass.PostHogFeatureFlagsTestcovers delivery on each load untilunsubscribe(), replay to a late listener, listeners survivingclose()andsetup(), and no delivery from a reload while disabled. A new test checks that a load finishing afterclose()reaches no listener and isn't replayed; it fails without the fix.PostHogBootstrapTestcovers replay of bootstrapped flags.📝 Checklist
If releasing new changes
pnpm changesetto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Claude Code (Claude Opus 5.5) implemented the listener for flags API parity with iOS 4.0 and Flutter 6, after a cross-SDK review of the flags APIs.
🤖 Generated with Claude Code