Skip to content

Commit 224aa18

Browse files
uemanbuenaflor
andauthored
Report Flutter framework feature flags (#2991)
* Add framework feature flags * explanation how that works * extract constant * changelog * add tests * format * Change integration name Co-authored-by: Giancarlo Buenaflor <[email protected]> * tests are complete * Update CHANGELOG.md * prefix flags * Update flutter/lib/src/integrations/flutter_framework_feature_flag_integration.dart * add more tests * Make cursor happy --------- Co-authored-by: Giancarlo Buenaflor <[email protected]>
1 parent 81f83eb commit 224aa18

File tree

4 files changed

+171
-0
lines changed

4 files changed

+171
-0
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
# Unreleased
4+
5+
### Features
6+
7+
- Report Flutter framework feature flags ([#2991](https://github.com/getsentry/sentry-dart/pull/2991))
8+
- Search for feature flags that are prefixed with `flutter:*`
9+
- This works on Flutter builds that include [this PR](https://github.com/flutter/flutter/pull/171545)
10+
311
## 9.4.1
412

513
### Fixes
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import 'package:flutter/foundation.dart';
2+
import 'package:sentry/sentry.dart';
3+
4+
const _featureFlag = 'FLUTTER_ENABLED_FEATURE_FLAGS';
5+
6+
// The Flutter framework feature flag works like this:
7+
// An enabled (experimental) feature gets added to the `FLUTTER_ENABLED_FEATURE_FLAGS`
8+
// dart define. Being in there means the feature is enabled, the feature is disabled
9+
// if it's not in there.
10+
// As a result, we also don't know the whole list of flags, but only the active ones.
11+
//
12+
// See
13+
// - https://github.com/flutter/flutter/pull/168437
14+
// - https://github.com/flutter/flutter/pull/171545
15+
//
16+
// The Flutter feature flag implementation is not meant to be public and can change in a patch release.
17+
// See this discussion https://github.com/getsentry/sentry-dart/pull/2991/files#r2183105202
18+
class FlutterFrameworkFeatureFlagIntegration
19+
extends Integration<SentryOptions> {
20+
final String flags;
21+
22+
FlutterFrameworkFeatureFlagIntegration({
23+
@visibleForTesting this.flags = const String.fromEnvironment(_featureFlag),
24+
});
25+
26+
@override
27+
void call(Hub hub, SentryOptions options) {
28+
if (flags.isEmpty) return;
29+
final enabledFeatureFlags = flags
30+
.split(',')
31+
.map((flag) => flag.trim())
32+
.where((flag) => flag.isNotEmpty);
33+
34+
if (enabledFeatureFlags.isEmpty) {
35+
return;
36+
}
37+
38+
for (final featureFlag in enabledFeatureFlags) {
39+
Sentry.addFeatureFlag('flutter:$featureFlag', true);
40+
}
41+
options.sdk.addIntegration('FlutterFrameworkFeatureFlag');
42+
}
43+
}
44+
45+
extension FlutterFrameworkFeatureFlagIntegrationX
46+
on List<Integration<SentryOptions>> {
47+
/// For better tree-shake-ability we only add the integration if any feature flag is enabled.
48+
void addFlutterFrameworkFeatureFlagIntegration() {
49+
if (const bool.hasEnvironment(_featureFlag)) {
50+
add(FlutterFrameworkFeatureFlagIntegration());
51+
}
52+
}
53+
}

flutter/lib/src/sentry_flutter.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import 'file_system_transport.dart';
1818
import 'flutter_exception_type_identifier.dart';
1919
import 'frame_callback_handler.dart';
2020
import 'integrations/connectivity/connectivity_integration.dart';
21+
import 'integrations/flutter_framework_feature_flag_integration.dart';
2122
import 'integrations/frames_tracking_integration.dart';
2223
import 'integrations/integrations.dart';
2324
import 'integrations/native_app_start_handler.dart';
@@ -172,6 +173,9 @@ mixin SentryFlutter {
172173
// This tracks Flutter application events, such as lifecycle events.
173174
integrations.add(WidgetsBindingIntegration());
174175

176+
// Adds Flutter framework feature flags.
177+
integrations.addFlutterFrameworkFeatureFlagIntegration();
178+
175179
// The ordering here matters, as we'd like to first start the native integration.
176180
// That allow us to send events to the network and then the Flutter integrations.
177181
final native = _native;
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import 'package:flutter_test/flutter_test.dart';
2+
import 'package:sentry/sentry.dart';
3+
import 'package:sentry_flutter/src/integrations/flutter_framework_feature_flag_integration.dart';
4+
5+
void main() {
6+
group(FlutterFrameworkFeatureFlagIntegration, () {
7+
late Fixture fixture;
8+
9+
setUp(() async {
10+
fixture = Fixture();
11+
12+
await Sentry.init((options) {
13+
options.dsn = 'https://example.com/sentry-dsn';
14+
});
15+
16+
// ignore: invalid_use_of_internal_member
17+
fixture.hub = Sentry.currentHub;
18+
// ignore: invalid_use_of_internal_member
19+
fixture.options = fixture.hub.options;
20+
});
21+
22+
tearDown(() {
23+
Sentry.close();
24+
});
25+
26+
test('adds sdk integration', () {
27+
final sut = fixture.getSut('foo,bar,baz');
28+
sut.call(fixture.hub, fixture.options);
29+
30+
expect(
31+
fixture.options.sdk.integrations
32+
.contains('FlutterFrameworkFeatureFlag'),
33+
true);
34+
});
35+
36+
test('adds feature flags', () {
37+
final sut = fixture.getSut('foo,bar,baz');
38+
sut.call(fixture.hub, fixture.options);
39+
40+
// ignore: invalid_use_of_internal_member
41+
final featureFlags = fixture.hub.scope.contexts[SentryFeatureFlags.type]
42+
as SentryFeatureFlags?;
43+
44+
expect(featureFlags, isNotNull);
45+
expect(featureFlags?.values.length, 3);
46+
expect(featureFlags?.values.first.flag, 'flutter:foo');
47+
expect(featureFlags?.values.first.result, true);
48+
expect(featureFlags?.values[1].flag, 'flutter:bar');
49+
expect(featureFlags?.values[1].result, true);
50+
expect(featureFlags?.values[2].flag, 'flutter:baz');
51+
expect(featureFlags?.values[2].result, true);
52+
});
53+
54+
test('skips empty', () {
55+
final sut = fixture.getSut('foo,,bar');
56+
sut.call(fixture.hub, fixture.options);
57+
58+
// ignore: invalid_use_of_internal_member
59+
final featureFlags = fixture.hub.scope.contexts[SentryFeatureFlags.type]
60+
as SentryFeatureFlags?;
61+
62+
expect(featureFlags, isNotNull);
63+
expect(featureFlags?.values.length, 2);
64+
expect(featureFlags?.values.first.flag, 'flutter:foo');
65+
expect(featureFlags?.values.first.result, true);
66+
expect(featureFlags?.values[1].flag, 'flutter:bar');
67+
expect(featureFlags?.values[1].result, true);
68+
});
69+
70+
test('skips empty variant', () {
71+
final sut = fixture.getSut(',');
72+
sut.call(fixture.hub, fixture.options);
73+
74+
// ignore: invalid_use_of_internal_member
75+
final featureFlags = fixture.hub.scope.contexts[SentryFeatureFlags.type]
76+
as SentryFeatureFlags?;
77+
78+
expect(featureFlags, isNull);
79+
});
80+
81+
test('prettifies', () {
82+
final sut = fixture.getSut('foo, bar');
83+
sut.call(fixture.hub, fixture.options);
84+
85+
// ignore: invalid_use_of_internal_member
86+
final featureFlags = fixture.hub.scope.contexts[SentryFeatureFlags.type]
87+
as SentryFeatureFlags?;
88+
89+
expect(featureFlags, isNotNull);
90+
expect(featureFlags?.values.length, 2);
91+
expect(featureFlags?.values.first.flag, 'flutter:foo');
92+
expect(featureFlags?.values.first.result, true);
93+
expect(featureFlags?.values[1].flag, 'flutter:bar');
94+
expect(featureFlags?.values[1].result, true);
95+
});
96+
});
97+
}
98+
99+
class Fixture {
100+
late Hub hub;
101+
late SentryOptions options;
102+
103+
FlutterFrameworkFeatureFlagIntegration getSut(String features) {
104+
return FlutterFrameworkFeatureFlagIntegration(flags: features);
105+
}
106+
}

0 commit comments

Comments
 (0)