diff --git a/example/lib/ui/views/dialog_view.dart b/example/lib/ui/views/dialog_view.dart index c89a6db..1c0d6eb 100644 --- a/example/lib/ui/views/dialog_view.dart +++ b/example/lib/ui/views/dialog_view.dart @@ -93,12 +93,16 @@ class DialogView extends StatelessWidget { await _dialogService.showCustomDialog( variant: DialogType.Basic, title: 'This is a custom UI with Text as main button', - description: 'Sheck out the builder in the dialog_ui_register.dart file', + description: + 'Sheck out the builder in the dialog_ui_register.dart file', mainButtonTitle: 'Ok', showIconInMainButton: false, barrierDismissible: true, - routeSettings: RouteSettings(name: '/customDialogWithTransition'), - transitionBuilder: (context, animation, secondaryAnimation, child) => SlideTransition( + routeSettings: + RouteSettings(name: '/customDialogWithTransition'), + transitionBuilder: + (context, animation, secondaryAnimation, child) => + SlideTransition( position: animation.drive( Tween( begin: const Offset(1.0, 0.0), @@ -122,10 +126,13 @@ class DialogView extends StatelessWidget { ), OutlinedButton( onPressed: () async { - final response = await _dialogService.showCustomDialog( + final response = await _dialogService.showCustomDialog< + GenericDialogResponse, GenericDialogRequest>( variant: DialogType.Generic, - title: 'This is a custom Generic UI with Text as main button', - description: 'Sheck out the builder in the dialog_ui_register.dart file', + title: + 'This is a custom Generic UI with Text as main button', + description: + 'Sheck out the builder in the dialog_ui_register.dart file', mainButtonTitle: 'Ok', showIconInMainButton: false, barrierDismissible: true, @@ -144,7 +151,8 @@ class DialogView extends StatelessWidget { await _dialogService.showCustomDialog( variant: DialogType.Basic, title: 'This is a custom UI with icon', - description: 'Sheck out the builder in the dialog_ui_register.dart file', + description: + 'Sheck out the builder in the dialog_ui_register.dart file', showIconInMainButton: true, routeSettings: RouteSettings(name: '/customDialog'), ); @@ -166,7 +174,8 @@ class DialogView extends StatelessWidget { title: 'Test Confirmation Dialog Title', description: 'Test Confirmation Dialog Description', barrierDismissible: true, - routeSettings: RouteSettings(name: '/materialConfirmationDialog'), + routeSettings: + RouteSettings(name: '/materialConfirmationDialog'), ); }, child: Text( @@ -210,7 +219,8 @@ class DialogView extends StatelessWidget { title: 'Test Confirmation Dialog Title', description: 'Test Confirmation Dialog Description', barrierDismissible: true, - routeSettings: RouteSettings(name: '/materialConfirmationDialog'), + routeSettings: + RouteSettings(name: '/materialConfirmationDialog'), ); }, child: Text( diff --git a/lib/src/dialog/dialog_service.dart b/lib/src/dialog/dialog_service.dart index cb3a63e..e217f6a 100644 --- a/lib/src/dialog/dialog_service.dart +++ b/lib/src/dialog/dialog_service.dart @@ -26,9 +26,11 @@ class DialogService { _dialogBuilders = {...?_dialogBuilders, ...builders}; } - Map _customDialogBuilders = Map(); + Map _customDialogBuilders = + Map(); - @Deprecated('Prefer to use the StackedServices.navigatorKey instead of using this key. This will be removed in the next major version update for stacked.') + @Deprecated( + 'Prefer to use the StackedServices.navigatorKey instead of using this key. This will be removed in the next major version update for stacked.') get navigatorKey { return Get.key; } @@ -45,7 +47,9 @@ class DialogService { ) void registerCustomDialogBuilder({ required dynamic variant, - required Widget Function(BuildContext, DialogRequest, Function(DialogResponse)) builder, + required Widget Function( + BuildContext, DialogRequest, Function(DialogResponse)) + builder, }) { _customDialogBuilders[variant] = builder; } @@ -86,7 +90,9 @@ class DialogService { navigatorKey: navigatorKey, ); } else { - var _dialogType = GetPlatform.isAndroid ? DialogPlatform.Material : DialogPlatform.Cupertino; + var _dialogType = GetPlatform.isAndroid + ? DialogPlatform.Material + : DialogPlatform.Cupertino; return _showDialog( title: title, description: description, @@ -192,7 +198,9 @@ class DialogService { RouteSettings? routeSettings, GlobalKey? navigatorKey, RouteTransitionsBuilder? transitionBuilder, - @Deprecated('Prefer to use `data` and pass in a generic type. customData doesn\'t work anymore') dynamic customData, + @Deprecated( + 'Prefer to use `data` and pass in a generic type. customData doesn\'t work anymore') + dynamic customData, R? data, }) { assert( diff --git a/lib/src/snackbar/snackbar_service.dart b/lib/src/snackbar/snackbar_service.dart index f17f984..e97c5ae 100644 --- a/lib/src/snackbar/snackbar_service.dart +++ b/lib/src/snackbar/snackbar_service.dart @@ -244,10 +244,13 @@ class SnackbarService { return getBar.show(); } else { Completer completer = Completer(); - sc.ambiguate(WidgetsBinding.instance)!.addPostFrameCallback((_) async { + final binding = sc.ambiguate(WidgetsBinding.instance)!; + binding.addPostFrameCallback((_) async { final result = getBar.show(); completer.complete(result); }); + // Schedule a frame so the callback also fires when the binding is idle. + binding.ensureVisualUpdate(); return completer.future; } } diff --git a/test/snackbar_service_test.dart b/test/snackbar_service_test.dart new file mode 100644 index 0000000..4772562 --- /dev/null +++ b/test/snackbar_service_test.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; +import 'package:stacked_services/stacked_services.dart'; + +void main() { + group('SnackbarService showCustomSnackBar -', () { + testWidgets( + 'schedules a frame and shows the snackbar when called after an async ' + 'gap (instantInit: false)', + (tester) async { + await tester.pumpWidget( + GetMaterialApp(home: Scaffold(body: Container())), + ); + await tester.pump(); + + final snackbarService = SnackbarService(); + snackbarService.registerCustomSnackbarConfig( + variant: 'regression-1163', + config: SnackbarConfig(), + ); + + expect(SchedulerBinding.instance.hasScheduledFrame, isFalse); + + snackbarService.showCustomSnackBar( + message: 'Blue and yellow', + variant: 'regression-1163', + duration: const Duration(milliseconds: 200), + ); + + expect(SchedulerBinding.instance.hasScheduledFrame, isTrue); + + await tester.pump(); + await tester.pump(const Duration(milliseconds: 50)); + + expect(find.text('Blue and yellow'), findsOneWidget); + + await tester.pump(const Duration(milliseconds: 200)); + await tester.pump(const Duration(milliseconds: 1200)); + }, + ); + + testWidgets( + 'still shows immediately when instantInit is true', + (tester) async { + await tester.pumpWidget( + GetMaterialApp(home: Scaffold(body: Container())), + ); + await tester.pump(); + + final snackbarService = SnackbarService(); + snackbarService.registerCustomSnackbarConfig( + variant: 'regression-1163-instant', + config: SnackbarConfig(instantInit: true), + ); + + snackbarService.showCustomSnackBar( + message: 'Instant message', + variant: 'regression-1163-instant', + duration: const Duration(milliseconds: 200), + ); + + await tester.pump(); + await tester.pump(const Duration(milliseconds: 50)); + + expect(find.text('Instant message'), findsOneWidget); + + await tester.pump(const Duration(milliseconds: 200)); + await tester.pump(const Duration(milliseconds: 1200)); + }, + ); + }); +}