diff --git a/CHANGELOG.md b/CHANGELOG.md index ed5af73942..2812a6c59b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * Fix: Do not append stack trace to the exception if there are no frames * Fix: Empty DSN disables the SDK and runs the App * Refactoring: Migrate Sentry Dart to null safety +* Fix: Null safety on sentry_flutter (#337) # 4.0.6 diff --git a/flutter/lib/src/default_integrations.dart b/flutter/lib/src/default_integrations.dart index 20ab9aa927..b63a4f1c1c 100644 --- a/flutter/lib/src/default_integrations.dart +++ b/flutter/lib/src/default_integrations.dart @@ -13,7 +13,7 @@ import 'widgets_binding_observer.dart'; class WidgetsFlutterBindingIntegration extends Integration { WidgetsFlutterBindingIntegration( - [WidgetsBinding Function() ensureInitialized]) + [WidgetsBinding Function()? ensureInitialized]) : _ensureInitialized = ensureInitialized ?? WidgetsFlutterBinding.ensureInitialized; @@ -104,7 +104,7 @@ class LoadContextsIntegration extends Integration { (event, {hint}) async { try { final infos = Map.from( - await _channel.invokeMethod('loadContexts'), + await (_channel.invokeMethod('loadContexts')), ); if (infos['contexts'] != null) { final contexts = Contexts.fromJson( @@ -137,7 +137,7 @@ class LoadContextsIntegration extends Integration { if (infos['package'] != null) { final package = Map.from(infos['package'] as Map); final sdk = event.sdk ?? options.sdk; - sdk.addPackage(package['sdk_name'], package['version']); + sdk.addPackage(package['sdk_name']!, package['version']!); event = event.copyWith(sdk: sdk); } } catch (error) { @@ -201,7 +201,7 @@ class NativeSdkIntegration extends Integration { /// - [SentryWidgetsBindingObserver] /// - [WidgetsBindingObserver](https://api.flutter.dev/flutter/widgets/WidgetsBindingObserver-class.html) class WidgetsBindingIntegration extends Integration { - SentryWidgetsBindingObserver _observer; + SentryWidgetsBindingObserver? _observer; @override FutureOr call(Hub hub, SentryFlutterOptions options) { @@ -215,7 +215,7 @@ class WidgetsBindingIntegration extends Integration { // If the instance is not created, we skip it to keep going. final instance = WidgetsBinding.instance; if (instance != null) { - instance.addObserver(_observer); + instance.addObserver(_observer!); options.sdk.addIntegration('widgetsBindingIntegration'); } else { options.logger( @@ -228,8 +228,8 @@ class WidgetsBindingIntegration extends Integration { @override void close() { final instance = WidgetsBinding.instance; - if (instance != null) { - instance.removeObserver(_observer); + if (instance != null && _observer != null) { + instance.removeObserver(_observer!); } } } @@ -246,10 +246,8 @@ class LoadAndroidImageListIntegration options.addEventProcessor( (event, {hint}) async { try { - if (event.exception != null && - event.exception.stackTrace != null && - event.exception.stackTrace.frames != null) { - final needsSymbolication = event.exception.stackTrace.frames + if (event.exception != null && event.exception!.stackTrace != null) { + final needsSymbolication = event.exception!.stackTrace!.frames .any((element) => 'native' == element.platform); // if there are no frames that require symbolication, we don't @@ -264,7 +262,7 @@ class LoadAndroidImageListIntegration // we call on every event because the loaded image list is cached // and it could be changed on the Native side. final imageList = List>.from( - await _channel.invokeMethod('loadImageList'), + await (_channel.invokeMethod('loadImageList')), ); if (imageList.isEmpty) { @@ -274,13 +272,13 @@ class LoadAndroidImageListIntegration final newDebugImages = []; for (final item in imageList) { - final codeFile = item['code_file'] as String; - final codeId = item['code_id'] as String; - final imageAddr = item['image_addr'] as String; - final imageSize = item['image_size'] as int; + final codeFile = item['code_file'] as String?; + final codeId = item['code_id'] as String?; + final imageAddr = item['image_addr'] as String?; + final imageSize = item['image_size'] as int?; final type = item['type'] as String; - final debugId = item['debug_id'] as String; - final debugFile = item['debug_file'] as String; + final debugId = item['debug_id'] as String?; + final debugFile = item['debug_file'] as String?; final image = DebugImage( type: type, @@ -326,10 +324,6 @@ class LoadReleaseIntegration extends Integration { FutureOr call(Hub hub, SentryFlutterOptions options) async { try { if (!kIsWeb) { - if (_packageLoader == null) { - options.logger(SentryLevel.debug, 'Package loader is null.'); - return; - } final packageInfo = await _packageLoader(); final release = '${packageInfo.packageName}@${packageInfo.version}+${packageInfo.buildNumber}'; diff --git a/flutter/lib/src/navigation/sentry_navigator_observer.dart b/flutter/lib/src/navigation/sentry_navigator_observer.dart index c249b9b2c0..917bc67c08 100644 --- a/flutter/lib/src/navigation/sentry_navigator_observer.dart +++ b/flutter/lib/src/navigation/sentry_navigator_observer.dart @@ -35,26 +35,22 @@ const _navigationKey = 'navigation'; /// - [RouteObserver](https://api.flutter.dev/flutter/widgets/RouteObserver-class.html) /// - [Navigating with arguments](https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments) class SentryNavigatorObserver extends RouteObserver> { - factory SentryNavigatorObserver({Hub hub}) { - return SentryNavigatorObserver._(hub ?? HubAdapter()); - } - - SentryNavigatorObserver._(this.hub) : assert(hub != null); + SentryNavigatorObserver({Hub? hub}) : hub = hub ?? HubAdapter(); final Hub hub; @override - void didPush(Route route, Route previousRoute) { + void didPush(Route route, Route? previousRoute) { super.didPush(route, previousRoute); _addBreadcrumb( type: 'didPush', from: previousRoute?.settings, - to: route?.settings, + to: route.settings, ); } @override - void didReplace({Route newRoute, Route oldRoute}) { + void didReplace({Route? newRoute, Route? oldRoute}) { super.didReplace(newRoute: newRoute, oldRoute: oldRoute); _addBreadcrumb( @@ -65,20 +61,20 @@ class SentryNavigatorObserver extends RouteObserver> { } @override - void didPop(Route route, Route previousRoute) { + void didPop(Route route, Route? previousRoute) { super.didPop(route, previousRoute); _addBreadcrumb( type: 'didPop', - from: route?.settings, + from: route.settings, to: previousRoute?.settings, ); } void _addBreadcrumb({ - String type, - RouteSettings from, - RouteSettings to, + required String type, + RouteSettings? from, + RouteSettings? to, }) { hub.addBreadcrumb(RouteObserverBreadcrumb( navigationType: type, @@ -98,10 +94,10 @@ class RouteObserverBreadcrumb extends Breadcrumb { factory RouteObserverBreadcrumb({ /// This should correspond to Flutters navigation events. /// See https://api.flutter.dev/flutter/widgets/RouteObserver-class.html - @required String navigationType, - RouteSettings from, - RouteSettings to, - SentryLevel level, + required String navigationType, + RouteSettings? from, + RouteSettings? to, + SentryLevel? level, }) { final dynamic fromArgs = _formatArgs(from?.arguments); final dynamic toArgs = _formatArgs(to?.arguments); @@ -116,26 +112,25 @@ class RouteObserverBreadcrumb extends Breadcrumb { } RouteObserverBreadcrumb._({ - @required String navigationType, - String from, + required String navigationType, + String? from, dynamic fromArgs, - String to, + String? to, dynamic toArgs, - SentryLevel level, - }) : assert(navigationType != null), - super( + SentryLevel? level, + }) : super( category: _navigationKey, type: _navigationKey, level: level, data: { - if (navigationType != null) 'state': navigationType, + 'state': navigationType, if (from != null) 'from': from, if (fromArgs != null) 'from_arguments': fromArgs, if (to != null) 'to': to, if (toArgs != null) 'to_arguments': toArgs, }); - static dynamic _formatArgs(Object args) { + static dynamic _formatArgs(Object? args) { if (args == null) { return null; } diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index d5969bd047..f23746f355 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -25,16 +25,12 @@ mixin SentryFlutter { static Future init( FlutterOptionsConfiguration optionsConfiguration, { - AppRunner appRunner, + AppRunner? appRunner, PackageLoader packageLoader = _loadPackageInfo, iOSPlatformChecker isIOSChecker = isIOS, AndroidPlatformChecker isAndroidChecker = isAndroid, MethodChannel channel = _channel, }) async { - if (optionsConfiguration == null) { - throw ArgumentError('OptionsConfiguration is required.'); - } - final flutterOptions = SentryFlutterOptions(); // first step is to install the native integration and set default values, @@ -53,7 +49,7 @@ mixin SentryFlutter { await Sentry.init( (options) async { - await optionsConfiguration(options); + await optionsConfiguration(options as SentryFlutterOptions); }, appRunner: appRunner, options: flutterOptions, diff --git a/flutter/lib/src/sentry_flutter_options.dart b/flutter/lib/src/sentry_flutter_options.dart index 9072c0a056..f5b7e1836c 100644 --- a/flutter/lib/src/sentry_flutter_options.dart +++ b/flutter/lib/src/sentry_flutter_options.dart @@ -5,19 +5,10 @@ import 'package:sentry/sentry.dart'; /// Note that some of these options require native Sentry integration, which is /// not available on all platforms. class SentryFlutterOptions extends SentryOptions { - SentryFlutterOptions({String dsn}) : super(dsn: dsn); - - bool _enableAutoSessionTracking = true; + SentryFlutterOptions({String? dsn}) : super(dsn: dsn); /// Enable or disable the Auto session tracking on the Native SDKs (Android/iOS) - bool get enableAutoSessionTracking => _enableAutoSessionTracking; - - set enableAutoSessionTracking(bool value) { - assert(value != null); - _enableAutoSessionTracking = value ?? _enableAutoSessionTracking; - } - - bool _enableNativeCrashHandling = true; + bool enableAutoSessionTracking = true; /// Enable or disable the Crash handling on the Native SDKs, e.g., /// UncaughtExceptionHandler and [anrEnabled] for Android. @@ -28,12 +19,7 @@ class SentryFlutterOptions extends SentryOptions { /// /// Disabling this feature affects the [enableAutoSessionTracking] /// feature, as this is required to mark Sessions as Crashed. - bool get enableNativeCrashHandling => _enableNativeCrashHandling; - - set enableNativeCrashHandling(bool value) { - assert(value != null); - _enableNativeCrashHandling = value ?? _enableNativeCrashHandling; - } + bool enableNativeCrashHandling = true; int _autoSessionTrackingIntervalMillis = 30000; @@ -44,25 +30,16 @@ class SentryFlutterOptions extends SentryOptions { _autoSessionTrackingIntervalMillis; set autoSessionTrackingIntervalMillis(int value) { - assert(value != null); - _autoSessionTrackingIntervalMillis = (value != null && value >= 0) - ? value - : _autoSessionTrackingIntervalMillis; + _autoSessionTrackingIntervalMillis = + value >= 0 ? value : _autoSessionTrackingIntervalMillis; } - bool _anrEnabled = false; - /// Enable or disable ANR (Application Not Responding) Default is enabled Used by AnrIntegration. /// Available only for Android. /// Disabled by default as the stack trace most of the time is hanging on /// the MessageChannel from Flutter, but you can enable it if you have /// Java/Kotlin code as well. - bool get anrEnabled => _anrEnabled; - - set anrEnabled(bool value) { - assert(value != null); - _anrEnabled = value ?? _anrEnabled; - } + bool anrEnabled = false; int _anrTimeoutIntervalMillis = 5000; @@ -72,24 +49,15 @@ class SentryFlutterOptions extends SentryOptions { int get anrTimeoutIntervalMillis => _anrTimeoutIntervalMillis; set anrTimeoutIntervalMillis(int value) { - assert(value != null); - _anrTimeoutIntervalMillis = - (value != null && value >= 0) ? value : _anrTimeoutIntervalMillis; + _anrTimeoutIntervalMillis = value >= 0 ? value : _anrTimeoutIntervalMillis; } - bool _enableAutoNativeBreadcrumbs = true; - /// Enable or disable the Automatic breadcrumbs on the Native platforms (Android/iOS) /// Screen's lifecycle, App's lifecycle, System events, etc... /// /// If you only want to record breadcrumbs inside the Flutter environment /// consider using [useFlutterBreadcrumbTracking]. - bool get enableAutoNativeBreadcrumbs => _enableAutoNativeBreadcrumbs; - - set enableAutoNativeBreadcrumbs(bool value) { - assert(value != null); - _enableAutoNativeBreadcrumbs = value ?? _enableAutoNativeBreadcrumbs; - } + bool enableAutoNativeBreadcrumbs = true; int _cacheDirSize = 30; @@ -98,15 +66,14 @@ class SentryFlutterOptions extends SentryOptions { int get cacheDirSize => _cacheDirSize; set cacheDirSize(int value) { - assert(value != null); - _cacheDirSize = (value != null && value >= 0) ? value : _cacheDirSize; + _cacheDirSize = value >= 0 ? value : _cacheDirSize; } @Deprecated( 'Use enableAppLifecycleBreadcrumbs instead. ' 'This option gets removed in Sentry 5.0.0', ) - bool get enableLifecycleBreadcrumbs => _enableAppLifecycleBreadcrumbs; + bool get enableLifecycleBreadcrumbs => enableAppLifecycleBreadcrumbs; @Deprecated( 'Use enableAppLifecycleBreadcrumbs instead. ' @@ -131,83 +98,36 @@ class SentryFlutterOptions extends SentryOptions { /// [lifecycle](https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle). /// However because an iOS Flutter application lives inside a single /// `UIViewController` this is an application wide lifecycle event. - bool get enableAppLifecycleBreadcrumbs => _enableAppLifecycleBreadcrumbs; - - set enableAppLifecycleBreadcrumbs(bool value) { - assert(value != null); - _enableAppLifecycleBreadcrumbs = value ?? _enableAppLifecycleBreadcrumbs; - } - - bool _enableAppLifecycleBreadcrumbs = false; + bool enableAppLifecycleBreadcrumbs = false; /// Consider disabling [enableAutoNativeBreadcrumbs] if you /// enable this. Otherwise you might record window metric events twice. /// Also consider using [enableBreadcrumbTrackingForCurrentPlatform] /// instead for more sensible defaults. - bool get enableWindowMetricBreadcrumbs => _enableWindowMetricBreadcrumbs; - - set enableWindowMetricBreadcrumbs(bool value) { - assert(value != null); - _enableWindowMetricBreadcrumbs = value ?? _enableWindowMetricBreadcrumbs; - } - - bool _enableWindowMetricBreadcrumbs = false; + bool enableWindowMetricBreadcrumbs = false; /// Consider disabling [enableAutoNativeBreadcrumbs] if you /// enable this. Otherwise you might record brightness change events twice. /// Also consider using [enableBreadcrumbTrackingForCurrentPlatform] /// instead for more sensible defaults. - bool get enableBrightnessChangeBreadcrumbs => - _enableBrightnessChangeBreadcrumbs; - - set enableBrightnessChangeBreadcrumbs(bool value) { - assert(value != null); - _enableBrightnessChangeBreadcrumbs = - value ?? _enableBrightnessChangeBreadcrumbs; - } - - bool _enableBrightnessChangeBreadcrumbs = false; + bool enableBrightnessChangeBreadcrumbs = false; /// Consider disabling [enableAutoNativeBreadcrumbs] if you /// enable this. Otherwise you might record text scale change events twice. /// Also consider using [enableBreadcrumbTrackingForCurrentPlatform] /// instead for more sensible defaults. - bool get enableTextScaleChangeBreadcrumbs => - _enableTextScaleChangeBreadcrumbs; - - set enableTextScaleChangeBreadcrumbs(bool value) { - assert(value != null); - _enableTextScaleChangeBreadcrumbs = - value ?? _enableTextScaleChangeBreadcrumbs; - } - - bool _enableTextScaleChangeBreadcrumbs = false; + bool enableTextScaleChangeBreadcrumbs = false; /// Consider disabling [enableAutoNativeBreadcrumbs] if you /// enable this. Otherwise you might record memory pressure events twice. /// Also consider using [enableBreadcrumbTrackingForCurrentPlatform] /// instead for more sensible defaults. - bool get enableMemoryPressureBreadcrumbs => _enableMemoryPressureBreadcrumbs; - - set enableMemoryPressureBreadcrumbs(bool value) { - assert(value != null); - _enableMemoryPressureBreadcrumbs = - value ?? _enableMemoryPressureBreadcrumbs; - } - - bool _enableMemoryPressureBreadcrumbs = false; + bool enableMemoryPressureBreadcrumbs = false; /// By default, we don't report [FlutterErrorDetails.silent] errors, /// but you can by enabling this flag. /// See https://api.flutter.dev/flutter/foundation/FlutterErrorDetails/silent.html - bool get reportSilentFlutterErrors => _reportSilentFlutterErrors; - - set reportSilentFlutterErrors(bool value) { - assert(value != null); - _reportSilentFlutterErrors = value ?? _reportSilentFlutterErrors; - } - - bool _reportSilentFlutterErrors = false; + bool reportSilentFlutterErrors = false; /// By using this, you are disabling native [Breadcrumb] tracking and instead /// you are just tracking [Breadcrumb]s which result from events available @@ -263,8 +183,6 @@ class SentryFlutterOptions extends SentryOptions { @foundation.visibleForTesting void configureBreadcrumbTrackingForPlatform( foundation.TargetPlatform platform) { - assert(platform != null); - // Bacause platform reports the Operating System and not if it is running // in a browser. So we have to check if this is Flutter for web. // See https://github.com/flutter/flutter/blob/c5a69b9b8ad186e9fce017fd4bfb8ce63f9f4d13/packages/flutter/lib/src/foundation/_platform_web.dart diff --git a/flutter/lib/src/widgets_binding_observer.dart b/flutter/lib/src/widgets_binding_observer.dart index 72916b1d23..6a591bce9f 100644 --- a/flutter/lib/src/widgets_binding_observer.dart +++ b/flutter/lib/src/widgets_binding_observer.dart @@ -19,16 +19,13 @@ import '../sentry_flutter.dart'; /// - [WidgetsBindingObserver](https://api.flutter.dev/flutter/widgets/WidgetsBindingObserver-class.html) class SentryWidgetsBindingObserver with WidgetsBindingObserver { SentryWidgetsBindingObserver({ - Hub hub, - @required SentryFlutterOptions options, - }) { - _hub = hub ?? HubAdapter(); - assert(options != null); - _options = options; - } + Hub? hub, + required SentryFlutterOptions options, + }) : _hub = hub ?? HubAdapter(), + _options = options; - Hub _hub; - SentryFlutterOptions _options; + final Hub _hub; + final SentryFlutterOptions _options; /// This method records lifecycle events. /// It tries to mimic the behavior of ActivityBreadcrumbsIntegration of Sentry @@ -66,15 +63,15 @@ class SentryWidgetsBindingObserver with WidgetsBindingObserver { if (!_options.enableWindowMetricBreadcrumbs) { return; } - final window = WidgetsBinding.instance.window; + final window = WidgetsBinding.instance?.window; _hub.addBreadcrumb(Breadcrumb( message: 'Screen size changed', category: 'device.screen', type: 'navigation', data: { - 'new_pixel_ratio': window.devicePixelRatio, - 'new_height': window.physicalSize.height, - 'new_width': window.physicalSize.width, + 'new_pixel_ratio': window?.devicePixelRatio, + 'new_height': window?.physicalSize.height, + 'new_width': window?.physicalSize.width, }, )); } @@ -86,7 +83,7 @@ class SentryWidgetsBindingObserver with WidgetsBindingObserver { if (!_options.enableBrightnessChangeBreadcrumbs) { return; } - final brightness = WidgetsBinding.instance.window.platformBrightness; + final brightness = WidgetsBinding.instance?.window.platformBrightness; final brightnessDescription = brightness == Brightness.dark ? 'dark' : 'light'; @@ -107,7 +104,7 @@ class SentryWidgetsBindingObserver with WidgetsBindingObserver { if (!_options.enableTextScaleChangeBreadcrumbs) { return; } - final newTextScaleFactor = WidgetsBinding.instance.window.textScaleFactor; + final newTextScaleFactor = WidgetsBinding.instance?.window.textScaleFactor; _hub.addBreadcrumb(Breadcrumb( message: 'Text scale factor changed to $newTextScaleFactor.', type: 'system', @@ -155,10 +152,9 @@ class SentryWidgetsBindingObserver with WidgetsBindingObserver { case AppLifecycleState.detached: return 'detached'; } - return ''; } - /* +/* These are also methods of `WidgetsBindingObserver` but are currently not implemented because I'm not sure what to do with them. See the reasoning for each method. If these methods are implemented the class definition should diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 13bfc7db72..4f902dc6f3 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://docs.sentry.io/platforms/flutter/ repository: https://github.com/getsentry/sentry-dart environment: - sdk: ">=2.8.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' flutter: ">=1.17.0" dependencies: @@ -20,9 +20,10 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - mockito: ^5.0.0-nullsafety.7 + mockito: ^5.0.0 yaml: ^3.0.0 # needed for version match (code and pubspec) pedantic: ^1.10.0 + build_runner: ^1.11.5 dependency_overrides: sentry: diff --git a/flutter/test/default_integrations_test.dart b/flutter/test/default_integrations_test.dart index 9518f06366..44c2468586 100644 --- a/flutter/test/default_integrations_test.dart +++ b/flutter/test/default_integrations_test.dart @@ -7,14 +7,14 @@ import 'package:sentry/sentry.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter/src/sentry_flutter_options.dart'; -import 'mocks.dart'; +import 'mocks.mocks.dart'; void main() { const _channel = MethodChannel('sentry_flutter'); TestWidgetsFlutterBinding.ensureInitialized(); - Fixture fixture; + late Fixture fixture; setUp(() { fixture = Fixture(); @@ -26,13 +26,16 @@ void main() { void _reportError({ bool silent = false, - FlutterExceptionHandler handler, + FlutterExceptionHandler? handler, dynamic exception, }) { // replace default error otherwise it fails on testing FlutterError.onError = handler ?? (FlutterErrorDetails errorDetails) async {}; + when(fixture.hub.captureEvent(captureAny)) + .thenAnswer((_) => Future.value(SentryId.empty())); + FlutterErrorIntegration()(fixture.hub, fixture.options); final throwable = exception ?? StateError('error'); @@ -106,7 +109,7 @@ void main() { test('nativeSdkIntegration do not throw', () async { _channel.setMockMethodCallHandler((MethodCall methodCall) async { - throw null; + throw Exception(); }); final integration = NativeSdkIntegration(_channel); @@ -142,7 +145,7 @@ void main() { var called = false; var ensureInitialized = () { called = true; - return WidgetsBinding.instance; + return WidgetsBinding.instance!; }; final integration = WidgetsFlutterBindingIntegration(ensureInitialized); await integration(fixture.hub, fixture.options); diff --git a/flutter/test/file_system_transport_test.dart b/flutter/test/file_system_transport_test.dart index 0f1e7b8f1d..08a5f29957 100644 --- a/flutter/test/file_system_transport_test.dart +++ b/flutter/test/file_system_transport_test.dart @@ -10,7 +10,7 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); - Fixture fixture; + late Fixture fixture; setUp(() { fixture = Fixture(); @@ -33,7 +33,7 @@ void main() { test('FileSystemTransport returns emptyId if channel throws', () async { _channel.setMockMethodCallHandler((MethodCall methodCall) async { - throw null; + throw Exception(); }); final transport = fixture.getSut(_channel); diff --git a/flutter/test/load_android_image_list_test.dart b/flutter/test/load_android_image_list_test.dart index 918ce6e798..36cf5c8292 100644 --- a/flutter/test/load_android_image_list_test.dart +++ b/flutter/test/load_android_image_list_test.dart @@ -88,10 +88,10 @@ void main() { LoadAndroidImageListIntegration(_channel)(hub, options); final ep = options.eventProcessors.first; - var event = getEvent(); + SentryEvent? event = getEvent(); event = await ep(event); - expect(1, event.debugMeta.images.length); + expect(1, event!.debugMeta!.images.length); }); test('Event processor asserts image list', () async { @@ -100,10 +100,10 @@ void main() { LoadAndroidImageListIntegration(_channel)(hub, options); final ep = options.eventProcessors.first; - var event = getEvent(); + SentryEvent? event = getEvent(); event = await ep(event); - final image = event.debugMeta.images.first; + final image = event!.debugMeta!.images.first; expect('/apex/com.android.art/javalib/arm64/boot.oat', image.codeFile); expect('13577ce71153c228ecf0eb73fc39f45010d487f8', image.codeId); diff --git a/flutter/test/load_contexts_integrations_test.dart b/flutter/test/load_contexts_integrations_test.dart index fc60a89d72..64b9e646db 100644 --- a/flutter/test/load_contexts_integrations_test.dart +++ b/flutter/test/load_contexts_integrations_test.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; @@ -44,22 +46,23 @@ void main() { expect(options.eventProcessors.length, 1); final e = SentryEvent(); - final event = await options.eventProcessors.first(e); + final event = + await (options.eventProcessors.first(e) as FutureOr); expect(called, true); - expect(event.contexts.device.name, 'Device1'); - expect(event.contexts.app.name, 'test-app'); - expect(event.contexts.operatingSystem.name, 'os1'); - expect(event.contexts.gpu.name, 'gpu1'); - expect(event.contexts.browser.name, 'browser1'); + expect(event.contexts.device!.name, 'Device1'); + expect(event.contexts.app!.name, 'test-app'); + expect(event.contexts.operatingSystem!.name, 'os1'); + expect(event.contexts.gpu!.name, 'gpu1'); + expect(event.contexts.browser!.name, 'browser1'); expect( event.contexts.runtimes.any((element) => element.name == 'RT1'), true); expect(event.contexts['theme'], 'material'); expect( - event.sdk.packages.any((element) => element.name == 'native-package'), + event.sdk!.packages.any((element) => element.name == 'native-package'), true, ); - expect(event.sdk.integrations.contains('NativeIntegration'), true); + expect(event.sdk!.integrations.contains('NativeIntegration'), true); }); test( @@ -81,14 +84,15 @@ void main() { runtimes: [const SentryRuntime(name: 'eRT')]) ..['theme'] = 'cuppertino'; final e = SentryEvent(contexts: eventContexts); - final event = await options.eventProcessors.first(e); + final event = + await (options.eventProcessors.first(e) as FutureOr); expect(called, true); - expect(event.contexts.device.name, 'eDevice'); - expect(event.contexts.app.name, 'eApp'); - expect(event.contexts.operatingSystem.name, 'eOS'); - expect(event.contexts.gpu.name, 'eGpu'); - expect(event.contexts.browser.name, 'eBrowser'); + expect(event.contexts.device!.name, 'eDevice'); + expect(event.contexts.app!.name, 'eApp'); + expect(event.contexts.operatingSystem!.name, 'eOS'); + expect(event.contexts.gpu!.name, 'eGpu'); + expect(event.contexts.browser!.name, 'eBrowser'); expect( event.contexts.runtimes.any((element) => element.name == 'RT1'), true); expect( @@ -111,24 +115,25 @@ void main() { packages: const [SentryPackage('event-package', '2.0')], ); final e = SentryEvent(sdk: eventSdk); - final event = await options.eventProcessors.first(e); + final event = + await (options.eventProcessors.first(e) as FutureOr); expect( - event.sdk.packages.any((element) => element.name == 'native-package'), + event.sdk!.packages.any((element) => element.name == 'native-package'), true, ); expect( - event.sdk.packages.any((element) => element.name == 'event-package'), + event.sdk!.packages.any((element) => element.name == 'event-package'), true, ); - expect(event.sdk.integrations.contains('NativeIntegration'), true); - expect(event.sdk.integrations.contains('EventIntegration'), true); + expect(event.sdk!.integrations.contains('NativeIntegration'), true); + expect(event.sdk!.integrations.contains('EventIntegration'), true); }, ); test('should not throw on loadContextsIntegration exception', () async { _channel.setMockMethodCallHandler((MethodCall methodCall) async { - throw null; + throw Exception(); }); final options = SentryFlutterOptions()..dsn = fakeDsn; final hub = Hub(options); diff --git a/flutter/test/mocks.dart b/flutter/test/mocks.dart index 52e59d29ff..787cc98bd5 100644 --- a/flutter/test/mocks.dart +++ b/flutter/test/mocks.dart @@ -1,8 +1,7 @@ -import 'package:mockito/mockito.dart'; +import 'package:mockito/annotations.dart'; import 'package:sentry/sentry.dart'; -class MockHub extends Mock implements Hub {} - -class MockTransport extends Mock implements Transport {} - const fakeDsn = 'https://abc@def.ingest.sentry.io/1234567'; + +@GenerateMocks([Hub, Transport]) +void main() {} diff --git a/flutter/test/mocks.mocks.dart b/flutter/test/mocks.mocks.dart new file mode 100644 index 0000000000..d81a53242f --- /dev/null +++ b/flutter/test/mocks.mocks.dart @@ -0,0 +1,101 @@ +// Mocks generated by Mockito 5.0.0-nullsafety.7 from annotations +// in sentry_flutter/test/mocks.dart. +// Do not manually edit this file. + +import 'dart:async' as _i4; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:sentry/src/hub.dart' as _i3; +import 'package:sentry/src/protocol/breadcrumb.dart' as _i7; +import 'package:sentry/src/protocol/sentry_event.dart' as _i5; +import 'package:sentry/src/protocol/sentry_id.dart' as _i2; +import 'package:sentry/src/protocol/sentry_level.dart' as _i6; +import 'package:sentry/src/sentry_client.dart' as _i8; +import 'package:sentry/src/transport/transport.dart' as _i9; + +// ignore_for_file: comment_references +// ignore_for_file: unnecessary_parenthesis + +class _FakeSentryId extends _i1.Fake implements _i2.SentryId {} + +class _FakeHub extends _i1.Fake implements _i3.Hub {} + +/// A class which mocks [Hub]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockHub extends _i1.Mock implements _i3.Hub { + MockHub() { + _i1.throwOnMissingStub(this); + } + + @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()) as _i2.SentryId); + @override + _i4.Future<_i2.SentryId> captureEvent(_i5.SentryEvent? event, + {dynamic stackTrace, dynamic hint}) => + (super.noSuchMethod( + Invocation.method( + #captureEvent, [event], {#stackTrace: stackTrace, #hint: hint}), + returnValue: + Future.value(_FakeSentryId())) as _i4.Future<_i2.SentryId>); + @override + _i4.Future<_i2.SentryId> captureException(dynamic throwable, + {dynamic stackTrace, dynamic hint}) => + (super.noSuchMethod( + Invocation.method(#captureException, [throwable], + {#stackTrace: stackTrace, #hint: hint}), + returnValue: Future.value(_FakeSentryId())) + as _i4.Future<_i2.SentryId>); + @override + _i4.Future<_i2.SentryId> captureMessage(String? message, + {_i6.SentryLevel? level, + String? template, + List? params, + dynamic hint}) => + (super.noSuchMethod( + Invocation.method(#captureMessage, [ + message + ], { + #level: level, + #template: template, + #params: params, + #hint: hint + }), + returnValue: Future.value(_FakeSentryId())) + as _i4.Future<_i2.SentryId>); + @override + void addBreadcrumb(_i7.Breadcrumb? crumb, {dynamic hint}) => super + .noSuchMethod(Invocation.method(#addBreadcrumb, [crumb], {#hint: hint}), + returnValueForMissingStub: null); + @override + void bindClient(_i8.SentryClient? client) => + super.noSuchMethod(Invocation.method(#bindClient, [client]), + returnValueForMissingStub: null); + @override + _i3.Hub clone() => (super.noSuchMethod(Invocation.method(#clone, []), + returnValue: _FakeHub()) as _i3.Hub); + @override + void configureScope(_i3.ScopeCallback? callback) => + super.noSuchMethod(Invocation.method(#configureScope, [callback]), + returnValueForMissingStub: null); +} + +/// A class which mocks [Transport]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTransport extends _i1.Mock implements _i9.Transport { + MockTransport() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.Future<_i2.SentryId> send(_i5.SentryEvent? event) => (super.noSuchMethod( + Invocation.method(#send, [event]), + returnValue: Future.value(_FakeSentryId())) as _i4.Future<_i2.SentryId>); +} diff --git a/flutter/test/not_initialized_widgets_binding_test.dart b/flutter/test/not_initialized_widgets_binding_test.dart index fb4e8bc5ec..7ffebc982a 100644 --- a/flutter/test/not_initialized_widgets_binding_test.dart +++ b/flutter/test/not_initialized_widgets_binding_test.dart @@ -2,12 +2,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter/src/sentry_flutter_options.dart'; -import 'mocks.dart'; +import 'mocks.mocks.dart'; /// Tests that require `WidgetsFlutterBinding.ensureInitialized();` not /// being called at all. void main() { - Fixture fixture; + late Fixture fixture; setUp(() { fixture = Fixture(); diff --git a/flutter/test/sentry_flutter_test.dart b/flutter/test/sentry_flutter_test.dart index 0ea57a7231..88fa44c7a5 100644 --- a/flutter/test/sentry_flutter_test.dart +++ b/flutter/test/sentry_flutter_test.dart @@ -6,6 +6,7 @@ import 'package:sentry/sentry.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'mocks.dart'; +import 'mocks.mocks.dart'; import 'sentry_flutter_util.dart'; void main() { @@ -75,8 +76,8 @@ void main() { final event = verify(transport.send(captureAny)).captured.first as SentryEvent; - expect(event.sdk.integrations.length, 7); - expect(event.sdk.integrations.contains('loadContextsIntegration'), true); + expect(event.sdk!.integrations.length, 7); + expect(event.sdk!.integrations.contains('loadContextsIntegration'), true); }); test('should not add loadContextsIntegration if not ios', () async { @@ -94,8 +95,9 @@ void main() { final event = verify(transport.send(captureAny)).captured.first as SentryEvent; - expect(event.sdk.integrations.length, 6); - expect(event.sdk.integrations.contains('loadContextsIntegration'), false); + expect(event.sdk!.integrations.length, 6); + expect( + event.sdk!.integrations.contains('loadContextsIntegration'), false); }); test('should not add loadAndroidImageListIntegration if not Android', @@ -114,8 +116,9 @@ void main() { final event = verify(transport.send(captureAny)).captured.first as SentryEvent; - expect(event.sdk.integrations.length, 6); - expect(event.sdk.integrations.contains('loadAndroidImageListIntegration'), + expect(event.sdk!.integrations.length, 6); + expect( + event.sdk!.integrations.contains('loadAndroidImageListIntegration'), false); }); }); diff --git a/flutter/test/sentry_navigator_observer_test.dart b/flutter/test/sentry_navigator_observer_test.dart index c07dff64d1..fd08c2a61d 100644 --- a/flutter/test/sentry_navigator_observer_test.dart +++ b/flutter/test/sentry_navigator_observer_test.dart @@ -3,7 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -import 'mocks.dart'; +import 'mocks.mocks.dart'; void main() { group('RouteObserverBreadcrumb', () { @@ -135,12 +135,12 @@ void main() { }); group('SentryNavigatorObserver', () { - PageRoute route(RouteSettings settings) => PageRouteBuilder( - pageBuilder: (_, __, ___) => null, + PageRoute route(RouteSettings? settings) => PageRouteBuilder( + pageBuilder: (_, __, ___) => Container(), settings: settings, ); - RouteSettings routeSettings(String name, [Object arguments]) => + RouteSettings routeSettings(String name, [Object? arguments]) => RouteSettings(name: name, arguments: arguments); test('Test recording of Breadcrumbs', () { @@ -206,7 +206,7 @@ void main() { test('No RouteSettings', () { PageRoute route() => PageRouteBuilder( - pageBuilder: (_, __, ___) => null, + pageBuilder: (_, __, ___) => Container(), ); final hub = MockHub(); diff --git a/flutter/test/widgets_binding_observer_test.dart b/flutter/test/widgets_binding_observer_test.dart index ac413d8286..501980701c 100644 --- a/flutter/test/widgets_binding_observer_test.dart +++ b/flutter/test/widgets_binding_observer_test.dart @@ -6,12 +6,12 @@ import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter/src/sentry_flutter_options.dart'; import 'package:sentry_flutter/src/widgets_binding_observer.dart'; -import 'mocks.dart'; +import 'mocks.mocks.dart'; void main() { group('WidgetsBindingObserver', () { - SentryFlutterOptions flutterTrackingEnabledOptions; - SentryFlutterOptions flutterTrackingDisabledOptions; + late SentryFlutterOptions flutterTrackingEnabledOptions; + late SentryFlutterOptions flutterTrackingDisabledOptions; setUp(() { WidgetsFlutterBinding.ensureInitialized(); @@ -30,12 +30,12 @@ void main() { hub: hub, options: flutterTrackingEnabledOptions, ); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); final message = const JSONMessageCodec() .encodeMessage({'type': 'memoryPressure'}); - await WidgetsBinding.instance.defaultBinaryMessenger + await WidgetsBinding.instance!.defaultBinaryMessenger .handlePlatformMessage('flutter/system', message, (_) {}); final breadcrumb = @@ -51,7 +51,7 @@ void main() { expect(breadcrumb.type, 'system'); expect(breadcrumb.category, 'device.event'); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); testWidgets('disable memory pressure breadcrumb', @@ -62,22 +62,22 @@ void main() { hub: hub, options: flutterTrackingDisabledOptions, ); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); final message = const JSONMessageCodec() .encodeMessage({'type': 'memoryPressure'}); - await WidgetsBinding.instance.defaultBinaryMessenger + await WidgetsBinding.instance!.defaultBinaryMessenger .handlePlatformMessage('flutter/system', message, (_) {}); verifyNever(hub.addBreadcrumb(captureAny)); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); testWidgets('lifecycle breadcrumbs', (WidgetTester tester) async { Future sendLifecycle(String event) async { - final messenger = ServicesBinding.instance.defaultBinaryMessenger; + final messenger = ServicesBinding.instance!.defaultBinaryMessenger; final message = const StringCodec().encodeMessage('AppLifecycleState.$event'); await messenger.handlePlatformMessage( @@ -94,7 +94,7 @@ void main() { hub: hub, options: flutterTrackingEnabledOptions, ); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); // paused lifecycle event await sendLifecycle('paused'); @@ -136,12 +136,12 @@ void main() { expect(breadcrumb.data, mapForLifecycle('detached')); expect(breadcrumb.level, SentryLevel.info); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); testWidgets('disable lifecycle breadcrumbs', (WidgetTester tester) async { Future sendLifecycle(String event) async { - final messenger = ServicesBinding.instance.defaultBinaryMessenger; + final messenger = ServicesBinding.instance!.defaultBinaryMessenger; final message = const StringCodec().encodeMessage('AppLifecycleState.$event'); await messenger.handlePlatformMessage( @@ -154,13 +154,13 @@ void main() { hub: hub, options: flutterTrackingDisabledOptions, ); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); await sendLifecycle('paused'); verifyNever(hub.addBreadcrumb(captureAny)); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); testWidgets('metrics changed breadcrumb', (WidgetTester tester) async { @@ -170,11 +170,11 @@ void main() { hub: hub, options: flutterTrackingEnabledOptions, ); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); - final window = WidgetsBinding.instance.window; + final window = WidgetsBinding.instance!.window; - window.onMetricsChanged(); + window.onMetricsChanged!(); final breadcrumb = verify(hub.addBreadcrumb(captureAny)).captured.single as Breadcrumb; @@ -189,7 +189,7 @@ void main() { 'new_width': window.physicalSize.width, }); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); testWidgets('disable metrics changed breadcrumb', @@ -200,15 +200,15 @@ void main() { hub: hub, options: flutterTrackingDisabledOptions, ); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); - final window = WidgetsBinding.instance.window; + final window = WidgetsBinding.instance!.window; - window.onMetricsChanged(); + window.onMetricsChanged!(); verifyNever(hub.addBreadcrumb(captureAny)); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); testWidgets('platform brightness breadcrumb', (WidgetTester tester) async { @@ -218,13 +218,13 @@ void main() { hub: hub, options: flutterTrackingEnabledOptions, ); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); - final window = WidgetsBinding.instance.window; + final window = WidgetsBinding.instance!.window; - window.onPlatformBrightnessChanged(); + window.onPlatformBrightnessChanged!(); - final brightness = WidgetsBinding.instance.window.platformBrightness; + final brightness = WidgetsBinding.instance!.window.platformBrightness; final brightnessDescription = brightness == Brightness.dark ? 'dark' : 'light'; @@ -241,7 +241,7 @@ void main() { 'action': 'BRIGHTNESS_CHANGED_TO_${brightnessDescription.toUpperCase()}' }); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); testWidgets('disable platform brightness breadcrumb', @@ -252,15 +252,15 @@ void main() { hub: hub, options: flutterTrackingDisabledOptions, ); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); - final window = WidgetsBinding.instance.window; + final window = WidgetsBinding.instance!.window; - window.onPlatformBrightnessChanged(); + window.onPlatformBrightnessChanged!(); verifyNever(hub.addBreadcrumb(captureAny)); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); testWidgets('text scale factor brightness changed breadcrumb', @@ -271,13 +271,14 @@ void main() { hub: hub, options: flutterTrackingEnabledOptions, ); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); - final window = WidgetsBinding.instance.window; + final window = WidgetsBinding.instance!.window; - window.onTextScaleFactorChanged(); + window.onTextScaleFactorChanged!(); - final newTextScaleFactor = WidgetsBinding.instance.window.textScaleFactor; + final newTextScaleFactor = + WidgetsBinding.instance!.window.textScaleFactor; final breadcrumb = verify(hub.addBreadcrumb(captureAny)).captured.single as Breadcrumb; @@ -291,7 +292,7 @@ void main() { 'action': 'TEXT_SCALE_CHANGED_TO_$newTextScaleFactor' }); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); testWidgets('disable text scale factor brightness changed breadcrumb', @@ -300,15 +301,15 @@ void main() { final observer = SentryWidgetsBindingObserver( hub: hub, options: flutterTrackingDisabledOptions); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); - final window = WidgetsBinding.instance.window; + final window = WidgetsBinding.instance!.window; - window.onTextScaleFactorChanged(); + window.onTextScaleFactorChanged!(); verifyNever(hub.addBreadcrumb(captureAny)); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); }); }