diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index f7d93a20c..cf486c4de 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -84,13 +84,13 @@ jobs: if: matrix.target == 'web' run: | flutter test --platform chrome --test-randomize-ordering-seed=random --exclude-tags canvasKit - flutter test --platform chrome --test-randomize-ordering-seed=random --tags canvasKit --web-renderer canvaskit + flutter test --platform chrome --test-randomize-ordering-seed=random --tags canvasKit - name: Test web (WASM) if: matrix.target == 'web' run: | flutter test --platform chrome --wasm --test-randomize-ordering-seed=random --exclude-tags canvasKit - flutter test --platform chrome --wasm --test-randomize-ordering-seed=random --tags canvasKit --web-renderer canvaskit + flutter test --platform chrome --wasm --test-randomize-ordering-seed=random --tags canvasKit - name: Test VM with coverage if: matrix.target == 'linux' || matrix.target == 'macos' || matrix.target == 'windows' diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index 6e0a00013..3771bfb9d 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -43,7 +43,7 @@ jobs: echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - + - uses: actions/setup-java@v4 with: distribution: "adopt" @@ -94,6 +94,19 @@ jobs: disable-animations: true script: flutter test integration_test/all.dart --dart-define SENTRY_AUTH_TOKEN_E2E=$SENTRY_AUTH_TOKEN_E2E --verbose + - name: launch android emulator & run android integration test in profile mode + uses: reactivecircus/android-emulator-runner@62dbb605bba737720e10b196cb4220d374026a6d #pin@v2.33.0 + with: + working-directory: ./flutter/example + api-level: 31 + profile: Nexus 6 + arch: x86_64 + force-avd-creation: false + avd-name: avd-x86_64-31 + emulator-options: -no-snapshot-save -no-window -accel on -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: flutter drive --driver=integration_test/test_driver/driver.dart --target=integration_test/sentry_widgets_flutter_binding_test.dart --profile -d emulator-5554 + cocoa: name: "${{ matrix.target }} | ${{ matrix.sdk }}" runs-on: macos-latest-xlarge @@ -143,7 +156,9 @@ jobs: - name: run integration test # Disable flutter integration tests for iOS for now (https://github.com/getsentry/sentry-dart/issues/1605#issuecomment-1695809346) if: ${{ matrix.target != 'ios' }} - run: flutter test -d "${{ steps.device.outputs.name }}" integration_test/all.dart --dart-define SENTRY_AUTH_TOKEN_E2E=$SENTRY_AUTH_TOKEN_E2E --verbose + run: | + flutter test -d "${{ steps.device.outputs.name }}" integration_test/all.dart --dart-define SENTRY_AUTH_TOKEN_E2E=$SENTRY_AUTH_TOKEN_E2E --verbose + flutter drive --driver=integration_test/test_driver/driver.dart --target=integration_test/sentry_widgets_flutter_binding_test.dart --profile -d "${{ steps.device.outputs.name }}" - name: run native test # We only have the native unit test package in the iOS xcodeproj at the moment. diff --git a/CHANGELOG.md b/CHANGELOG.md index 51bd44976..3237d98d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,16 @@ - [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#7190) - [diff](https://github.com/getsentry/sentry-java/compare/7.18.1...7.19.0) +## 8.11.1 + +### Improvements + +- Check for type before casting in TTID ([#2497](https://github.com/getsentry/sentry-dart/pull/2497)) + +### Fixes + +- SentryWidgetsFlutterBinding initializing even if a binding already exists ([#2494](https://github.com/getsentry/sentry-dart/pull/2494)) + ## 8.12.0-beta.1 ### Features diff --git a/flutter/example/integration_test/sentry_widgets_flutter_binding_test.dart b/flutter/example/integration_test/sentry_widgets_flutter_binding_test.dart new file mode 100644 index 000000000..b13b32d36 --- /dev/null +++ b/flutter/example/integration_test/sentry_widgets_flutter_binding_test.dart @@ -0,0 +1,28 @@ +// ignore_for_file: avoid_print +// ignore_for_file: invalid_use_of_internal_member + +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; + +// This needs needs to be run with: +// +// flutter drive \ +// --driver=integration_test/test_driver/driver.dart \ +// --target=integration_test/sentry_widgets_flutter_binding_test.dart --profile +// +// This test case will NOT fail in debug builds, hence why we need to test it +// in at least a profile build. + +void main() { + final originalBinding = + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('$SentryWidgetsFlutterBinding', () { + testWidgets('return existing binding', (tester) async { + final binding = SentryWidgetsFlutterBinding.ensureInitialized(); + + expect(binding, equals(originalBinding)); + }); + }); +} diff --git a/flutter/example/integration_test/test_driver/driver.dart b/flutter/example/integration_test/test_driver/driver.dart new file mode 100644 index 000000000..b38629cca --- /dev/null +++ b/flutter/example/integration_test/test_driver/driver.dart @@ -0,0 +1,3 @@ +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); diff --git a/flutter/lib/src/binding_wrapper.dart b/flutter/lib/src/binding_wrapper.dart index 823df58bf..8df735bbd 100644 --- a/flutter/lib/src/binding_wrapper.dart +++ b/flutter/lib/src/binding_wrapper.dart @@ -1,6 +1,5 @@ // ignore_for_file: invalid_use_of_internal_member -import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; @@ -48,16 +47,6 @@ WidgetsBinding? _ambiguate(WidgetsBinding? binding) => binding; class SentryWidgetsFlutterBinding extends WidgetsFlutterBinding with SentryWidgetsBindingMixin { - @override - void initInstances() { - super.initInstances(); - _instance = this; - } - - static SentryWidgetsFlutterBinding get instance => - BindingBase.checkInstance(_instance); - static SentryWidgetsFlutterBinding? _instance; - /// Returns an instance of [SentryWidgetsFlutterBinding]. /// If no binding has yet been initialized, creates and initializes one. /// @@ -65,15 +54,15 @@ class SentryWidgetsFlutterBinding extends WidgetsFlutterBinding /// returns the existing [WidgetsBinding] instance instead. static WidgetsBinding ensureInitialized() { try { - if (SentryWidgetsFlutterBinding._instance == null) { - SentryWidgetsFlutterBinding(); - } - return SentryWidgetsFlutterBinding.instance; - } catch (e) { + // Try to get the existing binding instance + return WidgetsBinding.instance; + } catch (_) { Sentry.currentHub.options.logger( SentryLevel.info, - 'WidgetsFlutterBinding already initialized. ' - 'Falling back to default WidgetsBinding instance.'); + 'WidgetsFlutterBinding has not been initialized yet. ' + 'Creating $SentryWidgetsFlutterBinding.'); + // No binding exists yet, create our custom one + SentryWidgetsFlutterBinding(); return WidgetsBinding.instance; } } diff --git a/flutter/lib/src/navigation/time_to_initial_display_tracker.dart b/flutter/lib/src/navigation/time_to_initial_display_tracker.dart index 5213d4587..3509dc6c4 100644 --- a/flutter/lib/src/navigation/time_to_initial_display_tracker.dart +++ b/flutter/lib/src/navigation/time_to_initial_display_tracker.dart @@ -51,11 +51,13 @@ class TimeToInitialDisplayTracker { final _endTimestamp = endTimestamp ?? await determineEndTime(); if (_endTimestamp == null) return; - final tracer = transaction as SentryTracer; + if (transaction is! SentryTracer) { + return; + } final ttidSpan = transaction.startChild( SentrySpanOperations.uiTimeToInitialDisplay, - description: '${tracer.name} initial display', + description: '${transaction.name} initial display', startTimestamp: startTimestamp, ); diff --git a/flutter/test/sentry_flutter_widgets_binding_test.dart b/flutter/test/sentry_widgets_binding_mixin_test.dart similarity index 100% rename from flutter/test/sentry_flutter_widgets_binding_test.dart rename to flutter/test/sentry_widgets_binding_mixin_test.dart diff --git a/flutter/test/sentry_widgets_flutter_binding_test.dart b/flutter/test/sentry_widgets_flutter_binding_test.dart new file mode 100644 index 000000000..6e51f73eb --- /dev/null +++ b/flutter/test/sentry_widgets_flutter_binding_test.dart @@ -0,0 +1,20 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/src/binding_wrapper.dart'; + +// Note: testing that SentryWidgetsFlutterBinding.ensureInitialized() returns +// the existing binding if one exists is not reliable to test in debug builds +// A previous faulty implementation was passing in debug but not in profile/release builds +// See flutter/example/integration_test/sentry_widgets_flutter_binding_test.dart + +void main() { + group('$SentryWidgetsFlutterBinding', () { + test( + 'no existing binding: ensureInitialized() returns SentryWidgetsFlutterBinding binding', + () { + final binding = SentryWidgetsFlutterBinding.ensureInitialized(); + + expect(binding, equals(WidgetsBinding.instance)); + }); + }); +}