From e61bb9f50980e53e84ba31c576158ab07c80e6c7 Mon Sep 17 00:00:00 2001 From: Jonas Uekoetter Date: Sat, 14 Jun 2025 18:09:25 +0200 Subject: [PATCH 01/11] Add framework feature flags --- ...er_framework_feature_flag_integration.dart | 29 +++++++++++++++++++ flutter/lib/src/sentry_flutter.dart | 4 +++ 2 files changed, 33 insertions(+) create mode 100644 flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart diff --git a/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart b/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart new file mode 100644 index 0000000000..5fdc718075 --- /dev/null +++ b/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart @@ -0,0 +1,29 @@ +import 'dart:async'; + +import 'package:sentry/sentry.dart'; + +// See the following PS for the introduction +// https://github.com/flutter/flutter/pull/168437 +class FlutterFrameworkFeatureFlagIntegration extends Integration { + @override + FutureOr call(Hub hub, SentryOptions options) { + final debugEnabledFeatureFlags = { + ...const String.fromEnvironment('FLUTTER_ENABLED_FEATURE_FLAGS') + .split(','), + }; + + for(final featureFlag in debugEnabledFeatureFlags) { + Sentry.addFeatureFlag(featureFlag, true); + } + options.sdk.addIntegration('FlutterFrameworkFeatureFlagIntegration'); + } +} + +extension FlutterFrameworkFeatureFlagIntegrationX on List> { + /// For better tree-shake-ability we only add the integration if a feature flag is enabled. + void addFlutterFrameworkFeatureFlagIntegration() { + if(const bool.hasEnvironment('FLUTTER_ENABLED_FEATURE_FLAGS')) { + add(FlutterFrameworkFeatureFlagIntegration()); + } + } +} \ No newline at end of file diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index ca1dfbcbaf..929adf97de 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -18,6 +18,7 @@ import 'file_system_transport.dart'; import 'flutter_exception_type_identifier.dart'; import 'frame_callback_handler.dart'; import 'integrations/connectivity/connectivity_integration.dart'; +import 'integrations/flutter_framework_feature_flag_integration.dart'; import 'integrations/frames_tracking_integration.dart'; import 'integrations/integrations.dart'; import 'integrations/native_app_start_handler.dart'; @@ -172,6 +173,9 @@ mixin SentryFlutter { // This tracks Flutter application events, such as lifecycle events. integrations.add(WidgetsBindingIntegration()); + // Adds Flutter framework feature flags. + integrations.addFlutterFrameworkFeatureFlagIntegration(); + // The ordering here matters, as we'd like to first start the native integration. // That allow us to send events to the network and then the Flutter integrations. final native = _native; From e91d49262cd36a1b78ebfd84b95c8c7a735080b0 Mon Sep 17 00:00:00 2001 From: Jonas Uekoetter Date: Sat, 14 Jun 2025 18:15:23 +0200 Subject: [PATCH 02/11] explanation how that works --- .../flutter_framework_feature_flag_integration.dart | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart b/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart index 5fdc718075..69374f7f97 100644 --- a/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart +++ b/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart @@ -2,8 +2,13 @@ import 'dart:async'; import 'package:sentry/sentry.dart'; -// See the following PS for the introduction -// https://github.com/flutter/flutter/pull/168437 +// The Flutter framework feature flag works like this: +// An enabled (experimental) feature gets added to the `FLUTTER_ENABLED_FEATURE_FLAGS` +// dart define. Being in there means the feature is enabled, the feature is disabled +// if it's not in there. +// As a result, we also don't know the whole list of flags, but only the active ones. +// +// See https://github.com/flutter/flutter/pull/168437 class FlutterFrameworkFeatureFlagIntegration extends Integration { @override FutureOr call(Hub hub, SentryOptions options) { @@ -20,7 +25,7 @@ class FlutterFrameworkFeatureFlagIntegration extends Integration } extension FlutterFrameworkFeatureFlagIntegrationX on List> { - /// For better tree-shake-ability we only add the integration if a feature flag is enabled. + /// For better tree-shake-ability we only add the integration if any feature flag is enabled. void addFlutterFrameworkFeatureFlagIntegration() { if(const bool.hasEnvironment('FLUTTER_ENABLED_FEATURE_FLAGS')) { add(FlutterFrameworkFeatureFlagIntegration()); From 02407278e0ae47317f3e871c8e8a24d70b745d86 Mon Sep 17 00:00:00 2001 From: Jonas Uekoetter Date: Sat, 14 Jun 2025 18:18:12 +0200 Subject: [PATCH 03/11] extract constant --- ...er_framework_feature_flag_integration.dart | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart b/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart index 69374f7f97..24d68f2e35 100644 --- a/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart +++ b/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'package:sentry/sentry.dart'; +const _featureFlag = 'FLUTTER_ENABLED_FEATURE_FLAGS'; + // The Flutter framework feature flag works like this: // An enabled (experimental) feature gets added to the `FLUTTER_ENABLED_FEATURE_FLAGS` // dart define. Being in there means the feature is enabled, the feature is disabled @@ -9,26 +11,26 @@ import 'package:sentry/sentry.dart'; // As a result, we also don't know the whole list of flags, but only the active ones. // // See https://github.com/flutter/flutter/pull/168437 -class FlutterFrameworkFeatureFlagIntegration extends Integration { +class FlutterFrameworkFeatureFlagIntegration + extends Integration { @override FutureOr call(Hub hub, SentryOptions options) { - final debugEnabledFeatureFlags = { - ...const String.fromEnvironment('FLUTTER_ENABLED_FEATURE_FLAGS') - .split(','), - }; + final debugEnabledFeatureFlags = + const String.fromEnvironment(_featureFlag).split(','); - for(final featureFlag in debugEnabledFeatureFlags) { + for (final featureFlag in debugEnabledFeatureFlags) { Sentry.addFeatureFlag(featureFlag, true); } options.sdk.addIntegration('FlutterFrameworkFeatureFlagIntegration'); } } -extension FlutterFrameworkFeatureFlagIntegrationX on List> { +extension FlutterFrameworkFeatureFlagIntegrationX + on List> { /// For better tree-shake-ability we only add the integration if any feature flag is enabled. void addFlutterFrameworkFeatureFlagIntegration() { - if(const bool.hasEnvironment('FLUTTER_ENABLED_FEATURE_FLAGS')) { + if (const bool.hasEnvironment(_featureFlag)) { add(FlutterFrameworkFeatureFlagIntegration()); } } -} \ No newline at end of file +} From 3716ca525958a4c72e8fa9ceb256f3370e500428 Mon Sep 17 00:00:00 2001 From: Jonas Uekoetter Date: Sat, 14 Jun 2025 18:43:20 +0200 Subject: [PATCH 04/11] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fee5124be..6fd201e2ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- Report Flutter framework feature flags ([#2991](https://github.com/getsentry/sentry-dart/pull/2991)) + ## 9.0.0-RC.4 ### Enhancements From cb4f4b435e83de890d4870ccc80685639f180980 Mon Sep 17 00:00:00 2001 From: Jonas Uekoetter Date: Tue, 17 Jun 2025 13:10:00 +0200 Subject: [PATCH 05/11] add tests --- ...er_framework_feature_flag_integration.dart | 9 ++++-- ...amework_feature_flag_integration_test.dart | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart diff --git a/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart b/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart index 24d68f2e35..fe142ec18d 100644 --- a/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart +++ b/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:sentry/sentry.dart'; const _featureFlag = 'FLUTTER_ENABLED_FEATURE_FLAGS'; @@ -13,10 +14,14 @@ const _featureFlag = 'FLUTTER_ENABLED_FEATURE_FLAGS'; // See https://github.com/flutter/flutter/pull/168437 class FlutterFrameworkFeatureFlagIntegration extends Integration { + + final String flags; + + FlutterFrameworkFeatureFlagIntegration({@visibleForTesting this.flags = const String.fromEnvironment(_featureFlag)}); + @override FutureOr call(Hub hub, SentryOptions options) { - final debugEnabledFeatureFlags = - const String.fromEnvironment(_featureFlag).split(','); + final debugEnabledFeatureFlags = flags.split(','); for (final featureFlag in debugEnabledFeatureFlags) { Sentry.addFeatureFlag(featureFlag, true); diff --git a/flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart b/flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart new file mode 100644 index 0000000000..6d71e67464 --- /dev/null +++ b/flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart @@ -0,0 +1,28 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/src/integrations/flutter_framework_feature_flag_integration.dart'; + +import '../mocks.dart'; +import '../mocks.mocks.dart'; + +void main() { + group(FlutterFrameworkFeatureFlagIntegration, () { + test('adds sdk integration', () { + final options = defaultTestOptions(); + FlutterFrameworkFeatureFlagIntegration(flags: 'foo,bar,baz') + .call(MockHub(), options); + + expect( + options.sdk.integrations + .contains('FlutterFrameworkFeatureFlagIntegration'), + true); + }); + + test('adds feature flags', () { + final options = defaultTestOptions(); + FlutterFrameworkFeatureFlagIntegration(flags: 'foo,bar,baz') + .call(MockHub(), options); + + // TODO what expect to write here? + }); + }); +} From b0fa43f3ebb7ffe85f1a468844570eac4bd43ae2 Mon Sep 17 00:00:00 2001 From: Jonas Uekoetter Date: Tue, 17 Jun 2025 13:29:10 +0200 Subject: [PATCH 06/11] format --- .../flutter_framework_feature_flag_integration.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart b/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart index fe142ec18d..1e5fd80abf 100644 --- a/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart +++ b/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart @@ -14,16 +14,17 @@ const _featureFlag = 'FLUTTER_ENABLED_FEATURE_FLAGS'; // See https://github.com/flutter/flutter/pull/168437 class FlutterFrameworkFeatureFlagIntegration extends Integration { - final String flags; - FlutterFrameworkFeatureFlagIntegration({@visibleForTesting this.flags = const String.fromEnvironment(_featureFlag)}); + FlutterFrameworkFeatureFlagIntegration({ + @visibleForTesting this.flags = const String.fromEnvironment(_featureFlag), + }); @override FutureOr call(Hub hub, SentryOptions options) { - final debugEnabledFeatureFlags = flags.split(','); + final enabledFeatureFlags = flags.split(','); - for (final featureFlag in debugEnabledFeatureFlags) { + for (final featureFlag in enabledFeatureFlags) { Sentry.addFeatureFlag(featureFlag, true); } options.sdk.addIntegration('FlutterFrameworkFeatureFlagIntegration'); From f8946ddece3dddea1894303d996dc0741f8f2de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Uek=C3=B6tter?= Date: Tue, 17 Jun 2025 18:39:56 +0200 Subject: [PATCH 07/11] Change integration name Co-authored-by: Giancarlo Buenaflor --- .../flutter_framework_feature_flag_integration.dart | 2 +- .../flutter_framework_feature_flag_integration_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart b/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart index 1e5fd80abf..3725b98e4f 100644 --- a/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart +++ b/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart @@ -27,7 +27,7 @@ class FlutterFrameworkFeatureFlagIntegration for (final featureFlag in enabledFeatureFlags) { Sentry.addFeatureFlag(featureFlag, true); } - options.sdk.addIntegration('FlutterFrameworkFeatureFlagIntegration'); + options.sdk.addIntegration('FlutterFrameworkFeatureFlag'); } } diff --git a/flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart b/flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart index 6d71e67464..162f136298 100644 --- a/flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart +++ b/flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart @@ -13,7 +13,7 @@ void main() { expect( options.sdk.integrations - .contains('FlutterFrameworkFeatureFlagIntegration'), + .contains('FlutterFrameworkFeatureFlag'), true); }); From 23deec5708c5068f9bcd3dde744265f50b77d9b9 Mon Sep 17 00:00:00 2001 From: Jonas Uekoetter Date: Tue, 17 Jun 2025 19:09:01 +0200 Subject: [PATCH 08/11] tests are complete --- ...amework_feature_flag_integration_test.dart | 57 +++++++++++++++---- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart b/flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart index 162f136298..567e15261c 100644 --- a/flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart +++ b/flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart @@ -1,28 +1,63 @@ import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry/sentry.dart'; import 'package:sentry_flutter/src/integrations/flutter_framework_feature_flag_integration.dart'; -import '../mocks.dart'; -import '../mocks.mocks.dart'; - void main() { group(FlutterFrameworkFeatureFlagIntegration, () { + late Fixture fixture; + + setUp(() async { + fixture = Fixture(); + + await Sentry.init((options) { + options.dsn = 'https://example.com/sentry-dsn'; + }); + + // ignore: invalid_use_of_internal_member + fixture.hub = Sentry.currentHub; + // ignore: invalid_use_of_internal_member + fixture.options = fixture.hub.options; + }); + + tearDown(() { + Sentry.close(); + }); + test('adds sdk integration', () { - final options = defaultTestOptions(); - FlutterFrameworkFeatureFlagIntegration(flags: 'foo,bar,baz') - .call(MockHub(), options); + final sut = fixture.getSut('foo,bar,baz'); + sut.call(fixture.hub, fixture.options); expect( - options.sdk.integrations + fixture.options.sdk.integrations .contains('FlutterFrameworkFeatureFlag'), true); }); test('adds feature flags', () { - final options = defaultTestOptions(); - FlutterFrameworkFeatureFlagIntegration(flags: 'foo,bar,baz') - .call(MockHub(), options); + final sut = fixture.getSut('foo,bar,baz'); + sut.call(fixture.hub, fixture.options); + + // ignore: invalid_use_of_internal_member + final featureFlags = fixture.hub.scope.contexts[SentryFeatureFlags.type] + as SentryFeatureFlags?; - // TODO what expect to write here? + expect(featureFlags, isNotNull); + expect(featureFlags?.values.length, 3); + expect(featureFlags?.values.first.flag, 'foo'); + expect(featureFlags?.values.first.result, true); + expect(featureFlags?.values[1].flag, 'bar'); + expect(featureFlags?.values[1].result, true); + expect(featureFlags?.values[2].flag, 'baz'); + expect(featureFlags?.values[2].result, true); }); }); } + +class Fixture { + late Hub hub; + late SentryOptions options; + + FlutterFrameworkFeatureFlagIntegration getSut(String features) { + return FlutterFrameworkFeatureFlagIntegration(flags: features); + } +} From b54db3c34c36cba85da7eb4b18a1ab3b68682056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Uek=C3=B6tter?= Date: Thu, 3 Jul 2025 13:25:49 +0200 Subject: [PATCH 09/11] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbe9eda97d..fb65f82287 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,7 +80,7 @@ void initState() { ``` - Add `message` parameter to `captureException()` ([#2882](https://github.com/getsentry/sentry-dart/pull/2882)) - Add module in SentryStackFrame ([#2931](https://github.com/getsentry/sentry-dart/pull/2931)) - - Set `SentryOptions.includeModuleInStackTrace = true` to enable this. This may change grouping of exceptions. + - Set `SentryOptions.includeModuleInStackTrace = true` to enable this. This may change grouping of exceptions. ### Dependencies From 4d09084f993b80cb39b246a50b391fe31e4b7159 Mon Sep 17 00:00:00 2001 From: Jonas Uekoetter Date: Thu, 3 Jul 2025 15:32:08 +0200 Subject: [PATCH 10/11] prefix flags --- CHANGELOG.md | 10 ++++++++-- .../flutter_framework_feature_flag_integration.dart | 2 +- ...lutter_framework_feature_flag_integration_test.dart | 6 +++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad079de53c..86610afab6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +# Unreleased + +### Features + +- Report Flutter framework feature flags ([#2991](https://github.com/getsentry/sentry-dart/pull/2991)) + - Search for feature flags that are prefixed with `flutter:*` + - This works on Flutter builds that include [this PR](https://github.com/flutter/flutter/pull/171545) + ## 9.3.0 ### Breaking Change (Tooling) @@ -18,8 +26,6 @@ // configure your feedback widget options.feedback.showBranding = false; ``` -- Report Flutter framework feature flags ([#2991](https://github.com/getsentry/sentry-dart/pull/2991)) - - This works on Flutter builds that include [this PR](https://github.com/flutter/flutter/pull/171545) ## 9.2.0 diff --git a/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart b/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart index 3725b98e4f..17c378246a 100644 --- a/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart +++ b/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart @@ -25,7 +25,7 @@ class FlutterFrameworkFeatureFlagIntegration final enabledFeatureFlags = flags.split(','); for (final featureFlag in enabledFeatureFlags) { - Sentry.addFeatureFlag(featureFlag, true); + Sentry.addFeatureFlag('flutter:$featureFlag', true); } options.sdk.addIntegration('FlutterFrameworkFeatureFlag'); } diff --git a/flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart b/flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart index 567e15261c..e8d4016731 100644 --- a/flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart +++ b/flutter/test/integrations/flutter_framework_feature_flag_integration_test.dart @@ -43,11 +43,11 @@ void main() { expect(featureFlags, isNotNull); expect(featureFlags?.values.length, 3); - expect(featureFlags?.values.first.flag, 'foo'); + expect(featureFlags?.values.first.flag, 'flutter:foo'); expect(featureFlags?.values.first.result, true); - expect(featureFlags?.values[1].flag, 'bar'); + expect(featureFlags?.values[1].flag, 'flutter:bar'); expect(featureFlags?.values[1].result, true); - expect(featureFlags?.values[2].flag, 'baz'); + expect(featureFlags?.values[2].flag, 'flutter:baz'); expect(featureFlags?.values[2].result, true); }); }); From 1d6314e87bb8d07541ab90d71d752d2ca1aec17b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Uek=C3=B6tter?= Date: Thu, 3 Jul 2025 20:55:07 +0200 Subject: [PATCH 11/11] Update flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart --- .../flutter_framework_feature_flag_integration.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart b/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart index 17c378246a..101d7188b6 100644 --- a/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart +++ b/flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart @@ -11,7 +11,12 @@ const _featureFlag = 'FLUTTER_ENABLED_FEATURE_FLAGS'; // if it's not in there. // As a result, we also don't know the whole list of flags, but only the active ones. // -// See https://github.com/flutter/flutter/pull/168437 +// See +// - https://github.com/flutter/flutter/pull/168437 +// - https://github.com/flutter/flutter/pull/171545 +// +// The Flutter feature flag implementation is not meant to be public and can change in a patch release. +// See this discussion https://github.com/getsentry/sentry-dart/pull/2991/files#r2183105202 class FlutterFrameworkFeatureFlagIntegration extends Integration { final String flags;