From 135bb5d4f295be02f231e3b1946307df21a933ee Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Fri, 8 Apr 2022 16:27:07 -0500 Subject: [PATCH] Remove deprecated Scaffold SnackBar API (#98549) --- .../flutter/lib/src/material/scaffold.dart | 261 --------- .../flutter/lib/src/material/snack_bar.dart | 6 +- .../flutter/test/material/debug_test.dart | 66 ++- .../flutter/test/material/snack_bar_test.dart | 512 +----------------- 4 files changed, 72 insertions(+), 773 deletions(-) diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index edd0b958df..1931db50e2 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -2017,195 +2017,8 @@ class ScaffoldState extends State with TickerProviderStateMixin, Resto // Used for both the snackbar and material banner APIs ScaffoldMessengerState? _scaffoldMessenger; - bool? _accessibleNavigation; // SNACKBAR API - final Queue> _snackBars = Queue>(); - AnimationController? _snackBarController; - Timer? _snackBarTimer; - - /// [ScaffoldMessengerState.showSnackBar] shows a [SnackBar] at the bottom of - /// the scaffold. This method should not be used, and will be deprecated in - /// the near future.. - /// - /// A scaffold can show at most one snack bar at a time. If this function is - /// called while another snack bar is already visible, the given snack bar - /// will be added to a queue and displayed after the earlier snack bars have - /// closed. - /// - /// To control how long a [SnackBar] remains visible, use [SnackBar.duration]. - /// - /// To remove the [SnackBar] with an exit animation, use - /// [ScaffoldMessengerState.hideCurrentSnackBar] or call - /// [ScaffoldFeatureController.close] on the returned [ScaffoldFeatureController]. - /// To remove a [SnackBar] suddenly (without an animation), use - /// [ScaffoldMessengerState.removeCurrentSnackBar]. - /// - /// See [ScaffoldMessenger.of] for information about how to obtain the - /// [ScaffoldMessengerState]. - /// - /// {@tool dartpad} - /// Here is an example of showing a [SnackBar] when the user presses a button. - /// - /// ** See code in examples/api/lib/material/scaffold/scaffold_state.show_snack_bar.0.dart ** - /// {@end-tool} - /// - /// See also: - /// - /// * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s. - @Deprecated( - 'Use ScaffoldMessenger.showSnackBar. ' - 'This feature was deprecated after v1.23.0-14.0.pre.', - ) - ScaffoldFeatureController showSnackBar(SnackBar snackbar) { - _snackBarController ??= SnackBar.createAnimationController(vsync: this) - ..addStatusListener(_handleSnackBarStatusChange); - if (_snackBars.isEmpty) { - assert(_snackBarController!.isDismissed); - _snackBarController!.forward(); - } - late ScaffoldFeatureController controller; - controller = ScaffoldFeatureController._( - // We provide a fallback key so that if back-to-back snackbars happen to - // match in structure, material ink splashes and highlights don't survive - // from one to the next. - snackbar.withAnimation(_snackBarController!, fallbackKey: UniqueKey()), - Completer(), - () { - assert(_snackBars.first == controller); - hideCurrentSnackBar(); - }, - null, // SnackBar doesn't use a builder function so setState() wouldn't rebuild it - ); - setState(() { - _snackBars.addLast(controller); - }); - return controller; - } - - void _handleSnackBarStatusChange(AnimationStatus status) { - switch (status) { - case AnimationStatus.dismissed: - assert(_snackBars.isNotEmpty); - setState(() { - _snackBars.removeFirst(); - }); - if (_snackBars.isNotEmpty) - _snackBarController!.forward(); - break; - case AnimationStatus.completed: - setState(() { - assert(_snackBarTimer == null); - // build will create a new timer if necessary to dismiss the snack bar - }); - break; - case AnimationStatus.forward: - case AnimationStatus.reverse: - break; - } - } - - /// [ScaffoldMessengerState.removeCurrentSnackBar] removes the current - /// [SnackBar] (if any) immediately. This method should not be used, and will - /// be deprecated in the near future. - /// - /// The removed snack bar does not run its normal exit animation. If there are - /// any queued snack bars, they begin their entrance animation immediately. - /// - /// See also: - /// - /// * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s. - @Deprecated( - 'Use ScaffoldMessenger.removeCurrentSnackBar. ' - 'This feature was deprecated after v1.23.0-14.0.pre.', - ) - void removeCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.remove }) { - assert(reason != null); - - // SnackBars and SnackBarActions can call to hide and remove themselves, but - // they are not aware of who presented them, the Scaffold or the - // ScaffoldMessenger. As such, when the SnackBar classes call upon Scaffold - // to remove (the current default), we should re-direct to the - // ScaffoldMessenger here if that is where the SnackBar originated from. - if (_messengerSnackBar != null) { - // ScaffoldMessenger is presenting SnackBars. - assert(debugCheckHasScaffoldMessenger(context)); - assert( - _scaffoldMessenger != null, - 'A SnackBar was shown by the ScaffoldMessenger, but has been called upon ' - 'to be removed from a Scaffold that is not registered with a ' - 'ScaffoldMessenger, this can happen if a Scaffold has been rebuilt ' - 'without an ancestor ScaffoldMessenger.', - ); - _scaffoldMessenger!.removeCurrentSnackBar(reason: reason); - return; - } - - if (_snackBars.isEmpty) - return; - final Completer completer = _snackBars.first._completer; - if (!completer.isCompleted) - completer.complete(reason); - _snackBarTimer?.cancel(); - _snackBarTimer = null; - _snackBarController!.value = 0.0; - } - - /// [ScaffoldMessengerState.hideCurrentSnackBar] removes the current - /// [SnackBar] by running its normal exit animation. This method should not be - /// used, and will be deprecated in the near future. - /// - /// The closed completer is called after the animation is complete. - /// - /// See also: - /// - /// * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s. - @Deprecated( - 'Use ScaffoldMessenger.hideCurrentSnackBar. ' - 'This feature was deprecated after v1.23.0-14.0.pre.', - ) - void hideCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.hide }) { - assert(reason != null); - - // SnackBars and SnackBarActions can call to hide and remove themselves, but - // they are not aware of who presented them, the Scaffold or the - // ScaffoldMessenger. As such, when the SnackBar classes call upon Scaffold - // to remove (the current default), we should re-direct to the - // ScaffoldMessenger here if that is where the SnackBar originated from. - if (_messengerSnackBar != null) { - // ScaffoldMessenger is presenting SnackBars. - assert(debugCheckHasScaffoldMessenger(context)); - assert( - _scaffoldMessenger != null, - 'A SnackBar was shown by the ScaffoldMessenger, but has been called upon ' - 'to be removed from a Scaffold that is not registered with a ' - 'ScaffoldMessenger, this can happen if a Scaffold has been rebuilt ' - 'without an ancestor ScaffoldMessenger.', - ); - _scaffoldMessenger!.hideCurrentSnackBar(reason: reason); - return; - } - - if (_snackBars.isEmpty || _snackBarController!.status == AnimationStatus.dismissed) - return; - final MediaQueryData mediaQuery = MediaQuery.of(context); - final Completer completer = _snackBars.first._completer; - if (mediaQuery.accessibleNavigation) { - _snackBarController!.value = 0.0; - completer.complete(reason); - } else { - _snackBarController!.reverse().then((void value) { - assert(mounted); - if (!completer.isCompleted) - completer.complete(reason); - }); - } - _snackBarTimer?.cancel(); - _snackBarTimer = null; - } - - // The _messengerSnackBar represents the current SnackBar being managed by - // the ScaffoldMessenger, instead of the Scaffold. ScaffoldFeatureController? _messengerSnackBar; // This is used to update the _messengerSnackBar by the ScaffoldMessenger. @@ -2672,31 +2485,12 @@ class ScaffoldState extends State with TickerProviderStateMixin, Resto _scaffoldMessenger = currentScaffoldMessenger; _scaffoldMessenger?._register(this); - // TODO(Piinks): Remove old SnackBar API after migrating ScaffoldMessenger - final MediaQueryData mediaQuery = MediaQuery.of(context); - // If we transition from accessible navigation to non-accessible navigation - // and there is a SnackBar that would have timed out that has already - // completed its timer, dismiss that SnackBar. If the timer hasn't finished - // yet, let it timeout as normal. - if ((_accessibleNavigation ?? false) - && !mediaQuery.accessibleNavigation - && _snackBarTimer != null - && !_snackBarTimer!.isActive) { - hideCurrentSnackBar(reason: SnackBarClosedReason.timeout); - } - _accessibleNavigation = mediaQuery.accessibleNavigation; - _maybeBuildPersistentBottomSheet(); super.didChangeDependencies(); } @override void dispose() { - // TODO(Piinks): Remove old SnackBar API after migrating ScaffoldMessenger - _snackBarController?.dispose(); - _snackBarTimer?.cancel(); - _snackBarTimer = null; - _geometryNotifier.dispose(); _floatingActionButtonMoveController.dispose(); _floatingActionButtonVisibilityController.dispose(); @@ -2817,31 +2611,6 @@ class ScaffoldState extends State with TickerProviderStateMixin, Resto final ThemeData themeData = Theme.of(context); final TextDirection textDirection = Directionality.of(context); - // TODO(Piinks): Remove old SnackBar API after migrating ScaffoldMessenger - _accessibleNavigation = mediaQuery.accessibleNavigation; - if (_snackBars.isNotEmpty) { - final ModalRoute? route = ModalRoute.of(context); - if (route == null || route.isCurrent) { - if (_snackBarController!.isCompleted && _snackBarTimer == null) { - final SnackBar snackBar = _snackBars.first._widget; - _snackBarTimer = Timer(snackBar.duration, () { - assert( - _snackBarController!.status == AnimationStatus.forward || - _snackBarController!.status == AnimationStatus.completed, - ); - // Look up MediaQuery again in case the setting changed. - final MediaQueryData mediaQuery = MediaQuery.of(context); - if (mediaQuery.accessibleNavigation && snackBar.action != null) - return; - hideCurrentSnackBar(reason: SnackBarClosedReason.timeout); - }); - } - } else { - _snackBarTimer?.cancel(); - _snackBarTimer = null; - } - } - final List children = []; _addIfNonNull( children, @@ -2895,15 +2664,6 @@ class ScaffoldState extends State with TickerProviderStateMixin, Resto bool isSnackBarFloating = false; double? snackBarWidth; - // We should only be using one API for SnackBars. Currently, we can use the - // Scaffold, which creates a SnackBar queue (_snackBars), or the - // ScaffoldMessenger, which sends a SnackBar to descendant Scaffolds. - // (_messengerSnackBar). - assert( - _snackBars.isEmpty || _messengerSnackBar == null, - 'Only one API should be used to manage SnackBars. The ScaffoldMessenger is ' - 'the preferred API instead of the Scaffold methods.', - ); if (_currentBottomSheet != null || _dismissedBottomSheets.isNotEmpty) { final Widget stack = Stack( @@ -2944,27 +2704,6 @@ class ScaffoldState extends State with TickerProviderStateMixin, Resto ); } - // SnackBar set by Scaffold - // TODO(Piinks): Remove old SnackBar API after migrating ScaffoldMessenger - if (_snackBars.isNotEmpty) { - final SnackBarBehavior snackBarBehavior = _snackBars.first._widget.behavior - ?? themeData.snackBarTheme.behavior - ?? SnackBarBehavior.fixed; - isSnackBarFloating = snackBarBehavior == SnackBarBehavior.floating; - snackBarWidth = _snackBars.first._widget.width; - - _addIfNonNull( - children, - _snackBars.first._widget, - _ScaffoldSlot.snackBar, - removeLeftPadding: false, - removeTopPadding: true, - removeRightPadding: false, - removeBottomPadding: widget.bottomNavigationBar != null || widget.persistentFooterButtons != null, - maintainBottomViewPadding: !_resizeToAvoidBottomInset, - ); - } - bool extendBodyBehindMaterialBanner = false; // MaterialBanner set by ScaffoldMessenger if (_messengerMaterialBanner != null) { diff --git a/packages/flutter/lib/src/material/snack_bar.dart b/packages/flutter/lib/src/material/snack_bar.dart index aeaff37358..6246d6b1d7 100644 --- a/packages/flutter/lib/src/material/snack_bar.dart +++ b/packages/flutter/lib/src/material/snack_bar.dart @@ -122,7 +122,7 @@ class _SnackBarActionState extends State { _haveTriggeredAction = true; }); widget.onPressed(); - Scaffold.of(context).hideCurrentSnackBar(reason: SnackBarClosedReason.action); + ScaffoldMessenger.of(context).hideCurrentSnackBar(reason: SnackBarClosedReason.action); } @override @@ -583,14 +583,14 @@ class _SnackBarState extends State { container: true, liveRegion: true, onDismiss: () { - Scaffold.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.dismiss); + ScaffoldMessenger.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.dismiss); }, child: Dismissible( key: const Key('dismissible'), direction: widget.dismissDirection, resizeDuration: null, onDismissed: (DismissDirection direction) { - Scaffold.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.swipe); + ScaffoldMessenger.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.swipe); }, child: snackBar, ), diff --git a/packages/flutter/test/material/debug_test.dart b/packages/flutter/test/material/debug_test.dart index c0155f9f90..57a4b14688 100644 --- a/packages/flutter/test/material/debug_test.dart +++ b/packages/flutter/test/material/debug_test.dart @@ -263,8 +263,10 @@ void main() { ), ), )); - // The Scaffold should assert we still have an ancestor ScaffoldMessenger in - // order to dismiss the SnackBar from the ScaffoldMessenger. + // Tap SnackBarAction to dismiss. + // The SnackBarAction should assert we still have an ancestor + // ScaffoldMessenger in order to dismiss the SnackBar from the + // Scaffold. await tester.tap(find.text('Test')); FlutterError.onError = oldHandler; @@ -286,11 +288,67 @@ void main() { expect(error.toStringDeep(), equalsIgnoringHashCodes( 'FlutterError\n' ' No ScaffoldMessenger widget found.\n' - ' Scaffold widgets require a ScaffoldMessenger widget ancestor.\n' + ' SnackBarAction widgets require a ScaffoldMessenger widget\n' + ' ancestor.\n' ' The specific widget that could not find a ScaffoldMessenger\n' ' ancestor was:\n' - ' Scaffold-[LabeledGlobalKey#00829]\n' + ' SnackBarAction\n' ' The ancestors of this widget were:\n' + ' TextButtonTheme\n' + ' Padding\n' + ' Row\n' + ' Padding\n' + ' MediaQuery\n' + ' Padding\n' + ' SafeArea\n' + ' FadeTransition\n' + ' IconTheme\n' + ' IconTheme\n' + ' _InheritedCupertinoTheme\n' + ' CupertinoTheme\n' + ' _InheritedTheme\n' + ' Theme\n' + ' DefaultTextStyle\n' + ' AnimatedDefaultTextStyle\n' + ' _InkFeatures-[GlobalKey#00000 ink renderer]\n' + ' NotificationListener\n' + ' PhysicalModel\n' + ' AnimatedPhysicalModel\n' + ' Material\n' + ' FractionalTranslation\n' + ' SlideTransition\n' + ' Listener\n' + ' _GestureSemantics\n' + ' RawGestureDetector\n' + ' GestureDetector\n' + " Dismissible-[<'dismissible'>]\n" + ' Semantics\n' + ' Align\n' + ' AnimatedBuilder\n' + ' ClipRect\n' + ' KeyedSubtree-[GlobalKey#00000]\n' + ' _EffectiveTickerMode\n' + ' TickerMode\n' + ' Offstage\n' + ' SizedBox\n' + ' Hero\n' + ' SnackBar-[#00000]\n' + ' MediaQuery\n' + ' LayoutId-[<_ScaffoldSlot.snackBar>]\n' + ' CustomMultiChildLayout\n' + ' AnimatedBuilder\n' + ' DefaultTextStyle\n' + ' AnimatedDefaultTextStyle\n' + ' _InkFeatures-[GlobalKey#00000 ink renderer]\n' + ' NotificationListener\n' + ' PhysicalModel\n' + ' AnimatedPhysicalModel\n' + ' Material\n' + ' _ScrollMetricsNotificationObserverScope\n' + ' NotificationListener\n' + ' ScrollMetricsNotificationObserver\n' + ' _ScaffoldScope\n' + ' Scaffold-[LabeledGlobalKey#00000]\n' ' MediaQuery\n' ' Directionality\n' ' [root]\n' diff --git a/packages/flutter/test/material/snack_bar_test.dart b/packages/flutter/test/material/snack_bar_test.dart index b54f79d581..7ea6014418 100644 --- a/packages/flutter/test/material/snack_bar_test.dart +++ b/packages/flutter/test/material/snack_bar_test.dart @@ -8,58 +8,12 @@ import 'dart:ui'; -import 'package:flutter/foundation.dart' show FlutterExceptionHandler; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('SnackBar control test', (WidgetTester tester) async { - const String helloSnackBar = 'Hello SnackBar'; - const Key tapTarget = Key('tap-target'); - await tester.pumpWidget(MaterialApp( - home: Scaffold( - body: Builder( - builder: (BuildContext context) { - return GestureDetector( - key: tapTarget, - onTap: () { - Scaffold.of(context).showSnackBar(const SnackBar( - content: Text(helloSnackBar), - duration: Duration(seconds: 2), - )); - }, - behavior: HitTestBehavior.opaque, - child: const SizedBox( - height: 100.0, - width: 100.0, - ), - ); - }, - ), - ), - )); - expect(find.text(helloSnackBar), findsNothing); - await tester.tap(find.byKey(tapTarget)); - expect(find.text(helloSnackBar), findsNothing); - await tester.pump(); // schedule animation - expect(find.text(helloSnackBar), findsOneWidget); - await tester.pump(); // begin animation - expect(find.text(helloSnackBar), findsOneWidget); - await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here - expect(find.text(helloSnackBar), findsOneWidget); - await tester.pump(const Duration(milliseconds: 750)); // 1.50s - expect(find.text(helloSnackBar), findsOneWidget); - await tester.pump(const Duration(milliseconds: 750)); // 2.25s - expect(find.text(helloSnackBar), findsOneWidget); - await tester.pump(const Duration(milliseconds: 750)); // 3.00s // timer triggers to dismiss snackbar, reverse animation is scheduled - await tester.pump(); // begin animation - expect(find.text(helloSnackBar), findsOneWidget); // frame 0 of dismiss animation - await tester.pump(const Duration(milliseconds: 750)); // 3.75s // last frame of animation, snackbar removed from build - expect(find.text(helloSnackBar), findsNothing); - }); - - testWidgets('SnackBar control test - ScaffoldMessenger', (WidgetTester tester) async { const String helloSnackBar = 'Hello SnackBar'; const Key tapTarget = Key('tap-target'); await tester.pumpWidget(MaterialApp( @@ -105,81 +59,6 @@ void main() { }); testWidgets('SnackBar twice test', (WidgetTester tester) async { - int snackBarCount = 0; - const Key tapTarget = Key('tap-target'); - await tester.pumpWidget(MaterialApp( - home: Scaffold( - body: Builder( - builder: (BuildContext context) { - return GestureDetector( - key: tapTarget, - onTap: () { - snackBarCount += 1; - Scaffold.of(context).showSnackBar(SnackBar( - content: Text('bar$snackBarCount'), - duration: const Duration(seconds: 2), - )); - }, - behavior: HitTestBehavior.opaque, - child: const SizedBox( - height: 100.0, - width: 100.0, - ), - ); - }, - ), - ), - )); - expect(find.text('bar1'), findsNothing); - expect(find.text('bar2'), findsNothing); - await tester.tap(find.byKey(tapTarget)); // queue bar1 - await tester.tap(find.byKey(tapTarget)); // queue bar2 - expect(find.text('bar1'), findsNothing); - expect(find.text('bar2'), findsNothing); - await tester.pump(); // schedule animation for bar1 - expect(find.text('bar1'), findsOneWidget); - expect(find.text('bar2'), findsNothing); - await tester.pump(); // begin animation - expect(find.text('bar1'), findsOneWidget); - expect(find.text('bar2'), findsNothing); - await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here - expect(find.text('bar1'), findsOneWidget); - expect(find.text('bar2'), findsNothing); - await tester.pump(const Duration(milliseconds: 750)); // 1.50s - expect(find.text('bar1'), findsOneWidget); - expect(find.text('bar2'), findsNothing); - await tester.pump(const Duration(milliseconds: 750)); // 2.25s - expect(find.text('bar1'), findsOneWidget); - expect(find.text('bar2'), findsNothing); - await tester.pump(const Duration(milliseconds: 750)); // 3.00s // timer triggers to dismiss snackbar, reverse animation is scheduled - await tester.pump(); // begin animation - expect(find.text('bar1'), findsOneWidget); - expect(find.text('bar2'), findsNothing); - await tester.pump(const Duration(milliseconds: 750)); // 3.75s // last frame of animation, snackbar removed from build, new snack bar put in its place - expect(find.text('bar1'), findsNothing); - expect(find.text('bar2'), findsOneWidget); - await tester.pump(); // begin animation - expect(find.text('bar1'), findsNothing); - expect(find.text('bar2'), findsOneWidget); - await tester.pump(const Duration(milliseconds: 750)); // 4.50s // animation last frame; two second timer starts here - expect(find.text('bar1'), findsNothing); - expect(find.text('bar2'), findsOneWidget); - await tester.pump(const Duration(milliseconds: 750)); // 5.25s - expect(find.text('bar1'), findsNothing); - expect(find.text('bar2'), findsOneWidget); - await tester.pump(const Duration(milliseconds: 750)); // 6.00s - expect(find.text('bar1'), findsNothing); - expect(find.text('bar2'), findsOneWidget); - await tester.pump(const Duration(milliseconds: 750)); // 6.75s // timer triggers to dismiss snackbar, reverse animation is scheduled - await tester.pump(); // begin animation - expect(find.text('bar1'), findsNothing); - expect(find.text('bar2'), findsOneWidget); - await tester.pump(const Duration(milliseconds: 750)); // 7.50s // last frame of animation, snackbar removed from build, new snack bar put in its place - expect(find.text('bar1'), findsNothing); - expect(find.text('bar2'), findsNothing); - }); - - testWidgets('SnackBar twice test - ScaffoldMessenger', (WidgetTester tester) async { int snackBarCount = 0; const Key tapTarget = Key('tap-target'); await tester.pumpWidget(MaterialApp( @@ -255,92 +134,6 @@ void main() { }); testWidgets('SnackBar cancel test', (WidgetTester tester) async { - int snackBarCount = 0; - const Key tapTarget = Key('tap-target'); - late int time; - late ScaffoldFeatureController lastController; - await tester.pumpWidget(MaterialApp( - home: Scaffold( - body: Builder( - builder: (BuildContext context) { - return GestureDetector( - key: tapTarget, - onTap: () { - snackBarCount += 1; - lastController = Scaffold.of(context).showSnackBar(SnackBar( - content: Text('bar$snackBarCount'), - duration: Duration(seconds: time), - )); - }, - behavior: HitTestBehavior.opaque, - child: const SizedBox( - height: 100.0, - width: 100.0, - ), - ); - }, - ), - ), - )); - expect(find.text('bar1'), findsNothing); - expect(find.text('bar2'), findsNothing); - time = 1000; - await tester.tap(find.byKey(tapTarget)); // queue bar1 - final ScaffoldFeatureController firstController = lastController; - time = 2; - await tester.tap(find.byKey(tapTarget)); // queue bar2 - expect(find.text('bar1'), findsNothing); - expect(find.text('bar2'), findsNothing); - await tester.pump(); // schedule animation for bar1 - expect(find.text('bar1'), findsOneWidget); - expect(find.text('bar2'), findsNothing); - await tester.pump(); // begin animation - expect(find.text('bar1'), findsOneWidget); - expect(find.text('bar2'), findsNothing); - await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here - expect(find.text('bar1'), findsOneWidget); - expect(find.text('bar2'), findsNothing); - await tester.pump(const Duration(milliseconds: 750)); // 1.50s - expect(find.text('bar1'), findsOneWidget); - expect(find.text('bar2'), findsNothing); - await tester.pump(const Duration(milliseconds: 750)); // 2.25s - expect(find.text('bar1'), findsOneWidget); - expect(find.text('bar2'), findsNothing); - await tester.pump(const Duration(milliseconds: 10000)); // 12.25s - expect(find.text('bar1'), findsOneWidget); - expect(find.text('bar2'), findsNothing); - - firstController.close(); // snackbar is manually dismissed - - await tester.pump(const Duration(milliseconds: 750)); // 13.00s // reverse animation is scheduled - await tester.pump(); // begin animation - expect(find.text('bar1'), findsOneWidget); - expect(find.text('bar2'), findsNothing); - await tester.pump(const Duration(milliseconds: 750)); // 13.75s // last frame of animation, snackbar removed from build, new snack bar put in its place - expect(find.text('bar1'), findsNothing); - expect(find.text('bar2'), findsOneWidget); - await tester.pump(); // begin animation - expect(find.text('bar1'), findsNothing); - expect(find.text('bar2'), findsOneWidget); - await tester.pump(const Duration(milliseconds: 750)); // 14.50s // animation last frame; two second timer starts here - expect(find.text('bar1'), findsNothing); - expect(find.text('bar2'), findsOneWidget); - await tester.pump(const Duration(milliseconds: 750)); // 15.25s - expect(find.text('bar1'), findsNothing); - expect(find.text('bar2'), findsOneWidget); - await tester.pump(const Duration(milliseconds: 750)); // 16.00s - expect(find.text('bar1'), findsNothing); - expect(find.text('bar2'), findsOneWidget); - await tester.pump(const Duration(milliseconds: 750)); // 16.75s // timer triggers to dismiss snackbar, reverse animation is scheduled - await tester.pump(); // begin animation - expect(find.text('bar1'), findsNothing); - expect(find.text('bar2'), findsOneWidget); - await tester.pump(const Duration(milliseconds: 750)); // 17.50s // last frame of animation, snackbar removed from build, new snack bar put in its place - expect(find.text('bar1'), findsNothing); - expect(find.text('bar2'), findsNothing); - }); - - testWidgets('SnackBar cancel test - ScaffoldMessenger', (WidgetTester tester) async { int snackBarCount = 0; const Key tapTarget = Key('tap-target'); late int time; @@ -432,48 +225,6 @@ void main() { late double width; int snackBarCount = 0; - await tester.pumpWidget(MaterialApp( - home: Scaffold( - body: Builder( - builder: (BuildContext context) { - width = MediaQuery.of(context).size.width; - - return GestureDetector( - key: tapTarget, - onTap: () { - snackBarCount += 1; - Scaffold.of(context).showSnackBar(SnackBar( - content: Text('bar$snackBarCount'), - duration: const Duration(seconds: 2), - dismissDirection: dismissDirection, - )); - }, - behavior: HitTestBehavior.opaque, - child: const SizedBox( - height: 100.0, - width: 100.0, - ), - ); - }, - ), - ), - )); - - await _testSnackBarDismiss( - tester: tester, - tapTarget: tapTarget, - scaffoldWidth: width, - onDismissDirectionChange: (DismissDirection dir) => dismissDirection = dir, - onDragGestureChange: () => snackBarCount = 0, - ); - }); - - testWidgets('SnackBar dismiss test - ScaffoldMessenger', (WidgetTester tester) async { - const Key tapTarget = Key('tap-target'); - late DismissDirection dismissDirection; - late double width; - int snackBarCount = 0; - await tester.pumpWidget(MaterialApp( home: Scaffold( body: Builder( @@ -511,45 +262,6 @@ void main() { }); testWidgets('SnackBar cannot be tapped twice', (WidgetTester tester) async { - int tapCount = 0; - await tester.pumpWidget(MaterialApp( - home: Scaffold( - body: Builder( - builder: (BuildContext context) { - return GestureDetector( - onTap: () { - Scaffold.of(context).showSnackBar(SnackBar( - content: const Text('I am a snack bar.'), - duration: const Duration(seconds: 2), - action: SnackBarAction( - label: 'ACTION', - onPressed: () { - ++tapCount; - }, - ), - )); - }, - child: const Text('X'), - ); - }, - ), - ), - )); - await tester.tap(find.text('X')); - await tester.pump(); // start animation - await tester.pump(const Duration(milliseconds: 750)); - - expect(tapCount, equals(0)); - await tester.tap(find.text('ACTION')); - expect(tapCount, equals(1)); - await tester.tap(find.text('ACTION')); - expect(tapCount, equals(1)); - await tester.pump(); - await tester.tap(find.text('ACTION')); - expect(tapCount, equals(1)); - }); - - testWidgets('SnackBar cannot be tapped twice - ScaffoldMessenger', (WidgetTester tester) async { int tapCount = 0; await tester.pumpWidget(MaterialApp( home: Scaffold( @@ -598,7 +310,7 @@ void main() { builder: (BuildContext context) { return GestureDetector( onTap: () { - Scaffold.of(context).showSnackBar( + ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('I am a snack bar.'), duration: const Duration(seconds: 2), @@ -640,7 +352,7 @@ void main() { builder: (BuildContext context) { return GestureDetector( onTap: () { - Scaffold.of(context).showSnackBar( + ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('I am a snack bar.'), duration: const Duration(seconds: 2), @@ -679,7 +391,7 @@ void main() { builder: (BuildContext context) { return GestureDetector( onTap: () { - Scaffold.of(context).showSnackBar( + ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('I am a snack bar.'), duration: const Duration(seconds: 2), @@ -809,7 +521,7 @@ void main() { themeBeforeSnackBar = Theme.of(context); return GestureDetector( onTap: () { - Scaffold.of(context).showSnackBar( + ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Builder( builder: (BuildContext context) { @@ -853,7 +565,7 @@ void main() { builder: (BuildContext context) { return GestureDetector( onTap: () { - Scaffold.of(context).showSnackBar( + ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('I am a snack bar.'), margin: EdgeInsets.all(padding), @@ -885,53 +597,6 @@ void main() { }); testWidgets('SnackbarBehavior.floating is positioned within safe area', (WidgetTester tester) async { - const double viewPadding = 50.0; - const double floatingSnackBarDefaultBottomMargin = 10.0; - await tester.pumpWidget( - MaterialApp( - home: MediaQuery( - data: const MediaQueryData( - // Simulate non-safe area. - viewPadding: EdgeInsets.only(bottom: viewPadding), - ), - child: Scaffold( - body: Builder( - builder: (BuildContext context) { - return GestureDetector( - onTap: () { - Scaffold.of(context).showSnackBar( - const SnackBar( - content: Text('I am a snack bar.'), - behavior: SnackBarBehavior.floating, - ), - ); - }, - child: const Text('X'), - ); - }, - ), - ), - ), - ), - ); - - await tester.tap(find.text('X')); - await tester.pump(); // Start animation - await tester.pump(const Duration(milliseconds: 750)); - - final Finder materialFinder = find.descendant( - of: find.byType(SnackBar), - matching: find.byType(Material), - ); - final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder); - expect( - snackBarBottomLeft.dy, - // Device height is 600. - 600 - viewPadding - floatingSnackBarDefaultBottomMargin, - ); - }); - - testWidgets('SnackbarBehavior.floating is positioned within safe area - ScaffoldMessenger', (WidgetTester tester) async { const double viewPadding = 50.0; const double floatingSnackBarDefaultBottomMargin = 10.0; await tester.pumpWidget( @@ -1452,47 +1117,6 @@ void main() { testWidgets('accessible navigation behavior with action', (WidgetTester tester) async { final GlobalKey scaffoldKey = GlobalKey(); - await tester.pumpWidget(MaterialApp( - home: MediaQuery( - data: const MediaQueryData(accessibleNavigation: true), - child: Scaffold( - key: scaffoldKey, - body: Builder( - builder: (BuildContext context) { - return GestureDetector( - onTap: () { - Scaffold.of(context).showSnackBar(SnackBar( - content: const Text('snack'), - duration: const Duration(seconds: 1), - action: SnackBarAction( - label: 'ACTION', - onPressed: () { }, - ), - )); - }, - child: const Text('X'), - ); - }, - ), - ), - ), - )); - await tester.tap(find.text('X')); - await tester.pump(); - // Find action immediately - expect(find.text('ACTION'), findsOneWidget); - // Snackbar doesn't close - await tester.pump(const Duration(seconds: 10)); - expect(find.text('ACTION'), findsOneWidget); - await tester.tap(find.text('ACTION')); - await tester.pump(); - // Snackbar closes immediately - expect(find.text('ACTION'), findsNothing); - }); - - testWidgets('accessible navigation behavior with action - ScaffoldMessenger', (WidgetTester tester) async { - final GlobalKey scaffoldKey = GlobalKey(); - await tester.pumpWidget(MaterialApp( home: MediaQuery( data: const MediaQueryData(accessibleNavigation: true), @@ -1537,47 +1161,6 @@ void main() { final SemanticsHandle handle = tester.ensureSemantics(); final GlobalKey scaffoldKey = GlobalKey(); - await tester.pumpWidget(MaterialApp( - home: MediaQuery( - data: const MediaQueryData(accessibleNavigation: true), - child: Scaffold( - key: scaffoldKey, - body: Builder(builder: (BuildContext context) { - return GestureDetector( - onTap: () { - Scaffold.of(context).showSnackBar(SnackBar( - content: const Text('snack'), - duration: const Duration(seconds: 1), - action: SnackBarAction( - label: 'ACTION', - onPressed: () { }, - ), - )); - }, - child: const Text('X'), - ); - }), - ), - ), - )); - await tester.tap(find.text('X')); - await tester.pumpAndSettle(); - - expect(tester.getSemantics(find.text('snack')), matchesSemantics( - isLiveRegion: true, - hasDismissAction: true, - hasScrollDownAction: true, - hasScrollUpAction: true, - label: 'snack', - textDirection: TextDirection.ltr, - )); - handle.dispose(); - }); - - testWidgets('contributes dismiss semantics - ScaffoldMessenger', (WidgetTester tester) async { - final SemanticsHandle handle = tester.ensureSemantics(); - final GlobalKey scaffoldKey = GlobalKey(); - await tester.pumpWidget(MaterialApp( home: MediaQuery( data: const MediaQueryData(accessibleNavigation: true), @@ -1666,54 +1249,6 @@ void main() { }); testWidgets('SnackBar handles updates to accessibleNavigation', (WidgetTester tester) async { - Future boilerplate({ required bool accessibleNavigation }) { - return tester.pumpWidget(MaterialApp( - home: MediaQuery( - data: MediaQueryData(accessibleNavigation: accessibleNavigation), - child: Scaffold( - body: Builder( - builder: (BuildContext context) { - return GestureDetector( - onTap: () { - Scaffold.of(context).showSnackBar(SnackBar( - content: const Text('test'), - action: SnackBarAction(label: 'foo', onPressed: () { }), - )); - }, - behavior: HitTestBehavior.opaque, - child: const Text('X'), - ); - }, - ), - ), - ), - )); - } - - await boilerplate(accessibleNavigation: false); - expect(find.text('test'), findsNothing); - await tester.tap(find.text('X')); - await tester.pump(); // schedule animation - expect(find.text('test'), findsOneWidget); - await tester.pump(); // begin animation - await tester.pump(const Duration(milliseconds: 4750)); // 4.75s - expect(find.text('test'), findsOneWidget); - - // Enabled accessible navigation - await boilerplate(accessibleNavigation: true); - - await tester.pump(const Duration(milliseconds: 4000)); // 8.75s - await tester.pump(); - expect(find.text('test'), findsOneWidget); - - // disable accessible navigation - await boilerplate(accessibleNavigation: false); - await tester.pumpAndSettle(const Duration(milliseconds: 5750)); - - expect(find.text('test'), findsNothing); - }); - - testWidgets('SnackBar handles updates to accessibleNavigation - ScaffoldMessenger', (WidgetTester tester) async { Future boilerplate({ required bool accessibleNavigation }) { return tester.pumpWidget(MaterialApp( home: MediaQuery( @@ -2127,7 +1662,7 @@ void main() { builder: (BuildContext context) { return GestureDetector( onTap: () { - Scaffold.of(context).showSnackBar(SnackBar( + ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: const Text('Some content'), behavior: SnackBarBehavior.fixed, action: SnackBarAction( @@ -2164,7 +1699,7 @@ void main() { builder: (BuildContext context) { return GestureDetector( onTap: () { - Scaffold.of(context).showSnackBar(SnackBar( + ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: const Text('Some content'), behavior: SnackBarBehavior.floating, action: SnackBarAction( @@ -2260,39 +1795,6 @@ void main() { expect(find.text(secondHeader), findsOneWidget); }); - testWidgets('SnackBars cannot be used by the Scaffold and ScaffoldMessenger at the same time', (WidgetTester tester) async { - await tester.pumpWidget(const MaterialApp( - home: Scaffold(), - )); - - final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger)); - scaffoldMessengerState.showSnackBar(SnackBar( - content: const Text('ScaffoldMessenger'), - duration: const Duration(seconds: 2), - action: SnackBarAction(label: 'ACTION', onPressed: () {}), - behavior: SnackBarBehavior.floating, - )); - final ScaffoldState scaffoldState = tester.state(find.byType(Scaffold)); - scaffoldState.showSnackBar(SnackBar( - content: const Text('Scaffold'), - duration: const Duration(seconds: 2), - action: SnackBarAction(label: 'ACTION', onPressed: () {}), - behavior: SnackBarBehavior.floating, - )); - - final List exceptions = []; - final FlutterExceptionHandler? oldHandler = FlutterError.onError; - FlutterError.onError = (FlutterErrorDetails details) { - exceptions.add(details.exception); - }; - await tester.pump(); - FlutterError.onError = oldHandler; - - expect(exceptions.length, 1); - final AssertionError error = exceptions.first as AssertionError; - expect(error.message, contains('Only one API should be used to manage SnackBars.')); - }); - testWidgets('SnackBars should be shown above the bottomSheet', (WidgetTester tester) async { await tester.pumpWidget(const MaterialApp( home: Scaffold(