From 87b6de20954e87a46d5e32cd576d2f5a1479c99e Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Fri, 23 Oct 2026 12:56:53 -0400 Subject: [PATCH 1/2] feat: add per-call sendFeatureFlagEvent to getFeatureFlag and isFeatureEnabled --- .changeset/flag-read-send-event-override.md | 5 ++ api/posthog_flutter.api.json | 54 +++++++++++++++++++ .../posthog/flutter/PosthogFlutterPlugin.kt | 4 +- .../PosthogFlutterPlugin.swift | 15 +++++- posthog_flutter/lib/posthog_flutter_web.dart | 18 +++++-- .../lib/src/core/posthog_core.dart | 13 +++-- posthog_flutter/lib/src/posthog.dart | 36 +++++++++++-- .../lib/src/posthog_flutter_desktop.dart | 20 +++++-- .../lib/src/posthog_flutter_io.dart | 14 +++-- .../posthog_flutter_platform_interface.dart | 7 ++- .../lib/src/posthog_flutter_web_handler.dart | 21 +++++--- .../test/posthog_flutter_desktop_test.dart | 50 +++++++++++++++++ .../test/posthog_flutter_io_test.dart | 23 ++++++++ ...sthog_flutter_platform_interface_fake.dart | 23 +++++++- .../posthog_flutter_web_handler_test.dart | 39 ++++++++++++++ posthog_flutter/test/posthog_test.dart | 18 +++++++ 16 files changed, 328 insertions(+), 32 deletions(-) create mode 100644 .changeset/flag-read-send-event-override.md diff --git a/.changeset/flag-read-send-event-override.md b/.changeset/flag-read-send-event-override.md new file mode 100644 index 00000000..46db7c78 --- /dev/null +++ b/.changeset/flag-read-send-event-override.md @@ -0,0 +1,5 @@ +--- +"posthog_flutter": minor +--- + +Add an optional `sendFeatureFlagEvent` parameter to `getFeatureFlag()` and `isFeatureEnabled()` to override `PostHogConfig.sendFeatureFlagEvent` per call diff --git a/api/posthog_flutter.api.json b/api/posthog_flutter.api.json index abb5e5cf..b22bd108 100644 --- a/api/posthog_flutter.api.json +++ b/api/posthog_flutter.api.json @@ -778,6 +778,15 @@ "name": "key", "relativePath": "lib/src/posthog_flutter_platform_interface.dart", "typeName": "String" + }, + { + "isDeprecated": false, + "isExperimental": false, + "isNamed": true, + "isRequired": false, + "name": "sendFeatureFlagEvent", + "relativePath": "lib/src/posthog_flutter_platform_interface.dart", + "typeName": "bool?" } ], "relativePath": "lib/src/posthog_flutter_platform_interface.dart", @@ -961,6 +970,15 @@ "name": "key", "relativePath": "lib/src/posthog_flutter_platform_interface.dart", "typeName": "String" + }, + { + "isDeprecated": false, + "isExperimental": false, + "isNamed": true, + "isRequired": false, + "name": "sendFeatureFlagEvent", + "relativePath": "lib/src/posthog_flutter_platform_interface.dart", + "typeName": "bool?" } ], "relativePath": "lib/src/posthog_flutter_platform_interface.dart", @@ -1823,6 +1841,15 @@ "name": "key", "relativePath": "lib/src/posthog_flutter_platform_interface.dart", "typeName": "String" + }, + { + "isDeprecated": false, + "isExperimental": false, + "isNamed": true, + "isRequired": false, + "name": "sendFeatureFlagEvent", + "relativePath": "lib/src/posthog_flutter_platform_interface.dart", + "typeName": "bool?" } ], "relativePath": "lib/src/posthog_flutter_platform_interface.dart", @@ -2006,6 +2033,15 @@ "name": "key", "relativePath": "lib/src/posthog_flutter_platform_interface.dart", "typeName": "String" + }, + { + "isDeprecated": false, + "isExperimental": false, + "isNamed": true, + "isRequired": false, + "name": "sendFeatureFlagEvent", + "relativePath": "lib/src/posthog_flutter_platform_interface.dart", + "typeName": "bool?" } ], "relativePath": "lib/src/posthog_flutter_platform_interface.dart", @@ -4665,6 +4701,15 @@ "name": "key", "relativePath": "lib/src/posthog.dart", "typeName": "String" + }, + { + "isDeprecated": false, + "isExperimental": false, + "isNamed": true, + "isRequired": false, + "name": "sendFeatureFlagEvent", + "relativePath": "lib/src/posthog.dart", + "typeName": "bool?" } ], "relativePath": "lib/src/posthog.dart", @@ -4964,6 +5009,15 @@ "name": "key", "relativePath": "lib/src/posthog.dart", "typeName": "String" + }, + { + "isDeprecated": false, + "isExperimental": false, + "isNamed": true, + "isRequired": false, + "name": "sendFeatureFlagEvent", + "relativePath": "lib/src/posthog.dart", + "typeName": "bool?" } ], "relativePath": "lib/src/posthog.dart", diff --git a/posthog_flutter/android/src/main/kotlin/com/posthog/flutter/PosthogFlutterPlugin.kt b/posthog_flutter/android/src/main/kotlin/com/posthog/flutter/PosthogFlutterPlugin.kt index 6dbdf144..67cd35ac 100644 --- a/posthog_flutter/android/src/main/kotlin/com/posthog/flutter/PosthogFlutterPlugin.kt +++ b/posthog_flutter/android/src/main/kotlin/com/posthog/flutter/PosthogFlutterPlugin.kt @@ -1690,7 +1690,7 @@ class PosthogFlutterPlugin : ) { try { val featureFlagKey: String = call.argument("key")!! - val flag = PostHog.getFeatureFlag(featureFlagKey) + val flag = PostHog.getFeatureFlag(featureFlagKey, sendFeatureFlagEvent = call.argument("sendEvent")) result.success(flag) } catch (e: Throwable) { result.error("PosthogFlutterException", e.localizedMessage, null) @@ -1935,7 +1935,7 @@ class PosthogFlutterPlugin : ) { try { val key: String = call.argument("key")!! - val isEnabled = PostHog.isFeatureEnabled(key) + val isEnabled = PostHog.isFeatureEnabled(key, sendFeatureFlagEvent = call.argument("sendEvent")) result.success(isEnabled) } catch (e: Throwable) { result.error("PosthogFlutterException", e.localizedMessage, null) diff --git a/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift b/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift index 3d605001..6e3cf985 100644 --- a/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift +++ b/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift @@ -1360,7 +1360,13 @@ extension PosthogFlutterPlugin { if let args = call.arguments as? [String: Any], let featureFlagKey = args["key"] as? String { - let value = PostHogSDK.shared.getFeatureFlag(featureFlagKey) + // A missing sendEvent follows config.sendFeatureFlagEvent. + let value: Any? + if let sendEvent = args["sendEvent"] as? Bool { + value = PostHogSDK.shared.getFeatureFlag(featureFlagKey, sendFeatureFlagEvent: sendEvent) + } else { + value = PostHogSDK.shared.getFeatureFlag(featureFlagKey) + } result(value) } else { _badArgumentError(result) @@ -1374,7 +1380,12 @@ extension PosthogFlutterPlugin { if let args = call.arguments as? [String: Any], let featureFlagKey = args["key"] as? String { - let value = PostHogSDK.shared.isFeatureEnabled(featureFlagKey) + let value: Bool + if let sendEvent = args["sendEvent"] as? Bool { + value = PostHogSDK.shared.isFeatureEnabled(featureFlagKey, sendFeatureFlagEvent: sendEvent) + } else { + value = PostHogSDK.shared.isFeatureEnabled(featureFlagKey) + } result(value) } else { _badArgumentError(result) diff --git a/posthog_flutter/lib/posthog_flutter_web.dart b/posthog_flutter/lib/posthog_flutter_web.dart index 11e17958..6b1b2945 100644 --- a/posthog_flutter/lib/posthog_flutter_web.dart +++ b/posthog_flutter/lib/posthog_flutter_web.dart @@ -380,9 +380,13 @@ class PosthogFlutterWeb extends PosthogFlutterPlatformInterface { } @override - Future isFeatureEnabled(String key) async { + Future isFeatureEnabled(String key, + {bool? sendFeatureFlagEvent}) async { final result = await handleWebMethodCall( - MethodCall('isFeatureEnabled', {'key': key}), + MethodCall('isFeatureEnabled', { + 'key': key, + if (sendFeatureFlagEvent != null) 'sendEvent': sendFeatureFlagEvent, + }), ); return result as bool? ?? false; } @@ -448,8 +452,14 @@ class PosthogFlutterWeb extends PosthogFlutterPlatformInterface { } @override - Future getFeatureFlag({required String key}) async { - return handleWebMethodCall(MethodCall('getFeatureFlag', {'key': key})); + Future getFeatureFlag({ + required String key, + bool? sendFeatureFlagEvent, + }) async { + return handleWebMethodCall(MethodCall('getFeatureFlag', { + 'key': key, + if (sendFeatureFlagEvent != null) 'sendEvent': sendFeatureFlagEvent, + })); } @override diff --git a/posthog_flutter/lib/src/core/posthog_core.dart b/posthog_flutter/lib/src/core/posthog_core.dart index 4def67e4..90faab7a 100644 --- a/posthog_flutter/lib/src/core/posthog_core.dart +++ b/posthog_flutter/lib/src/core/posthog_core.dart @@ -864,11 +864,14 @@ abstract class PostHogCore extends PostHogCoreStateless { ); } - /// Gets a feature flag value. + /// Gets a feature flag value, capturing `$feature_flag_called` when + /// [sendEvent] is true, or when it is null and `sendFeatureFlagEvents` is + /// enabled in the config. /// /// Returns null if the flag does not exist or has not been loaded. - PostHogFeatureFlagValue? getFeatureFlag(String key) { - final result = getFeatureFlagResult(key, sendEvent: _sendFeatureFlagEvents); + PostHogFeatureFlagValue? getFeatureFlag(String key, {bool? sendEvent}) { + final result = getFeatureFlagResult(key, + sendEvent: sendEvent ?? _sendFeatureFlagEvents); if (result == null) return null; return result.variant ?? result.enabled; } @@ -877,8 +880,8 @@ abstract class PostHogCore extends PostHogCoreStateless { _getStoredFlagDetails()?.toResponse().featureFlags; /// Checks if a feature flag is enabled. - bool? isFeatureEnabled(String key) { - final response = getFeatureFlag(key); + bool? isFeatureEnabled(String key, {bool? sendEvent}) { + final response = getFeatureFlag(key, sendEvent: sendEvent); if (response == null) return null; if (response is bool) return response; return true; // String variants are truthy diff --git a/posthog_flutter/lib/src/posthog.dart b/posthog_flutter/lib/src/posthog.dart index 291d2d83..91018d5e 100644 --- a/posthog_flutter/lib/src/posthog.dart +++ b/posthog_flutter/lib/src/posthog.dart @@ -481,8 +481,23 @@ class Posthog { /// /// The [key] is the feature flag key. /// + /// [sendFeatureFlagEvent] controls whether this call captures the + /// `$feature_flag_called` event. Defaults to `null`, which follows + /// [PostHogConfig.sendFeatureFlagEvent]. Pass `false` to skip the event for + /// this call, or `true` to capture it even when the config turns it off. + /// /// Returns `false` when the flag is disabled, missing, or not a boolean flag. - Future isFeatureEnabled(String key) => _posthog.isFeatureEnabled(key); + /// + /// **Example:** + /// ```dart + /// if (await Posthog().isFeatureEnabled('new-checkout', + /// sendFeatureFlagEvent: false)) { + /// // show the new checkout without recording a flag exposure + /// } + /// ``` + Future isFeatureEnabled(String key, {bool? sendFeatureFlagEvent}) => + _posthog.isFeatureEnabled(key, + sendFeatureFlagEvent: sendFeatureFlagEvent); /// Reloads feature flags for the current user. /// @@ -761,8 +776,23 @@ class Posthog { /// Returns `null` if the flag does not exist or cannot be loaded. For boolean /// flags, returns `true` or `false`. For multivariate flags, returns the /// variant key. - Future getFeatureFlag(String key) => - _posthog.getFeatureFlag(key: key); + /// + /// [sendFeatureFlagEvent] controls whether this call captures the + /// `$feature_flag_called` event. Defaults to `null`, which follows + /// [PostHogConfig.sendFeatureFlagEvent]. Pass `false` to skip the event for + /// this call, or `true` to capture it even when the config turns it off. + /// + /// **Example:** + /// ```dart + /// final variant = await Posthog().getFeatureFlag('button-color', + /// sendFeatureFlagEvent: false); + /// if (variant == 'red') { + /// // show the red button without recording a flag exposure + /// } + /// ``` + Future getFeatureFlag(String key, {bool? sendFeatureFlagEvent}) => + _posthog.getFeatureFlag( + key: key, sendFeatureFlagEvent: sendFeatureFlagEvent); /// Returns the full feature flag result for [key], including value and /// payload. diff --git a/posthog_flutter/lib/src/posthog_flutter_desktop.dart b/posthog_flutter/lib/src/posthog_flutter_desktop.dart index 0ae3bc80..76e2d0c3 100644 --- a/posthog_flutter/lib/src/posthog_flutter_desktop.dart +++ b/posthog_flutter/lib/src/posthog_flutter_desktop.dart @@ -442,8 +442,13 @@ class PosthogFlutterDesktop extends PosthogFlutterPlatformInterface { _guard('unregister', (client) => client.unregister(key)); @override - Future isFeatureEnabled(String key) => _guardWith('isFeatureEnabled', - false, (client) => client.isFeatureEnabled(key) ?? false); + Future isFeatureEnabled(String key, {bool? sendFeatureFlagEvent}) => + _guardWith( + 'isFeatureEnabled', + false, + (client) => + client.isFeatureEnabled(key, sendEvent: sendFeatureFlagEvent) ?? + false); @override Future reloadFeatureFlags() => _guard( @@ -494,8 +499,15 @@ class PosthogFlutterDesktop extends PosthogFlutterPlatformInterface { }); @override - Future getFeatureFlag({required String key}) => _guardWith( - 'getFeatureFlag', null, (client) => client.getFeatureFlag(key)); + Future getFeatureFlag({ + required String key, + bool? sendFeatureFlagEvent, + }) => + _guardWith( + 'getFeatureFlag', + null, + (client) => + client.getFeatureFlag(key, sendEvent: sendFeatureFlagEvent)); @override Future getFeatureFlagPayload({required String key}) => _guardWith( diff --git a/posthog_flutter/lib/src/posthog_flutter_io.dart b/posthog_flutter/lib/src/posthog_flutter_io.dart index 15a50a6e..efddbe35 100644 --- a/posthog_flutter/lib/src/posthog_flutter_io.dart +++ b/posthog_flutter/lib/src/posthog_flutter_io.dart @@ -566,7 +566,8 @@ class PosthogFlutterIO extends PosthogFlutterPlatformInterface { } @override - Future isFeatureEnabled(String key) async { + Future isFeatureEnabled(String key, + {bool? sendFeatureFlagEvent}) async { if (!isSupportedPlatform()) { return false; } @@ -574,6 +575,7 @@ class PosthogFlutterIO extends PosthogFlutterPlatformInterface { try { return await _methodChannel.invokeMethod('isFeatureEnabled', { 'key': key, + if (sendFeatureFlagEvent != null) 'sendEvent': sendFeatureFlagEvent, }); } on PlatformException catch (exception) { printIfDebug('Exeption on isFeatureEnabled: $exception'); @@ -691,13 +693,19 @@ class PosthogFlutterIO extends PosthogFlutterPlatformInterface { } @override - Future getFeatureFlag({required String key}) async { + Future getFeatureFlag({ + required String key, + bool? sendFeatureFlagEvent, + }) async { if (!isSupportedPlatform()) { return null; } try { - return await _methodChannel.invokeMethod('getFeatureFlag', {'key': key}); + return await _methodChannel.invokeMethod('getFeatureFlag', { + 'key': key, + if (sendFeatureFlagEvent != null) 'sendEvent': sendFeatureFlagEvent, + }); } on PlatformException catch (exception) { printIfDebug('Exeption on getFeatureFlag: $exception'); return null; diff --git a/posthog_flutter/lib/src/posthog_flutter_platform_interface.dart b/posthog_flutter/lib/src/posthog_flutter_platform_interface.dart index c242f772..4758b0d7 100644 --- a/posthog_flutter/lib/src/posthog_flutter_platform_interface.dart +++ b/posthog_flutter/lib/src/posthog_flutter_platform_interface.dart @@ -169,7 +169,7 @@ abstract class PosthogFlutterPlatformInterface extends PlatformInterface { throw UnimplementedError('unregister() has not been implemented.'); } - Future isFeatureEnabled(String key) { + Future isFeatureEnabled(String key, {bool? sendFeatureFlagEvent}) { throw UnimplementedError('isFeatureEnabled() has not been implemented.'); } @@ -216,7 +216,10 @@ abstract class PosthogFlutterPlatformInterface extends PlatformInterface { throw UnimplementedError('group() has not been implemented.'); } - Future getFeatureFlag({required String key}) { + Future getFeatureFlag({ + required String key, + bool? sendFeatureFlagEvent, + }) { throw UnimplementedError('getFeatureFlag() has not been implemented.'); } diff --git a/posthog_flutter/lib/src/posthog_flutter_web_handler.dart b/posthog_flutter/lib/src/posthog_flutter_web_handler.dart index 211a8dca..fbeac8b4 100644 --- a/posthog_flutter/lib/src/posthog_flutter_web_handler.dart +++ b/posthog_flutter/lib/src/posthog_flutter_web_handler.dart @@ -33,7 +33,7 @@ extension PostHogExtension on PostHog { external JSAny? get_distinct_id(); external void reset(); external void debug(JSAny debug); - external JSAny? isFeatureEnabled(JSAny key); + external JSAny? isFeatureEnabled(JSAny key, [JSAny? options]); external void group(JSAny type, JSAny key, JSAny properties); external void reloadFeatureFlags(); external void setPersonProperties( @@ -56,7 +56,7 @@ extension PostHogExtension on PostHog { external void opt_out_capturing(); // ignore: non_constant_identifier_names external bool has_opted_out_capturing(); - external JSAny? getFeatureFlag(JSAny key); + external JSAny? getFeatureFlag(JSAny key, [JSAny? options]); external JSAny? getFeatureFlagPayload(JSAny key); external JSAny? getFeatureFlagResult(JSAny key, [JSAny? options]); external void register(JSAny properties); @@ -116,6 +116,13 @@ Map safeMapConversion(dynamic mapData) { return {}; } +// An absent sendEvent leaves send_event unset so posthog-js applies its own +// default. +JSAny? _flagOptions(Map args) { + final sendEvent = args['sendEvent'] as bool?; + return {if (sendEvent != null) 'send_event': sendEvent}.jsify(); +} + bool _sdkInfoOverridden = false; void _maybeOverrideSDKInfo() { @@ -309,9 +316,10 @@ Future handleWebMethodCall(MethodCall call) async { break; case 'isFeatureEnabled': final key = args['key'] as String; - final isFeatureEnabled = - posthog?.isFeatureEnabled(stringToJSAny(key))?.dartify() as bool? ?? - false; + final isFeatureEnabled = posthog + ?.isFeatureEnabled(stringToJSAny(key), _flagOptions(args)) + ?.dartify() as bool? ?? + false; return isFeatureEnabled; case 'group': final groupType = args['groupType'] as String; @@ -365,7 +373,8 @@ Future handleWebMethodCall(MethodCall call) async { case 'getFeatureFlag': final key = args['key'] as String; - final featureFlag = posthog?.getFeatureFlag(stringToJSAny(key)); + final featureFlag = + posthog?.getFeatureFlag(stringToJSAny(key), _flagOptions(args)); return featureFlag?.dartify(); case 'getFeatureFlagPayload': final key = args['key'] as String; diff --git a/posthog_flutter/test/posthog_flutter_desktop_test.dart b/posthog_flutter/test/posthog_flutter_desktop_test.dart index 314c9eb8..37d2ee45 100644 --- a/posthog_flutter/test/posthog_flutter_desktop_test.dart +++ b/posthog_flutter/test/posthog_flutter_desktop_test.dart @@ -1802,6 +1802,56 @@ void main() { expect(view['distinct_id'], distinctId); }); + test(r'sendFeatureFlagEvent false skips $feature_flag_called', () async { + final server = await LocalPostHogServer.start(); + server.respond = (_) => PostHogResponse.json(_variantFlagResponse()); + final flagsLoaded = Completer(); + final config = configFor(server) + ..onFeatureFlags = () { + if (!flagsLoaded.isCompleted) flagsLoaded.complete(); + }; + final platform = await setUpPlatform(config); + await flagsLoaded.future; + + expect( + await platform.isFeatureEnabled('variant-flag', + sendFeatureFlagEvent: false), + isTrue); + expect( + await platform.getFeatureFlag( + key: 'variant-flag', sendFeatureFlagEvent: false), + 'test-variant'); + await platform.capture(eventName: 'sentinel event'); + await server.waitForEvent('sentinel event'); + expect(server.eventNames, isNot(contains(r'$feature_flag_called'))); + }); + + test( + r'getFeatureFlag follows config.sendFeatureFlagEvent unless overridden', + () async { + final server = await LocalPostHogServer.start(); + server.respond = (_) => PostHogResponse.json(_variantFlagResponse()); + final flagsLoaded = Completer(); + final config = configFor(server) + ..sendFeatureFlagEvent = false + ..onFeatureFlags = () { + if (!flagsLoaded.isCompleted) flagsLoaded.complete(); + }; + final platform = await setUpPlatform(config); + await flagsLoaded.future; + + expect( + await platform.getFeatureFlag(key: 'variant-flag'), 'test-variant'); + await platform.capture(eventName: 'sentinel event'); + await server.waitForEvent('sentinel event'); + expect(server.eventNames, isNot(contains(r'$feature_flag_called'))); + + await platform.getFeatureFlag( + key: 'variant-flag', sendFeatureFlagEvent: true); + final called = await server.waitForEvent(r'$feature_flag_called'); + expect(_propertiesOf(called)[r'$feature_flag'], 'variant-flag'); + }); + test('onFeatureFlags reports the flags cached by the previous launch', () async { final server = await LocalPostHogServer.start(); diff --git a/posthog_flutter/test/posthog_flutter_io_test.dart b/posthog_flutter/test/posthog_flutter_io_test.dart index 5648ecf9..9f65c988 100644 --- a/posthog_flutter/test/posthog_flutter_io_test.dart +++ b/posthog_flutter/test/posthog_flutter_io_test.dart @@ -102,6 +102,29 @@ void main() { }); }); + group('PosthogFlutterIO sendFeatureFlagEvent', () { + for (final sendFeatureFlagEvent in [null, true, false]) { + final expected = { + 'key': 'flag', + if (sendFeatureFlagEvent != null) 'sendEvent': sendFeatureFlagEvent, + }; + + test('getFeatureFlag sends $expected', () async { + await posthogFlutterIO.getFeatureFlag( + key: 'flag', sendFeatureFlagEvent: sendFeatureFlagEvent); + expect(log.single.method, 'getFeatureFlag'); + expect(log.single.arguments, expected); + }); + + test('isFeatureEnabled sends $expected', () async { + await posthogFlutterIO.isFeatureEnabled('flag', + sendFeatureFlagEvent: sendFeatureFlagEvent); + expect(log.single.method, 'isFeatureEnabled'); + expect(log.single.arguments, expected); + }); + } + }); + group('PosthogFlutterIO onFeatureFlags via setup', () { test( 'setup initializes method call handler and registers callback if provided', diff --git a/posthog_flutter/test/posthog_flutter_platform_interface_fake.dart b/posthog_flutter/test/posthog_flutter_platform_interface_fake.dart index 27a2ce89..96d4b180 100644 --- a/posthog_flutter/test/posthog_flutter_platform_interface_fake.dart +++ b/posthog_flutter/test/posthog_flutter_platform_interface_fake.dart @@ -97,6 +97,8 @@ class PosthogFlutterPlatformFake extends PosthogFlutterPlatformInterface { final Map featureFlagValues = {}; final Map featureFlagPayloads = {}; + final List> flagReadCalls = []; + // Call tracking for getFeatureFlagResult final List> getFeatureFlagResultCalls = []; @@ -269,10 +271,29 @@ class PosthogFlutterPlatformFake extends PosthogFlutterPlatformInterface { } @override - Future getFeatureFlag({required String key}) async { + Future getFeatureFlag({ + required String key, + bool? sendFeatureFlagEvent, + }) async { + flagReadCalls.add({ + 'method': 'getFeatureFlag', + 'key': key, + 'sendFeatureFlagEvent': sendFeatureFlagEvent, + }); return featureFlagValues[key]; } + @override + Future isFeatureEnabled(String key, + {bool? sendFeatureFlagEvent}) async { + flagReadCalls.add({ + 'method': 'isFeatureEnabled', + 'key': key, + 'sendFeatureFlagEvent': sendFeatureFlagEvent, + }); + return featureFlagValues[key] == true; + } + @override Future getFeatureFlagPayload({required String key}) async { return featureFlagPayloads[key]; diff --git a/posthog_flutter/test/posthog_flutter_web_handler_test.dart b/posthog_flutter/test/posthog_flutter_web_handler_test.dart index b2164a06..2a9a3849 100644 --- a/posthog_flutter/test/posthog_flutter_web_handler_test.dart +++ b/posthog_flutter/test/posthog_flutter_web_handler_test.dart @@ -234,6 +234,45 @@ void main() { expect(screenNames[r'$feature_view'], isNull); }); + group('PosthogFlutterWeb sendFeatureFlagEvent', () { + late Map optionsByMethod; + + setUp(() { + optionsByMethod = {}; + final fake = JSObject(); + for (final method in ['getFeatureFlag', 'isFeatureEnabled']) { + fake.setProperty( + method.toJS, + ((JSString key, JSAny? options) { + optionsByMethod[method] = options.dartify(); + return true.toJS; + }).toJS, + ); + } + globalContext.setProperty('posthog'.toJS, fake); + }); + + for (final (sendFeatureFlagEvent, expected) in [ + (null, {}), + (false, {'send_event': false}), + (true, {'send_event': true}), + ]) { + test('maps $sendFeatureFlagEvent to posthog-js options $expected', + () async { + final web = PosthogFlutterWeb(); + await web.getFeatureFlag( + key: 'flag', sendFeatureFlagEvent: sendFeatureFlagEvent); + await web.isFeatureEnabled('flag', + sendFeatureFlagEvent: sendFeatureFlagEvent); + + expect(optionsByMethod, { + 'getFeatureFlag': expected, + 'isFeatureEnabled': expected, + }); + }); + } + }); + group('PosthogFlutterWeb captureException', () { test('handles native JavaScript failures from posthog-js', () async { final failWithJavaScriptError = globalContext.callMethod( diff --git a/posthog_flutter/test/posthog_test.dart b/posthog_flutter/test/posthog_test.dart index af07e2fc..11d6efb8 100644 --- a/posthog_flutter/test/posthog_test.dart +++ b/posthog_flutter/test/posthog_test.dart @@ -451,6 +451,24 @@ void main() { ]); }); + test('getFeatureFlag and isFeatureEnabled forward sendFeatureFlagEvent', + () async { + final fake = PosthogFlutterPlatformFake(); + PosthogFlutterPlatformInterface.instance = fake; + + await Posthog().getFeatureFlag('a'); + await Posthog().getFeatureFlag('b', sendFeatureFlagEvent: false); + await Posthog().isFeatureEnabled('c'); + await Posthog().isFeatureEnabled('d', sendFeatureFlagEvent: true); + + expect(fake.flagReadCalls, [ + {'method': 'getFeatureFlag', 'key': 'a', 'sendFeatureFlagEvent': null}, + {'method': 'getFeatureFlag', 'key': 'b', 'sendFeatureFlagEvent': false}, + {'method': 'isFeatureEnabled', 'key': 'c', 'sendFeatureFlagEvent': null}, + {'method': 'isFeatureEnabled', 'key': 'd', 'sendFeatureFlagEvent': true}, + ]); + }); + group('getFeatureFlagResult', () { late PosthogFlutterPlatformFake fakePlatformInterface; From a6213ab2510945a40f27e503d8fd6c6d34434c09 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Mon, 26 Oct 2026 13:36:42 -0400 Subject: [PATCH 2/2] refactor(core): read sendFeatureFlagEvent instead of its deprecated alias --- posthog_flutter/lib/src/core/posthog_core.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/posthog_flutter/lib/src/core/posthog_core.dart b/posthog_flutter/lib/src/core/posthog_core.dart index 90faab7a..b7ffc7ab 100644 --- a/posthog_flutter/lib/src/core/posthog_core.dart +++ b/posthog_flutter/lib/src/core/posthog_core.dart @@ -19,7 +19,7 @@ import 'uuid.dart'; /// It extends [PostHogCoreStateless] with state management for sessions, /// identity, feature flags, and person profiles. abstract class PostHogCore extends PostHogCoreStateless { - final bool _sendFeatureFlagEvents; + final bool _sendFeatureFlagEvent; final PostHogPersonProfiles _personProfiles; final List? _evaluationContexts; @@ -56,7 +56,7 @@ abstract class PostHogCore extends PostHogCoreStateless { bool _flagsRequestAnnounced = false; PostHogCore(PostHogConfig config, {required super.storage}) - : _sendFeatureFlagEvents = config.sendFeatureFlagEvents, + : _sendFeatureFlagEvent = config.sendFeatureFlagEvent, _personProfiles = config.personProfiles, _evaluationContexts = config.evaluationContexts == null ? null @@ -865,13 +865,13 @@ abstract class PostHogCore extends PostHogCoreStateless { } /// Gets a feature flag value, capturing `$feature_flag_called` when - /// [sendEvent] is true, or when it is null and `sendFeatureFlagEvents` is + /// [sendEvent] is true, or when it is null and `sendFeatureFlagEvent` is /// enabled in the config. /// /// Returns null if the flag does not exist or has not been loaded. PostHogFeatureFlagValue? getFeatureFlag(String key, {bool? sendEvent}) { final result = getFeatureFlagResult(key, - sendEvent: sendEvent ?? _sendFeatureFlagEvents); + sendEvent: sendEvent ?? _sendFeatureFlagEvent); if (result == null) return null; return result.variant ?? result.enabled; }