diff --git a/.craft.yml b/.craft.yml
index daf9c4106c..19249c5ef9 100644
--- a/.craft.yml
+++ b/.craft.yml
@@ -18,6 +18,7 @@ targets:
drift:
isar:
link:
+ firebase_remote_config:
- name: github
- name: registry
sdks:
@@ -31,4 +32,6 @@ targets:
pub:sentry_hive:
pub:sentry_isar:
# TODO: after we published link we need to add it to the registry repo and then uncomment here
- # pub:sentry_link:
\ No newline at end of file
+ # pub:sentry_link:
+ # TODO: after we published firebase we need to add it to the registry repo and then uncomment here
+ # pub:sentry_firebase_remote_config:
\ No newline at end of file
diff --git a/.github/workflows/diagrams.yml b/.github/workflows/diagrams.yml
index d909808b72..06e9754282 100644
--- a/.github/workflows/diagrams.yml
+++ b/.github/workflows/diagrams.yml
@@ -51,6 +51,14 @@ jobs:
working-directory: ./isar
run: lakos . -i "{test/**,example/**}" | dot -Tsvg -o class-diagram.svg
+ - name: link
+ working-directory: ./link
+ run: lakos . -i "{test/**,example/**}" | dot -Tsvg -o class-diagram.svg
+
+ - name: firebase_remote_config
+ working-directory: ./firebase_remote_config
+ run: lakos . -i "{test/**,example/**}" | dot -Tsvg -o class-diagram.svg
+
# Source: https://stackoverflow.com/a/58035262
- name: Extract branch name
shell: bash
diff --git a/.github/workflows/firebase_remote_config.yml b/.github/workflows/firebase_remote_config.yml
new file mode 100644
index 0000000000..09ad1f6a93
--- /dev/null
+++ b/.github/workflows/firebase_remote_config.yml
@@ -0,0 +1,56 @@
+name: sentry-firebase-remote-config
+on:
+ push:
+ branches:
+ - main
+ - release/**
+ pull_request:
+ paths:
+ - '!**/*.md'
+ - '!**/class-diagram.svg'
+ - '.github/workflows/firebase_remote_config.yml'
+ - '.github/workflows/analyze.yml'
+ - '.github/actions/dart-test/**'
+ - '.github/actions/coverage/**'
+ - 'dart/**'
+ - 'flutter/**'
+ - 'firebase_remote_config/**'
+
+# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value
+concurrency:
+ group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
+ cancel-in-progress: true
+
+jobs:
+ build:
+ name: '${{ matrix.os }} | ${{ matrix.sdk }}'
+ runs-on: ${{ matrix.os }}-latest
+ timeout-minutes: 30
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [macos, ubuntu, windows]
+ sdk: [stable, beta]
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: ./.github/actions/flutter-test
+ with:
+ directory: firebase_remote_config
+ web: false
+
+# TODO: don't set coverage for now to finish publishing it
+# - uses: ./.github/actions/coverage
+# if: runner.os == 'Linux' && matrix.sdk == 'stable'
+# with:
+# token: ${{ secrets.CODECOV_TOKEN }}
+# directory: firebase_remote_config
+# coverage: sentry_firebase_remote_config
+# min-coverage: 55
+
+ analyze:
+ uses: ./.github/workflows/analyze.yml
+ with:
+ package: firebase_remote_config
+ sdk: flutter
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6fbaedec44..54cd0b958c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,20 @@
// Manually track a feature flag
Sentry.addFeatureFlag('my-feature', true);
```
+- Firebase Remote Config Integration ([#2837](https://github.com/getsentry/sentry-dart/pull/2837))
+```dart
+// Add the integration to automatically track feature flags from firebase remote config.
+await SentryFlutter.init(
+ (options) {
+ options.dsn = 'https://example@sentry.io/add-your-dsn-here';
+ options.addIntegration(
+ SentryFirebaseRemoteConfigIntegration(
+ firebaseRemoteConfig: yourRirebaseRemoteConfig,
+ ),
+ );
+ },
+);
+```
### Behavioral changes
diff --git a/firebase_remote_config/.gitignore b/firebase_remote_config/.gitignore
new file mode 100644
index 0000000000..ba521d5a39
--- /dev/null
+++ b/firebase_remote_config/.gitignore
@@ -0,0 +1,14 @@
+# Omit committing pubspec.lock for library packages; see
+# https://dart.dev/guides/libraries/private-files#pubspeclock.
+pubspec.lock
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
diff --git a/firebase_remote_config/.metadata b/firebase_remote_config/.metadata
new file mode 100644
index 0000000000..07eecc71cd
--- /dev/null
+++ b/firebase_remote_config/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "09de023485e95e6d1225c2baa44b8feb85e0d45f"
+ channel: "stable"
+
+project_type: package
diff --git a/firebase_remote_config/CHANGELOG.md b/firebase_remote_config/CHANGELOG.md
new file mode 120000
index 0000000000..04c99a55ca
--- /dev/null
+++ b/firebase_remote_config/CHANGELOG.md
@@ -0,0 +1 @@
+../CHANGELOG.md
\ No newline at end of file
diff --git a/firebase_remote_config/LICENSE b/firebase_remote_config/LICENSE
new file mode 100644
index 0000000000..2a6964d84d
--- /dev/null
+++ b/firebase_remote_config/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Sentry
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/firebase_remote_config/README.md b/firebase_remote_config/README.md
new file mode 100644
index 0000000000..31fe89e293
--- /dev/null
+++ b/firebase_remote_config/README.md
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+===========
+
+
+
+
+
+
+
+
+Sentry integration for `firebase_remote_config` package
+===========
+
+| package | build | pub | likes | popularity | pub points |
+|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------| ------- |
+| sentry_firebase_remote_config | [](https://github.com/getsentry/sentry-dart/actions?query=workflow%3Asentry-firebase) | [](https://pub.dev/packages/sentry_firebase_remote_config) | [](https://pub.dev/packages/sentry_firebase_remote_config/score) | [](https://pub.dev/packages/sentry_firebase_remote_config/score) | [](https://pub.dev/packages/sentry_firebase_remote_config/score)
+
+Integration for [`firebase_remote_config`](https://pub.dev/packages/firebase_remote_config) package. Track changes to firebase boolean values as feature flags in Sentry.io
+
+#### Usage
+
+- Sign up for a Sentry.io account and get a DSN at https://sentry.io.
+
+- Follow the installing instructions on [pub.dev](https://pub.dev/packages/sentry/install).
+
+- Initialize the Sentry SDK using the DSN issued by Sentry.io.
+
+- Call...
+
+```dart
+import 'package:firebase_core/firebase_core.dart';
+import 'package:firebase_remote_config_example/home_page.dart';
+import 'package:flutter/material.dart';
+import 'package:sentry_flutter/sentry_flutter.dart';
+import 'package:sentry_firebase_remote_config/sentry_firebase_remote_config.dart';
+
+import 'firebase_options.dart';
+
+Future main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+ await Firebase.initializeApp(
+ options: DefaultFirebaseOptions.currentPlatform,
+ );
+
+ final remoteConfig = FirebaseRemoteConfig.instance;
+ await remoteConfig.setConfigSettings(RemoteConfigSettings(
+ fetchTimeout: const Duration(minutes: 1),
+ minimumFetchInterval: const Duration(hours: 1),
+ ));
+
+ await SentryFlutter.init(
+ (options) {
+ options.dsn = 'https://example@sentry.io/add-your-dsn-here';
+
+ final sentryFirebaseRemoteConfigIntegration = SentryFirebaseRemoteConfigIntegration(
+ firebaseRemoteConfig: remoteConfig,
+ // Don't call `await remoteConfig.activate();` when firebase config is updated. Per default this is true.
+ activateOnConfigUpdated: false,
+ );
+ options.addIntegration(sentryFirebaseRemoteConfigIntegration);
+ },
+ );
+
+ runApp(const RemoteConfigApp());
+}
+
+class RemoteConfigApp extends StatelessWidget {
+ const RemoteConfigApp({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'Remote Config Example',
+ home: const HomePage(),
+ theme: ThemeData(
+ useMaterial3: true,
+ primarySwatch: Colors.blue,
+ ),
+ );
+ }
+}
+```
+
+#### Resources
+
+* [](https://docs.sentry.io/platforms/flutter/)
+* [](https://docs.sentry.io/platforms/dart/)
+* [](https://github.com/getsentry/sentry-dart/discussions)
+* [](https://discord.gg/PXa5Apfe7K)
+* [](https://stackoverflow.com/questions/tagged/sentry)
+* [](https://twitter.com/intent/follow?screen_name=getsentry)
diff --git a/firebase_remote_config/analysis_options.yaml b/firebase_remote_config/analysis_options.yaml
new file mode 100644
index 0000000000..7119dc352d
--- /dev/null
+++ b/firebase_remote_config/analysis_options.yaml
@@ -0,0 +1,32 @@
+include: package:lints/recommended.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-inference: true
+ strict-raw-types: true
+ errors:
+ # treat missing required parameters as a warning (not a hint)
+ missing_required_param: error
+ # treat missing returns as a warning (not a hint)
+ missing_return: error
+ # allow having TODOs in the code
+ todo: ignore
+ # allow self-reference to deprecated members (we do this because otherwise we have
+ # to annotate every member in every test, assert, etc, when we deprecate something)
+ deprecated_member_use_from_same_package: warning
+ # ignore sentry/path on pubspec as we change it on deployment
+ invalid_dependency: ignore
+ exclude:
+ - example/**
+ - test/mocks/mocks.mocks.dart
+
+linter:
+ rules:
+ - prefer_final_locals
+ - prefer_single_quotes
+ - prefer_relative_imports
+ - unnecessary_brace_in_string_interps
+ - implementation_imports
+ - require_trailing_commas
+ - unawaited_futures
diff --git a/firebase_remote_config/dartdoc_options.yaml b/firebase_remote_config/dartdoc_options.yaml
new file mode 120000
index 0000000000..7cbb8c0d74
--- /dev/null
+++ b/firebase_remote_config/dartdoc_options.yaml
@@ -0,0 +1 @@
+../dart/dartdoc_options.yaml
\ No newline at end of file
diff --git a/firebase_remote_config/example/example.dart b/firebase_remote_config/example/example.dart
new file mode 100644
index 0000000000..eba2d4c1d8
--- /dev/null
+++ b/firebase_remote_config/example/example.dart
@@ -0,0 +1,52 @@
+import 'package:firebase_core/firebase_core.dart';
+import 'package:firebase_remote_config_example/home_page.dart';
+import 'package:flutter/material.dart';
+import 'package:sentry_flutter/sentry_flutter.dart';
+import 'package:sentry_firebase_remote_config/sentry_firebase_remote_config.dart';
+
+import 'firebase_options.dart';
+
+Future main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+ await Firebase.initializeApp(
+ options: DefaultFirebaseOptions.currentPlatform,
+ );
+
+ final remoteConfig = FirebaseRemoteConfig.instance;
+ await remoteConfig.setConfigSettings(RemoteConfigSettings(
+ fetchTimeout: const Duration(minutes: 1),
+ minimumFetchInterval: const Duration(hours: 1),
+ ));
+
+ await SentryFlutter.init(
+ (options) {
+ options.dsn = 'https://example@sentry.io/add-your-dsn-here';
+
+ final sentryFirebaseRemoteConfigIntegration =
+ SentryFirebaseRemoteConfigIntegration(
+ firebaseRemoteConfig: remoteConfig,
+ // Don't call `await remoteConfig.activate();` when firebase config is updated. Per default this is true.
+ activateOnConfigUpdated: false,
+ );
+ options.addIntegration(sentryFirebaseRemoteConfigIntegration);
+ },
+ );
+
+ runApp(const RemoteConfigApp());
+}
+
+class RemoteConfigApp extends StatelessWidget {
+ const RemoteConfigApp({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'Remote Config Example',
+ home: const HomePage(),
+ theme: ThemeData(
+ useMaterial3: true,
+ primarySwatch: Colors.blue,
+ ),
+ );
+ }
+}
diff --git a/firebase_remote_config/lib/sentry_firebase_remote_config.dart b/firebase_remote_config/lib/sentry_firebase_remote_config.dart
new file mode 100644
index 0000000000..39222fe090
--- /dev/null
+++ b/firebase_remote_config/lib/sentry_firebase_remote_config.dart
@@ -0,0 +1,3 @@
+library;
+
+export 'src/sentry_firebase_remote_config_integration.dart';
diff --git a/firebase_remote_config/lib/src/sentry_firebase_remote_config_integration.dart b/firebase_remote_config/lib/src/sentry_firebase_remote_config_integration.dart
new file mode 100644
index 0000000000..c2df37330e
--- /dev/null
+++ b/firebase_remote_config/lib/src/sentry_firebase_remote_config_integration.dart
@@ -0,0 +1,52 @@
+import 'dart:async';
+
+import 'package:firebase_remote_config/firebase_remote_config.dart';
+import 'package:sentry/sentry.dart';
+
+class SentryFirebaseRemoteConfigIntegration extends Integration {
+ SentryFirebaseRemoteConfigIntegration({
+ required FirebaseRemoteConfig firebaseRemoteConfig,
+ bool activateOnConfigUpdated = true,
+ }) : _firebaseRemoteConfig = firebaseRemoteConfig,
+ _activateOnConfigUpdated = activateOnConfigUpdated;
+
+ final FirebaseRemoteConfig _firebaseRemoteConfig;
+ final bool _activateOnConfigUpdated;
+ StreamSubscription? _subscription;
+
+ @override
+ FutureOr call(Hub hub, SentryOptions options) async {
+ _subscription = _firebaseRemoteConfig.onConfigUpdated.listen((event) async {
+ if (_activateOnConfigUpdated) {
+ await _firebaseRemoteConfig.activate();
+ }
+ for (final updatedKey in event.updatedKeys) {
+ final value = _firebaseRemoteConfig.getBoolOrNull(updatedKey);
+ if (value != null) {
+ await Sentry.addFeatureFlag(updatedKey, value);
+ }
+ }
+ });
+ options.sdk.addIntegration('SentryFirebaseRemoteConfigIntegration');
+ }
+
+ @override
+ FutureOr close() async {
+ await _subscription?.cancel();
+ _subscription = null;
+ }
+}
+
+extension _SentryFirebaseRemoteConfig on FirebaseRemoteConfig {
+ bool? getBoolOrNull(String key) {
+ final strValue = getString(key);
+ final lowerCase = strValue.toLowerCase();
+ if (lowerCase == 'true' || lowerCase == '1') {
+ return true;
+ }
+ if (lowerCase == 'false' || lowerCase == '0') {
+ return false;
+ }
+ return null;
+ }
+}
diff --git a/firebase_remote_config/pubspec.yaml b/firebase_remote_config/pubspec.yaml
new file mode 100644
index 0000000000..c6dbf430cf
--- /dev/null
+++ b/firebase_remote_config/pubspec.yaml
@@ -0,0 +1,32 @@
+name: sentry_firebase_remote_config
+description: "Sentry integration to use feature flags from Firebase Remote Config."
+version: 9.0.0-alpha.2
+homepage: https://docs.sentry.io/platforms/flutter/
+repository: https://github.com/getsentry/sentry-dart
+issue_tracker: https://github.com/getsentry/sentry-dart/issues
+
+environment:
+ sdk: '>=3.5.0 <4.0.0'
+ flutter: '>=3.24.0'
+
+platforms:
+ android:
+ ios:
+ macos:
+ linux:
+ windows:
+ web:
+
+dependencies:
+ flutter:
+ sdk: flutter
+ firebase_remote_config: ^5.4.3
+ sentry: ^9.0.0-alpha.2
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ flutter_lints: ^5.0.0
+ coverage: ^1.3.0
+ mockito: ^5.1.0
+ build_runner: ^2.4.6
diff --git a/firebase_remote_config/pubspec_overrides.yaml b/firebase_remote_config/pubspec_overrides.yaml
new file mode 100644
index 0000000000..16e71d16f0
--- /dev/null
+++ b/firebase_remote_config/pubspec_overrides.yaml
@@ -0,0 +1,3 @@
+dependency_overrides:
+ sentry:
+ path: ../dart
diff --git a/firebase_remote_config/test/mocks/mocks.dart b/firebase_remote_config/test/mocks/mocks.dart
new file mode 100644
index 0000000000..579fc1638d
--- /dev/null
+++ b/firebase_remote_config/test/mocks/mocks.dart
@@ -0,0 +1,12 @@
+import 'package:firebase_remote_config/firebase_remote_config.dart';
+import 'package:mockito/annotations.dart';
+import 'package:sentry/sentry.dart';
+import 'dart:async';
+
+@GenerateMocks([
+ Hub,
+ FirebaseRemoteConfig,
+ Stream,
+ StreamSubscription,
+])
+void main() {}
diff --git a/firebase_remote_config/test/mocks/mocks.mocks.dart b/firebase_remote_config/test/mocks/mocks.mocks.dart
new file mode 100644
index 0000000000..6f9eebffda
--- /dev/null
+++ b/firebase_remote_config/test/mocks/mocks.mocks.dart
@@ -0,0 +1,972 @@
+// Mocks generated by Mockito 5.4.5 from annotations
+// in sentry_firebase/test/mocks/mocks.dart.
+// Do not manually edit this file.
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'dart:async' as _i5;
+
+import 'package:firebase_core/firebase_core.dart' as _i3;
+import 'package:firebase_remote_config/firebase_remote_config.dart' as _i7;
+import 'package:firebase_remote_config_platform_interface/firebase_remote_config_platform_interface.dart'
+ as _i4;
+import 'package:mockito/mockito.dart' as _i1;
+import 'package:mockito/src/dummies.dart' as _i8;
+import 'package:sentry/sentry.dart' as _i2;
+import 'package:sentry/src/profiling.dart' as _i6;
+
+// ignore_for_file: type=lint
+// ignore_for_file: avoid_redundant_argument_values
+// ignore_for_file: avoid_setters_without_getters
+// ignore_for_file: comment_references
+// ignore_for_file: deprecated_member_use
+// ignore_for_file: deprecated_member_use_from_same_package
+// ignore_for_file: implementation_imports
+// ignore_for_file: invalid_use_of_visible_for_testing_member
+// ignore_for_file: must_be_immutable
+// ignore_for_file: prefer_const_constructors
+// ignore_for_file: unnecessary_parenthesis
+// ignore_for_file: camel_case_types
+// ignore_for_file: subtype_of_sealed_class
+
+class _FakeSentryOptions_0 extends _i1.SmartFake implements _i2.SentryOptions {
+ _FakeSentryOptions_0(Object parent, Invocation parentInvocation)
+ : super(parent, parentInvocation);
+}
+
+class _FakeSentryId_1 extends _i1.SmartFake implements _i2.SentryId {
+ _FakeSentryId_1(Object parent, Invocation parentInvocation)
+ : super(parent, parentInvocation);
+}
+
+class _FakeScope_2 extends _i1.SmartFake implements _i2.Scope {
+ _FakeScope_2(Object parent, Invocation parentInvocation)
+ : super(parent, parentInvocation);
+}
+
+class _FakeHub_3 extends _i1.SmartFake implements _i2.Hub {
+ _FakeHub_3(Object parent, Invocation parentInvocation)
+ : super(parent, parentInvocation);
+}
+
+class _FakeISentrySpan_4 extends _i1.SmartFake implements _i2.ISentrySpan {
+ _FakeISentrySpan_4(Object parent, Invocation parentInvocation)
+ : super(parent, parentInvocation);
+}
+
+class _FakeFirebaseApp_5 extends _i1.SmartFake implements _i3.FirebaseApp {
+ _FakeFirebaseApp_5(Object parent, Invocation parentInvocation)
+ : super(parent, parentInvocation);
+}
+
+class _FakeDateTime_6 extends _i1.SmartFake implements DateTime {
+ _FakeDateTime_6(Object parent, Invocation parentInvocation)
+ : super(parent, parentInvocation);
+}
+
+class _FakeRemoteConfigSettings_7 extends _i1.SmartFake
+ implements _i4.RemoteConfigSettings {
+ _FakeRemoteConfigSettings_7(Object parent, Invocation parentInvocation)
+ : super(parent, parentInvocation);
+}
+
+class _FakeRemoteConfigValue_8 extends _i1.SmartFake
+ implements _i4.RemoteConfigValue {
+ _FakeRemoteConfigValue_8(Object parent, Invocation parentInvocation)
+ : super(parent, parentInvocation);
+}
+
+class _FakeFuture_9 extends _i1.SmartFake implements _i5.Future {
+ _FakeFuture_9(Object parent, Invocation parentInvocation)
+ : super(parent, parentInvocation);
+}
+
+class _FakeStreamSubscription_10 extends _i1.SmartFake
+ implements _i5.StreamSubscription {
+ _FakeStreamSubscription_10(Object parent, Invocation parentInvocation)
+ : super(parent, parentInvocation);
+}
+
+/// A class which mocks [Hub].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockHub extends _i1.Mock implements _i2.Hub {
+ MockHub() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ _i2.SentryOptions get options => (super.noSuchMethod(
+ Invocation.getter(#options),
+ returnValue: _FakeSentryOptions_0(
+ this,
+ Invocation.getter(#options),
+ ),
+ ) as _i2.SentryOptions);
+
+ @override
+ bool get isEnabled =>
+ (super.noSuchMethod(Invocation.getter(#isEnabled), returnValue: false)
+ as bool);
+
+ @override
+ _i2.SentryId get lastEventId => (super.noSuchMethod(
+ Invocation.getter(#lastEventId),
+ returnValue: _FakeSentryId_1(this, Invocation.getter(#lastEventId)),
+ ) as _i2.SentryId);
+
+ @override
+ _i2.Scope get scope => (super.noSuchMethod(
+ Invocation.getter(#scope),
+ returnValue: _FakeScope_2(this, Invocation.getter(#scope)),
+ ) as _i2.Scope);
+
+ @override
+ set profilerFactory(_i6.SentryProfilerFactory? value) => super.noSuchMethod(
+ Invocation.setter(#profilerFactory, value),
+ returnValueForMissingStub: null,
+ );
+
+ @override
+ _i5.Future<_i2.SentryId> captureEvent(
+ _i2.SentryEvent? event, {
+ dynamic stackTrace,
+ _i2.Hint? hint,
+ _i2.ScopeCallback? withScope,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #captureEvent,
+ [event],
+ {#stackTrace: stackTrace, #hint: hint, #withScope: withScope},
+ ),
+ returnValue: _i5.Future<_i2.SentryId>.value(
+ _FakeSentryId_1(
+ this,
+ Invocation.method(
+ #captureEvent,
+ [event],
+ {#stackTrace: stackTrace, #hint: hint, #withScope: withScope},
+ ),
+ ),
+ ),
+ ) as _i5.Future<_i2.SentryId>);
+
+ @override
+ _i5.Future<_i2.SentryId> captureException(
+ dynamic throwable, {
+ dynamic stackTrace,
+ _i2.Hint? hint,
+ _i2.ScopeCallback? withScope,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #captureException,
+ [throwable],
+ {#stackTrace: stackTrace, #hint: hint, #withScope: withScope},
+ ),
+ returnValue: _i5.Future<_i2.SentryId>.value(
+ _FakeSentryId_1(
+ this,
+ Invocation.method(
+ #captureException,
+ [throwable],
+ {#stackTrace: stackTrace, #hint: hint, #withScope: withScope},
+ ),
+ ),
+ ),
+ ) as _i5.Future<_i2.SentryId>);
+
+ @override
+ _i5.Future<_i2.SentryId> captureMessage(
+ String? message, {
+ _i2.SentryLevel? level,
+ String? template,
+ List? params,
+ _i2.Hint? hint,
+ _i2.ScopeCallback? withScope,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #captureMessage,
+ [message],
+ {
+ #level: level,
+ #template: template,
+ #params: params,
+ #hint: hint,
+ #withScope: withScope,
+ },
+ ),
+ returnValue: _i5.Future<_i2.SentryId>.value(
+ _FakeSentryId_1(
+ this,
+ Invocation.method(
+ #captureMessage,
+ [message],
+ {
+ #level: level,
+ #template: template,
+ #params: params,
+ #hint: hint,
+ #withScope: withScope,
+ },
+ ),
+ ),
+ ),
+ ) as _i5.Future<_i2.SentryId>);
+
+ @override
+ _i5.Future<_i2.SentryId> captureFeedback(
+ _i2.SentryFeedback? feedback, {
+ _i2.Hint? hint,
+ _i2.ScopeCallback? withScope,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #captureFeedback,
+ [feedback],
+ {#hint: hint, #withScope: withScope},
+ ),
+ returnValue: _i5.Future<_i2.SentryId>.value(
+ _FakeSentryId_1(
+ this,
+ Invocation.method(
+ #captureFeedback,
+ [feedback],
+ {#hint: hint, #withScope: withScope},
+ ),
+ ),
+ ),
+ ) as _i5.Future<_i2.SentryId>);
+
+ @override
+ _i5.Future addBreadcrumb(_i2.Breadcrumb? crumb, {_i2.Hint? hint}) =>
+ (super.noSuchMethod(
+ Invocation.method(#addBreadcrumb, [crumb], {#hint: hint}),
+ returnValue: _i5.Future.value(),
+ returnValueForMissingStub: _i5.Future.value(),
+ ) as _i5.Future);
+
+ @override
+ void bindClient(_i2.SentryClient? client) => super.noSuchMethod(
+ Invocation.method(#bindClient, [client]),
+ returnValueForMissingStub: null,
+ );
+
+ @override
+ _i2.Hub clone() => (super.noSuchMethod(
+ Invocation.method(#clone, []),
+ returnValue: _FakeHub_3(this, Invocation.method(#clone, [])),
+ ) as _i2.Hub);
+
+ @override
+ _i5.Future close() => (super.noSuchMethod(
+ Invocation.method(#close, []),
+ returnValue: _i5.Future.value(),
+ returnValueForMissingStub: _i5.Future.value(),
+ ) as _i5.Future);
+
+ @override
+ _i5.FutureOr configureScope(_i2.ScopeCallback? callback) =>
+ (super.noSuchMethod(Invocation.method(#configureScope, [callback]))
+ as _i5.FutureOr);
+
+ @override
+ _i2.ISentrySpan startTransaction(
+ String? name,
+ String? operation, {
+ String? description,
+ DateTime? startTimestamp,
+ bool? bindToScope,
+ bool? waitForChildren,
+ Duration? autoFinishAfter,
+ bool? trimEnd,
+ _i2.OnTransactionFinish? onFinish,
+ Map? customSamplingContext,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #startTransaction,
+ [name, operation],
+ {
+ #description: description,
+ #startTimestamp: startTimestamp,
+ #bindToScope: bindToScope,
+ #waitForChildren: waitForChildren,
+ #autoFinishAfter: autoFinishAfter,
+ #trimEnd: trimEnd,
+ #onFinish: onFinish,
+ #customSamplingContext: customSamplingContext,
+ },
+ ),
+ returnValue: _FakeISentrySpan_4(
+ this,
+ Invocation.method(
+ #startTransaction,
+ [name, operation],
+ {
+ #description: description,
+ #startTimestamp: startTimestamp,
+ #bindToScope: bindToScope,
+ #waitForChildren: waitForChildren,
+ #autoFinishAfter: autoFinishAfter,
+ #trimEnd: trimEnd,
+ #onFinish: onFinish,
+ #customSamplingContext: customSamplingContext,
+ },
+ ),
+ ),
+ ) as _i2.ISentrySpan);
+
+ @override
+ _i2.ISentrySpan startTransactionWithContext(
+ _i2.SentryTransactionContext? transactionContext, {
+ Map? customSamplingContext,
+ DateTime? startTimestamp,
+ bool? bindToScope,
+ bool? waitForChildren,
+ Duration? autoFinishAfter,
+ bool? trimEnd,
+ _i2.OnTransactionFinish? onFinish,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #startTransactionWithContext,
+ [transactionContext],
+ {
+ #customSamplingContext: customSamplingContext,
+ #startTimestamp: startTimestamp,
+ #bindToScope: bindToScope,
+ #waitForChildren: waitForChildren,
+ #autoFinishAfter: autoFinishAfter,
+ #trimEnd: trimEnd,
+ #onFinish: onFinish,
+ },
+ ),
+ returnValue: _FakeISentrySpan_4(
+ this,
+ Invocation.method(
+ #startTransactionWithContext,
+ [transactionContext],
+ {
+ #customSamplingContext: customSamplingContext,
+ #startTimestamp: startTimestamp,
+ #bindToScope: bindToScope,
+ #waitForChildren: waitForChildren,
+ #autoFinishAfter: autoFinishAfter,
+ #trimEnd: trimEnd,
+ #onFinish: onFinish,
+ },
+ ),
+ ),
+ ) as _i2.ISentrySpan);
+
+ @override
+ _i5.Future<_i2.SentryId> captureTransaction(
+ _i2.SentryTransaction? transaction, {
+ _i2.SentryTraceContextHeader? traceContext,
+ _i2.Hint? hint,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #captureTransaction,
+ [transaction],
+ {#traceContext: traceContext, #hint: hint},
+ ),
+ returnValue: _i5.Future<_i2.SentryId>.value(
+ _FakeSentryId_1(
+ this,
+ Invocation.method(
+ #captureTransaction,
+ [transaction],
+ {#traceContext: traceContext, #hint: hint},
+ ),
+ ),
+ ),
+ ) as _i5.Future<_i2.SentryId>);
+
+ @override
+ void setSpanContext(
+ dynamic throwable,
+ _i2.ISentrySpan? span,
+ String? transaction,
+ ) =>
+ super.noSuchMethod(
+ Invocation.method(#setSpanContext, [throwable, span, transaction]),
+ returnValueForMissingStub: null,
+ );
+}
+
+/// A class which mocks [FirebaseRemoteConfig].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockFirebaseRemoteConfig extends _i1.Mock
+ implements _i7.FirebaseRemoteConfig {
+ MockFirebaseRemoteConfig() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ _i3.FirebaseApp get app => (super.noSuchMethod(
+ Invocation.getter(#app),
+ returnValue: _FakeFirebaseApp_5(this, Invocation.getter(#app)),
+ ) as _i3.FirebaseApp);
+
+ @override
+ DateTime get lastFetchTime => (super.noSuchMethod(
+ Invocation.getter(#lastFetchTime),
+ returnValue: _FakeDateTime_6(
+ this,
+ Invocation.getter(#lastFetchTime),
+ ),
+ ) as DateTime);
+
+ @override
+ _i4.RemoteConfigFetchStatus get lastFetchStatus => (super.noSuchMethod(
+ Invocation.getter(#lastFetchStatus),
+ returnValue: _i4.RemoteConfigFetchStatus.noFetchYet,
+ ) as _i4.RemoteConfigFetchStatus);
+
+ @override
+ _i4.RemoteConfigSettings get settings => (super.noSuchMethod(
+ Invocation.getter(#settings),
+ returnValue: _FakeRemoteConfigSettings_7(
+ this,
+ Invocation.getter(#settings),
+ ),
+ ) as _i4.RemoteConfigSettings);
+
+ @override
+ _i5.Stream<_i4.RemoteConfigUpdate> get onConfigUpdated => (super.noSuchMethod(
+ Invocation.getter(#onConfigUpdated),
+ returnValue: _i5.Stream<_i4.RemoteConfigUpdate>.empty(),
+ ) as _i5.Stream<_i4.RemoteConfigUpdate>);
+
+ @override
+ Map get pluginConstants => (super.noSuchMethod(
+ Invocation.getter(#pluginConstants),
+ returnValue: {},
+ ) as Map);
+
+ @override
+ _i5.Future activate() => (super.noSuchMethod(
+ Invocation.method(#activate, []),
+ returnValue: _i5.Future.value(false),
+ ) as _i5.Future);
+
+ @override
+ _i5.Future ensureInitialized() => (super.noSuchMethod(
+ Invocation.method(#ensureInitialized, []),
+ returnValue: _i5.Future.value(),
+ returnValueForMissingStub: _i5.Future.value(),
+ ) as _i5.Future);
+
+ @override
+ _i5.Future fetch() => (super.noSuchMethod(
+ Invocation.method(#fetch, []),
+ returnValue: _i5.Future.value(),
+ returnValueForMissingStub: _i5.Future.value(),
+ ) as _i5.Future);
+
+ @override
+ _i5.Future fetchAndActivate() => (super.noSuchMethod(
+ Invocation.method(#fetchAndActivate, []),
+ returnValue: _i5.Future.value(false),
+ ) as _i5.Future);
+
+ @override
+ Map getAll() => (super.noSuchMethod(
+ Invocation.method(#getAll, []),
+ returnValue: {},
+ ) as Map);
+
+ @override
+ bool getBool(String? key) => (super.noSuchMethod(
+ Invocation.method(#getBool, [key]),
+ returnValue: false,
+ ) as bool);
+
+ @override
+ int getInt(String? key) =>
+ (super.noSuchMethod(Invocation.method(#getInt, [key]), returnValue: 0)
+ as int);
+
+ @override
+ double getDouble(String? key) => (super.noSuchMethod(
+ Invocation.method(#getDouble, [key]),
+ returnValue: 0.0,
+ ) as double);
+
+ @override
+ String getString(String? key) => (super.noSuchMethod(
+ Invocation.method(#getString, [key]),
+ returnValue: _i8.dummyValue(
+ this,
+ Invocation.method(#getString, [key]),
+ ),
+ ) as String);
+
+ @override
+ _i4.RemoteConfigValue getValue(String? key) => (super.noSuchMethod(
+ Invocation.method(#getValue, [key]),
+ returnValue: _FakeRemoteConfigValue_8(
+ this,
+ Invocation.method(#getValue, [key]),
+ ),
+ ) as _i4.RemoteConfigValue);
+
+ @override
+ _i5.Future setConfigSettings(
+ _i4.RemoteConfigSettings? remoteConfigSettings,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(#setConfigSettings, [remoteConfigSettings]),
+ returnValue: _i5.Future.value(),
+ returnValueForMissingStub: _i5.Future.value(),
+ ) as _i5.Future);
+
+ @override
+ _i5.Future setDefaults(Map? defaultParameters) =>
+ (super.noSuchMethod(
+ Invocation.method(#setDefaults, [defaultParameters]),
+ returnValue: _i5.Future.value(),
+ returnValueForMissingStub: _i5.Future.value(),
+ ) as _i5.Future);
+
+ @override
+ _i5.Future setCustomSignals(Map? customSignals) =>
+ (super.noSuchMethod(
+ Invocation.method(#setCustomSignals, [customSignals]),
+ returnValue: _i5.Future.value(),
+ returnValueForMissingStub: _i5.Future.value(),
+ ) as _i5.Future);
+}
+
+/// A class which mocks [Stream].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockStream extends _i1.Mock implements _i5.Stream {
+ MockStream() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ bool get isBroadcast =>
+ (super.noSuchMethod(Invocation.getter(#isBroadcast), returnValue: false)
+ as bool);
+
+ @override
+ _i5.Future get length => (super.noSuchMethod(
+ Invocation.getter(#length),
+ returnValue: _i5.Future.value(0),
+ ) as _i5.Future);
+
+ @override
+ _i5.Future get isEmpty => (super.noSuchMethod(
+ Invocation.getter(#isEmpty),
+ returnValue: _i5.Future.value(false),
+ ) as _i5.Future);
+
+ @override
+ _i5.Future get first => (super.noSuchMethod(
+ Invocation.getter(#first),
+ returnValue: _i8.ifNotNull(
+ _i8.dummyValueOrNull(this, Invocation.getter(#first)),
+ (T v) => _i5.Future.value(v),
+ ) ??
+ _FakeFuture_9(this, Invocation.getter(#first)),
+ ) as _i5.Future);
+
+ @override
+ _i5.Future get last => (super.noSuchMethod(
+ Invocation.getter(#last),
+ returnValue: _i8.ifNotNull(
+ _i8.dummyValueOrNull(this, Invocation.getter(#last)),
+ (T v) => _i5.Future.value(v),
+ ) ??
+ _FakeFuture_9(this, Invocation.getter(#last)),
+ ) as _i5.Future);
+
+ @override
+ _i5.Future get single => (super.noSuchMethod(
+ Invocation.getter(#single),
+ returnValue: _i8.ifNotNull(
+ _i8.dummyValueOrNull(this, Invocation.getter(#single)),
+ (T v) => _i5.Future.value(v),
+ ) ??
+ _FakeFuture_9(this, Invocation.getter(#single)),
+ ) as _i5.Future);
+
+ @override
+ _i5.Stream asBroadcastStream({
+ void Function(_i5.StreamSubscription)? onListen,
+ void Function(_i5.StreamSubscription)? onCancel,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(#asBroadcastStream, [], {
+ #onListen: onListen,
+ #onCancel: onCancel,
+ }),
+ returnValue: _i5.Stream.empty(),
+ ) as _i5.Stream);
+
+ @override
+ _i5.StreamSubscription listen(
+ void Function(T)? onData, {
+ Function? onError,
+ void Function()? onDone,
+ bool? cancelOnError,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #listen,
+ [onData],
+ {
+ #onError: onError,
+ #onDone: onDone,
+ #cancelOnError: cancelOnError,
+ },
+ ),
+ returnValue: _FakeStreamSubscription_10(
+ this,
+ Invocation.method(
+ #listen,
+ [onData],
+ {
+ #onError: onError,
+ #onDone: onDone,
+ #cancelOnError: cancelOnError,
+ },
+ ),
+ ),
+ ) as _i5.StreamSubscription);
+
+ @override
+ _i5.Stream where(bool Function(T)? test) => (super.noSuchMethod(
+ Invocation.method(#where, [test]),
+ returnValue: _i5.Stream.empty(),
+ ) as _i5.Stream);
+
+ @override
+ _i5.Stream map(S Function(T)? convert) => (super.noSuchMethod(
+ Invocation.method(#map, [convert]),
+ returnValue: _i5.Stream.empty(),
+ ) as _i5.Stream);
+
+ @override
+ _i5.Stream asyncMap(_i5.FutureOr Function(T)? convert) =>
+ (super.noSuchMethod(
+ Invocation.method(#asyncMap, [convert]),
+ returnValue: _i5.Stream.empty(),
+ ) as _i5.Stream);
+
+ @override
+ _i5.Stream asyncExpand(_i5.Stream? Function(T)? convert) =>
+ (super.noSuchMethod(
+ Invocation.method(#asyncExpand, [convert]),
+ returnValue: _i5.Stream.empty(),
+ ) as _i5.Stream);
+
+ @override
+ _i5.Stream handleError(
+ Function? onError, {
+ bool Function(dynamic)? test,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(#handleError, [onError], {#test: test}),
+ returnValue: _i5.Stream.empty(),
+ ) as _i5.Stream);
+
+ @override
+ _i5.Stream expand(Iterable Function(T)? convert) =>
+ (super.noSuchMethod(
+ Invocation.method(#expand, [convert]),
+ returnValue: _i5.Stream.empty(),
+ ) as _i5.Stream);
+
+ @override
+ _i5.Future pipe(_i5.StreamConsumer? streamConsumer) =>
+ (super.noSuchMethod(
+ Invocation.method(#pipe, [streamConsumer]),
+ returnValue: _i5.Future.value(),
+ ) as _i5.Future);
+
+ @override
+ _i5.Stream transform(_i5.StreamTransformer? streamTransformer) =>
+ (super.noSuchMethod(
+ Invocation.method(#transform, [streamTransformer]),
+ returnValue: _i5.Stream.empty(),
+ ) as _i5.Stream);
+
+ @override
+ _i5.Future reduce(T Function(T, T)? combine) => (super.noSuchMethod(
+ Invocation.method(#reduce, [combine]),
+ returnValue: _i8.ifNotNull(
+ _i8.dummyValueOrNull(
+ this,
+ Invocation.method(#reduce, [combine]),
+ ),
+ (T v) => _i5.Future.value(v),
+ ) ??
+ _FakeFuture_9(this, Invocation.method(#reduce, [combine])),
+ ) as _i5.Future);
+
+ @override
+ _i5.Future fold(S? initialValue, S Function(S, T)? combine) =>
+ (super.noSuchMethod(
+ Invocation.method(#fold, [initialValue, combine]),
+ returnValue: _i8.ifNotNull(
+ _i8.dummyValueOrNull(
+ this,
+ Invocation.method(#fold, [initialValue, combine]),
+ ),
+ (S v) => _i5.Future.value(v),
+ ) ??
+ _FakeFuture_9(
+ this,
+ Invocation.method(#fold, [initialValue, combine]),
+ ),
+ ) as _i5.Future);
+
+ @override
+ _i5.Future join([String? separator = '']) => (super.noSuchMethod(
+ Invocation.method(#join, [separator]),
+ returnValue: _i5.Future.value(
+ _i8.dummyValue(
+ this,
+ Invocation.method(#join, [separator]),
+ ),
+ ),
+ ) as _i5.Future);
+
+ @override
+ _i5.Future contains(Object? needle) => (super.noSuchMethod(
+ Invocation.method(#contains, [needle]),
+ returnValue: _i5.Future.value(false),
+ ) as _i5.Future);
+
+ @override
+ _i5.Future forEach(void Function(T)? action) => (super.noSuchMethod(
+ Invocation.method(#forEach, [action]),
+ returnValue: _i5.Future.value(),
+ returnValueForMissingStub: _i5.Future.value(),
+ ) as _i5.Future);
+
+ @override
+ _i5.Future every(bool Function(T)? test) => (super.noSuchMethod(
+ Invocation.method(#every, [test]),
+ returnValue: _i5.Future.value(false),
+ ) as _i5.Future);
+
+ @override
+ _i5.Future any(bool Function(T)? test) => (super.noSuchMethod(
+ Invocation.method(#any, [test]),
+ returnValue: _i5.Future.value(false),
+ ) as _i5.Future);
+
+ @override
+ _i5.Stream cast() => (super.noSuchMethod(
+ Invocation.method(#cast, []),
+ returnValue: _i5.Stream.empty(),
+ ) as _i5.Stream);
+
+ @override
+ _i5.Future> toList() => (super.noSuchMethod(
+ Invocation.method(#toList, []),
+ returnValue: _i5.Future>.value([]),
+ ) as _i5.Future>);
+
+ @override
+ _i5.Future> toSet() => (super.noSuchMethod(
+ Invocation.method(#toSet, []),
+ returnValue: _i5.Future>.value({}),
+ ) as _i5.Future>);
+
+ @override
+ _i5.Future drain([E? futureValue]) => (super.noSuchMethod(
+ Invocation.method(#drain, [futureValue]),
+ returnValue: _i8.ifNotNull(
+ _i8.dummyValueOrNull(
+ this,
+ Invocation.method(#drain, [futureValue]),
+ ),
+ (E v) => _i5.Future.value(v),
+ ) ??
+ _FakeFuture_9(
+ this,
+ Invocation.method(#drain, [futureValue]),
+ ),
+ ) as _i5.Future);
+
+ @override
+ _i5.Stream take(int? count) => (super.noSuchMethod(
+ Invocation.method(#take, [count]),
+ returnValue: _i5.Stream.empty(),
+ ) as _i5.Stream);
+
+ @override
+ _i5.Stream takeWhile(bool Function(T)? test) => (super.noSuchMethod(
+ Invocation.method(#takeWhile, [test]),
+ returnValue: _i5.Stream.empty(),
+ ) as _i5.Stream);
+
+ @override
+ _i5.Stream skip(int? count) => (super.noSuchMethod(
+ Invocation.method(#skip, [count]),
+ returnValue: _i5.Stream.empty(),
+ ) as _i5.Stream);
+
+ @override
+ _i5.Stream skipWhile(bool Function(T)? test) => (super.noSuchMethod(
+ Invocation.method(#skipWhile, [test]),
+ returnValue: _i5.Stream.empty(),
+ ) as _i5.Stream);
+
+ @override
+ _i5.Stream distinct([bool Function(T, T)? equals]) => (super.noSuchMethod(
+ Invocation.method(#distinct, [equals]),
+ returnValue: _i5.Stream.empty(),
+ ) as _i5.Stream);
+
+ @override
+ _i5.Future firstWhere(bool Function(T)? test, {T Function()? orElse}) =>
+ (super.noSuchMethod(
+ Invocation.method(#firstWhere, [test], {#orElse: orElse}),
+ returnValue: _i8.ifNotNull(
+ _i8.dummyValueOrNull(
+ this,
+ Invocation.method(#firstWhere, [test], {#orElse: orElse}),
+ ),
+ (T v) => _i5.Future.value(v),
+ ) ??
+ _FakeFuture_9(
+ this,
+ Invocation.method(#firstWhere, [test], {#orElse: orElse}),
+ ),
+ ) as _i5.Future);
+
+ @override
+ _i5.Future lastWhere(bool Function(T)? test, {T Function()? orElse}) =>
+ (super.noSuchMethod(
+ Invocation.method(#lastWhere, [test], {#orElse: orElse}),
+ returnValue: _i8.ifNotNull(
+ _i8.dummyValueOrNull(
+ this,
+ Invocation.method(#lastWhere, [test], {#orElse: orElse}),
+ ),
+ (T v) => _i5.Future.value(v),
+ ) ??
+ _FakeFuture_9(
+ this,
+ Invocation.method(#lastWhere, [test], {#orElse: orElse}),
+ ),
+ ) as _i5.Future);
+
+ @override
+ _i5.Future singleWhere(bool Function(T)? test, {T Function()? orElse}) =>
+ (super.noSuchMethod(
+ Invocation.method(#singleWhere, [test], {#orElse: orElse}),
+ returnValue: _i8.ifNotNull(
+ _i8.dummyValueOrNull(
+ this,
+ Invocation.method(#singleWhere, [test], {#orElse: orElse}),
+ ),
+ (T v) => _i5.Future.value(v),
+ ) ??
+ _FakeFuture_9(
+ this,
+ Invocation.method(#singleWhere, [test], {#orElse: orElse}),
+ ),
+ ) as _i5.Future);
+
+ @override
+ _i5.Future elementAt(int? index) => (super.noSuchMethod(
+ Invocation.method(#elementAt, [index]),
+ returnValue: _i8.ifNotNull(
+ _i8.dummyValueOrNull(
+ this,
+ Invocation.method(#elementAt, [index]),
+ ),
+ (T v) => _i5.Future.value(v),
+ ) ??
+ _FakeFuture_9(this, Invocation.method(#elementAt, [index])),
+ ) as _i5.Future);
+
+ @override
+ _i5.Stream timeout(
+ Duration? timeLimit, {
+ void Function(_i5.EventSink)? onTimeout,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(#timeout, [timeLimit], {#onTimeout: onTimeout}),
+ returnValue: _i5.Stream.empty(),
+ ) as _i5.Stream);
+}
+
+/// A class which mocks [StreamSubscription].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockStreamSubscription extends _i1.Mock
+ implements _i5.StreamSubscription {
+ MockStreamSubscription() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ bool get isPaused =>
+ (super.noSuchMethod(Invocation.getter(#isPaused), returnValue: false)
+ as bool);
+
+ @override
+ _i5.Future cancel() => (super.noSuchMethod(
+ Invocation.method(#cancel, []),
+ returnValue: _i5.Future.value(),
+ returnValueForMissingStub: _i5.Future.value(),
+ ) as _i5.Future);
+
+ @override
+ void onData(void Function(T)? handleData) => super.noSuchMethod(
+ Invocation.method(#onData, [handleData]),
+ returnValueForMissingStub: null,
+ );
+
+ @override
+ void onError(Function? handleError) => super.noSuchMethod(
+ Invocation.method(#onError, [handleError]),
+ returnValueForMissingStub: null,
+ );
+
+ @override
+ void onDone(void Function()? handleDone) => super.noSuchMethod(
+ Invocation.method(#onDone, [handleDone]),
+ returnValueForMissingStub: null,
+ );
+
+ @override
+ void pause([_i5.Future? resumeSignal]) => super.noSuchMethod(
+ Invocation.method(#pause, [resumeSignal]),
+ returnValueForMissingStub: null,
+ );
+
+ @override
+ void resume() => super.noSuchMethod(
+ Invocation.method(#resume, []),
+ returnValueForMissingStub: null,
+ );
+
+ @override
+ _i5.Future asFuture([E? futureValue]) => (super.noSuchMethod(
+ Invocation.method(#asFuture, [futureValue]),
+ returnValue: _i8.ifNotNull(
+ _i8.dummyValueOrNull(
+ this,
+ Invocation.method(#asFuture, [futureValue]),
+ ),
+ (E v) => _i5.Future.value(v),
+ ) ??
+ _FakeFuture_9(
+ this,
+ Invocation.method(#asFuture, [futureValue]),
+ ),
+ ) as _i5.Future);
+}
diff --git a/firebase_remote_config/test/src/sentry_firebase_remote_config_integration_test.dart b/firebase_remote_config/test/src/sentry_firebase_remote_config_integration_test.dart
new file mode 100644
index 0000000000..8c8eae56dd
--- /dev/null
+++ b/firebase_remote_config/test/src/sentry_firebase_remote_config_integration_test.dart
@@ -0,0 +1,156 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:sentry/sentry.dart';
+
+import 'package:mockito/mockito.dart';
+import '../mocks/mocks.mocks.dart';
+
+import 'package:firebase_remote_config/firebase_remote_config.dart';
+import 'package:sentry_firebase_remote_config/sentry_firebase_remote_config.dart';
+
+void main() {
+ late Fixture fixture;
+
+ givenRemoveConfigUpdate() {
+ final update = RemoteConfigUpdate({'test', 'foo'});
+ when(fixture.mockFirebaseRemoteConfig.onConfigUpdated)
+ .thenAnswer((_) => Stream.value(update));
+
+ when(fixture.mockFirebaseRemoteConfig.getString('test')).thenReturn('true');
+ when(fixture.mockFirebaseRemoteConfig.getString('foo')).thenReturn('bar');
+ when(fixture.mockFirebaseRemoteConfig.activate())
+ .thenAnswer((_) => Future.value(true));
+ }
+
+ 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 integration to options', () async {
+ givenRemoveConfigUpdate();
+
+ final sut = await fixture.getSut();
+
+ sut.call(fixture.hub, fixture.options);
+
+ expect(
+ fixture.options.sdk.integrations
+ .contains('SentryFirebaseRemoteConfigIntegration'),
+ isTrue,
+ );
+ });
+
+ test('adds boolean update to feature flags', () async {
+ givenRemoveConfigUpdate();
+
+ final sut = await fixture.getSut();
+ sut.call(fixture.hub, fixture.options);
+ await Future.delayed(
+ const Duration(
+ milliseconds: 100,
+ ),
+ ); // wait for the subscription to be called
+
+ // ignore: invalid_use_of_internal_member
+ final featureFlags = fixture.hub.scope.contexts[SentryFeatureFlags.type]
+ as SentryFeatureFlags?;
+
+ expect(featureFlags, isNotNull);
+ expect(featureFlags?.values.length, 1);
+ expect(featureFlags?.values.first.name, 'test');
+ expect(featureFlags?.values.first.value, true);
+ });
+
+ test('stream canceld on close', () async {
+ final streamSubscription = MockStreamSubscription();
+ when(streamSubscription.cancel()).thenAnswer((_) => Future.value());
+
+ final stream = MockStream();
+ when(stream.listen(any)).thenAnswer((_) => streamSubscription);
+
+ when(fixture.mockFirebaseRemoteConfig.onConfigUpdated)
+ .thenAnswer((_) => stream);
+
+ final sut = await fixture.getSut();
+ await sut.call(fixture.hub, fixture.options);
+ await sut.close();
+
+ verify(streamSubscription.cancel()).called(1);
+ });
+
+ test('activate called by default', () async {
+ givenRemoveConfigUpdate();
+
+ final sut = await fixture.getSut();
+ sut.call(fixture.hub, fixture.options);
+ await Future.delayed(
+ const Duration(
+ milliseconds: 100,
+ ),
+ );
+
+ verify(fixture.mockFirebaseRemoteConfig.activate()).called(1);
+ });
+
+ test('activate not called if activateOnConfigUpdated is false', () async {
+ givenRemoveConfigUpdate();
+
+ final sut = await fixture.getSut(activateOnConfigUpdated: false);
+ sut.call(fixture.hub, fixture.options);
+ await Future.delayed(
+ const Duration(
+ milliseconds: 100,
+ ),
+ );
+
+ verifyNever(fixture.mockFirebaseRemoteConfig.activate());
+ });
+
+ test('activate called if activateOnConfigUpdated is true', () async {
+ givenRemoveConfigUpdate();
+
+ final sut = await fixture.getSut(activateOnConfigUpdated: true);
+ sut.call(fixture.hub, fixture.options);
+ await Future.delayed(
+ const Duration(
+ milliseconds: 100,
+ ),
+ );
+
+ verify(fixture.mockFirebaseRemoteConfig.activate()).called(1);
+ });
+}
+
+class Fixture {
+ late Hub hub;
+ late SentryOptions options;
+
+ final mockFirebaseRemoteConfig = MockFirebaseRemoteConfig();
+
+ Future getSut({
+ bool? activateOnConfigUpdated,
+ }) async {
+ if (activateOnConfigUpdated == null) {
+ return SentryFirebaseRemoteConfigIntegration(
+ firebaseRemoteConfig: mockFirebaseRemoteConfig,
+ );
+ } else {
+ return SentryFirebaseRemoteConfigIntegration(
+ firebaseRemoteConfig: mockFirebaseRemoteConfig,
+ activateOnConfigUpdated: activateOnConfigUpdated,
+ );
+ }
+ }
+}
diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh
index 8920aa395f..4b495014ed 100755
--- a/scripts/bump-version.sh
+++ b/scripts/bump-version.sh
@@ -10,7 +10,7 @@ NEW_VERSION="${2}"
echo "Current version: ${OLD_VERSION}"
echo "Bumping version: ${NEW_VERSION}"
-for pkg in {dart,flutter,logging,dio,file,sqflite,drift,hive,isar,link}; do
+for pkg in {dart,flutter,logging,dio,file,sqflite,drift,hive,isar,link,firebase_remote_config}; do
# Bump version in pubspec.yaml
perl -pi -e "s/^version: .*/version: $NEW_VERSION/" $pkg/pubspec.yaml
# Bump sentry dependency version in pubspec.yaml