Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
64c845c5
Unverified
Commit
64c845c5
authored
Oct 05, 2020
by
Kate Lovett
Committed by
GitHub
Oct 05, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Re-land ScaffoldMessenger (#66504)
parent
7f2ca5e5
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
1568 additions
and
178 deletions
+1568
-178
app.dart
packages/flutter/lib/src/material/app.dart
+35
-21
app_bar.dart
packages/flutter/lib/src/material/app_bar.dart
+1
-1
debug.dart
packages/flutter/lib/src/material/debug.dart
+32
-1
scaffold.dart
packages/flutter/lib/src/material/scaffold.dart
+596
-38
snack_bar.dart
packages/flutter/lib/src/material/snack_bar.dart
+17
-15
animated_list.dart
packages/flutter/lib/src/widgets/animated_list.dart
+3
-1
framework.dart
packages/flutter/lib/src/widgets/framework.dart
+40
-21
debug_test.dart
packages/flutter/test/material/debug_test.dart
+81
-0
floating_action_button_location_test.dart
...r/test/material/floating_action_button_location_test.dart
+1
-1
scaffold_test.dart
packages/flutter/test/material/scaffold_test.dart
+111
-0
snack_bar_test.dart
packages/flutter/test/material/snack_bar_test.dart
+646
-74
snack_bar_theme_test.dart
packages/flutter/test/material/snack_bar_theme_test.dart
+5
-5
No files found.
packages/flutter/lib/src/material/app.dart
View file @
64c845c5
...
...
@@ -15,6 +15,7 @@ import 'floating_action_button.dart';
import
'icons.dart'
;
import
'material_localizations.dart'
;
import
'page.dart'
;
import
'scaffold.dart'
show
ScaffoldMessenger
,
ScaffoldMessengerState
;
import
'theme.dart'
;
/// [MaterialApp] uses this [TextStyle] as its [DefaultTextStyle] to encourage
...
...
@@ -166,6 +167,7 @@ class MaterialApp extends StatefulWidget {
const
MaterialApp
({
Key
?
key
,
this
.
navigatorKey
,
this
.
scaffoldMessengerKey
,
this
.
home
,
this
.
routes
=
const
<
String
,
WidgetBuilder
>{},
this
.
initialRoute
,
...
...
@@ -214,6 +216,7 @@ class MaterialApp extends StatefulWidget {
/// Creates a [MaterialApp] that uses the [Router] instead of a [Navigator].
const
MaterialApp
.
router
({
Key
?
key
,
this
.
scaffoldMessengerKey
,
this
.
routeInformationProvider
,
required
this
.
routeInformationParser
,
required
this
.
routerDelegate
,
...
...
@@ -263,6 +266,14 @@ class MaterialApp extends StatefulWidget {
/// {@macro flutter.widgets.widgetsApp.navigatorKey}
final
GlobalKey
<
NavigatorState
>?
navigatorKey
;
/// A key to use when building the [ScaffoldMessenger].
///
/// If a [scaffoldMessengerKey] is specified, the [ScaffoldMessenger] can be
/// directly manipulated without first obtaining it from a [BuildContext] via
/// [ScaffoldMessenger.of]: from the [scaffoldMessengerKey], use the
/// [GlobalKey.currentState] getter.
final
GlobalKey
<
ScaffoldMessengerState
>?
scaffoldMessengerKey
;
/// {@macro flutter.widgets.widgetsApp.home}
final
Widget
?
home
;
...
...
@@ -724,27 +735,30 @@ class _MaterialAppState extends State<MaterialApp> {
}
theme
??=
widget
.
theme
??
ThemeData
.
light
();
return
AnimatedTheme
(
data:
theme
,
isMaterialAppTheme:
true
,
child:
widget
.
builder
!=
null
?
Builder
(
builder:
(
BuildContext
context
)
{
// Why are we surrounding a builder with a builder?
//
// The widget.builder may contain code that invokes
// Theme.of(), which should return the theme we selected
// above in AnimatedTheme. However, if we invoke
// widget.builder() directly as the child of AnimatedTheme
// then there is no Context separating them, and the
// widget.builder() will not find the theme. Therefore, we
// surround widget.builder with yet another builder so that
// a context separates them and Theme.of() correctly
// resolves to the theme we passed to AnimatedTheme.
return
widget
.
builder
!(
context
,
child
);
},
)
:
child
!,
return
ScaffoldMessenger
(
key:
widget
.
scaffoldMessengerKey
,
child:
AnimatedTheme
(
data:
theme
,
isMaterialAppTheme:
true
,
child:
widget
.
builder
!=
null
?
Builder
(
builder:
(
BuildContext
context
)
{
// Why are we surrounding a builder with a builder?
//
// The widget.builder may contain code that invokes
// Theme.of(), which should return the theme we selected
// above in AnimatedTheme. However, if we invoke
// widget.builder() directly as the child of AnimatedTheme
// then there is no Context separating them, and the
// widget.builder() will not find the theme. Therefore, we
// surround widget.builder with yet another builder so that
// a context separates them and Theme.of() correctly
// resolves to the theme we passed to AnimatedTheme.
return
widget
.
builder
!(
context
,
child
);
},
)
:
child
!,
)
);
}
...
...
packages/flutter/lib/src/material/app_bar.dart
View file @
64c845c5
...
...
@@ -135,7 +135,7 @@ class _ToolbarContainerLayout extends SingleChildLayoutDelegate {
/// icon: const Icon(Icons.add_alert),
/// tooltip: 'Show Snackbar',
/// onPressed: () {
///
scaffoldKey.currentState
.showSnackBar(snackBar);
///
ScaffoldMessenger.of(context)
.showSnackBar(snackBar);
/// },
/// ),
/// IconButton(
...
...
packages/flutter/lib/src/material/debug.dart
View file @
64c845c5
...
...
@@ -7,7 +7,7 @@ import 'package:flutter/widgets.dart';
import
'material.dart'
;
import
'material_localizations.dart'
;
import
'scaffold.dart'
show
Scaffold
;
import
'scaffold.dart'
show
Scaffold
,
ScaffoldMessenger
;
/// Asserts that the given context has a [Material] ancestor.
///
...
...
@@ -123,3 +123,34 @@ bool debugCheckHasScaffold(BuildContext context) {
}());
return
true
;
}
/// Asserts that the given context has a [ScaffoldMessenger] ancestor.
///
/// Used by various widgets to make sure that they are only used in an
/// appropriate context.
///
/// To invoke this function, use the following pattern, typically in the
/// relevant Widget's build method:
///
/// ```dart
/// assert(debugCheckHasScaffoldMessenger(context));
/// ```
///
/// Does nothing if asserts are disabled. Always returns true.
bool
debugCheckHasScaffoldMessenger
(
BuildContext
context
)
{
assert
(()
{
if
(
context
.
findAncestorWidgetOfExactType
<
ScaffoldMessenger
>()
==
null
)
{
throw
FlutterError
.
fromParts
(<
DiagnosticsNode
>[
ErrorSummary
(
'No ScaffoldMessenger widget found.'
),
ErrorDescription
(
'
${context.widget.runtimeType}
widgets require a ScaffoldMessenger widget ancestor.'
),
...
context
.
describeMissingAncestor
(
expectedAncestorType:
ScaffoldMessenger
),
ErrorHint
(
'Typically, the ScaffoldMessenger widget is introduced by the MaterialApp '
'at the top of your application widget tree.'
)
]);
}
return
true
;
}());
return
true
;
}
packages/flutter/lib/src/material/scaffold.dart
View file @
64c845c5
...
...
@@ -17,6 +17,7 @@ import 'bottom_sheet.dart';
import
'button_bar.dart'
;
import
'colors.dart'
;
import
'curves.dart'
;
import
'debug.dart'
;
import
'divider.dart'
;
import
'drawer.dart'
;
import
'flexible_space_bar.dart'
;
...
...
@@ -59,6 +60,402 @@ enum _ScaffoldSlot {
statusBar
,
}
/// Manages [SnackBar]s for descendant [Scaffold]s.
///
/// This class provides APIs for showing snack bars.
///
/// To display a snack bar, obtain the [ScaffoldMessengerState] for the current
/// [BuildContext] via [ScaffoldMessenger.of] and use the
/// [ScaffoldMessengerState.showSnackBar] function.
///
/// {@tool dartpad --template=stateless_widget_scaffold_center}
///
/// Here is an example of showing a [SnackBar] when the user presses a button.
///
/// ```dart
/// Widget build(BuildContext context) {
/// return OutlinedButton(
/// onPressed: () {
/// ScaffoldMessenger.of(context).showSnackBar(
/// const SnackBar(
/// content: Text('A SnackBar has been shown.'),
/// ),
/// );
/// },
/// child: const Text('Show SnackBar'),
/// );
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [SnackBar], which is a temporary notification typically shown near the
/// bottom of the app using the [ScaffoldMessengerState.showSnackBar] method.
/// * [debugCheckHasScaffoldMessenger], which asserts that the given context
/// has a [ScaffoldMessenger] ancestor.
/// * Cookbook: [Display a SnackBar](https://flutter.dev/docs/cookbook/design/snackbars)
class
ScaffoldMessenger
extends
StatefulWidget
{
/// Creates a widget that manages [SnackBar]s for [Scaffold] descendants.
const
ScaffoldMessenger
({
Key
?
key
,
required
this
.
child
,
})
:
assert
(
child
!=
null
),
super
(
key:
key
);
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.child}
final
Widget
child
;
/// The state from the closest instance of this class that encloses the given
/// context.
///
/// {@tool dartpad --template=stateless_widget_scaffold_center}
/// Typical usage of the [ScaffoldMessenger.of] function is to call it in
/// response to a user gesture or an application state change.
///
/// ```dart
/// Widget build(BuildContext context) {
/// return ElevatedButton(
/// child: const Text('SHOW A SNACKBAR'),
/// onPressed: () {
/// ScaffoldMessenger.of(context).showSnackBar(
/// const SnackBar(
/// content: Text('Have a snack!'),
/// ),
/// );
/// },
/// );
/// }
/// ```
/// {@end-tool}
///
/// A less elegant but more expedient solution is to assign a [GlobalKey] to the
/// [ScaffoldMessenger], then use the `key.currentState` property to obtain the
/// [ScaffoldMessengerState] rather than using the [ScaffoldMessenger.of]
/// function. The [MaterialApp.scaffoldMessengerKey] refers to the root
/// ScaffoldMessenger that is provided by default.
///
/// {@tool dartpad --template=freeform}
/// Sometimes [SnackBar]s are produced by code that doesn't have ready access
/// to a valid [BuildContext]. One such example of this is when you show a
/// SnackBar from a method outside of the `build` function. In these
/// cases, you can assign a [GlobalKey] to the [ScaffoldMessenger]. This
/// example shows a key being used to obtain the [ScaffoldMessengerState]
/// provided by the [MaterialApp].
///
/// ```dart imports
/// import 'package:flutter/material.dart';
/// ```
/// ```dart
/// void main() => runApp(MyApp());
///
/// class MyApp extends StatefulWidget {
/// @override
/// _MyAppState createState() => _MyAppState();
/// }
///
/// class _MyAppState extends State<MyApp> {
/// final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
/// int _counter = 0;
///
/// void _incrementCounter() {
/// setState(() {
/// _counter++;
/// });
/// if (_counter % 10 == 0) {
/// _scaffoldMessengerKey.currentState.showSnackBar(const SnackBar(
/// content: Text('A multiple of ten!'),
/// ));
/// }
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return MaterialApp(
/// scaffoldMessengerKey: _scaffoldMessengerKey,
/// home: Scaffold(
/// appBar: AppBar(title: Text('ScaffoldMessenger Demo')),
/// body: Center(
/// child: Column(
/// mainAxisAlignment: MainAxisAlignment.center,
/// children: <Widget>[
/// Text(
/// 'You have pushed the button this many times:',
/// ),
/// Text(
/// '$_counter',
/// style: Theme.of(context).textTheme.headline4,
/// ),
/// ],
/// ),
/// ),
/// floatingActionButton: FloatingActionButton(
/// onPressed: _incrementCounter,
/// tooltip: 'Increment',
/// child: Icon(Icons.add),
/// ),
/// ),
/// );
/// }
/// }
///
/// ```
/// {@end-tool}
///
/// If there is no [ScaffoldMessenger] in scope, then this will return null.
/// See also:
///
/// * [debugCheckHasScaffoldMessenger], which asserts that the given context
/// has a [ScaffoldMessenger] ancestor.
static
ScaffoldMessengerState
?
of
(
BuildContext
context
,
{
bool
nullOk
=
false
})
{
assert
(
nullOk
!=
null
);
assert
(
context
!=
null
);
assert
(
nullOk
||
debugCheckHasScaffoldMessenger
(
context
));
final
_ScaffoldMessengerScope
?
scope
=
context
.
dependOnInheritedWidgetOfExactType
<
_ScaffoldMessengerScope
>();
return
scope
?.
_scaffoldMessengerState
;
}
@override
ScaffoldMessengerState
createState
()
=>
ScaffoldMessengerState
();
}
/// State for a [ScaffoldMessenger].
///
/// A [ScaffoldMessengerState] object can be used to [showSnackBar] for every
/// registered [Scaffold] that is a descendant of the associated
/// [ScaffoldMessenger]. Scaffolds will register to receive [SnackBar]s from
/// their closest ScaffoldMessenger ancestor.
///
/// Typically obtained via [ScaffoldMessenger.of].
class
ScaffoldMessengerState
extends
State
<
ScaffoldMessenger
>
with
TickerProviderStateMixin
{
final
LinkedHashSet
<
ScaffoldState
>
_scaffolds
=
LinkedHashSet
<
ScaffoldState
>();
final
Queue
<
ScaffoldFeatureController
<
SnackBar
,
SnackBarClosedReason
>>
_snackBars
=
Queue
<
ScaffoldFeatureController
<
SnackBar
,
SnackBarClosedReason
>>();
AnimationController
?
_snackBarController
;
Timer
?
_snackBarTimer
;
bool
?
_accessibleNavigation
;
@override
void
didChangeDependencies
()
{
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
==
true
&&
!
mediaQuery
.
accessibleNavigation
&&
_snackBarTimer
!=
null
&&
!
_snackBarTimer
!.
isActive
)
{
hideCurrentSnackBar
(
reason:
SnackBarClosedReason
.
timeout
);
}
_accessibleNavigation
=
mediaQuery
.
accessibleNavigation
;
super
.
didChangeDependencies
();
}
void
_register
(
ScaffoldState
scaffold
)
{
_scaffolds
.
add
(
scaffold
);
if
(
_snackBars
.
isNotEmpty
)
{
scaffold
.
_updateSnackBar
();
}
}
void
_unregister
(
ScaffoldState
scaffold
)
{
final
bool
removed
=
_scaffolds
.
remove
(
scaffold
);
// ScaffoldStates should only be removed once.
assert
(
removed
);
}
/// Shows a [SnackBar] across all registered [Scaffold]s.
///
/// 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 [hideCurrentSnackBar]
/// or call [ScaffoldFeatureController.close] on the returned
/// [ScaffoldFeatureController]. To remove a [SnackBar] suddenly (without an
/// animation), use [removeCurrentSnackBar].
///
/// See [ScaffoldMessenger.of] for information about how to obtain the
/// [ScaffoldMessengerState].
///
/// {@tool dartpad --template=stateless_widget_scaffold_center}
///
/// Here is an example of showing a [SnackBar] when the user presses a button.
///
/// ```dart
/// Widget build(BuildContext context) {
/// return OutlinedButton(
/// onPressed: () {
/// ScaffoldMessenger.of(context).showSnackBar(
/// const SnackBar(
/// content: Text('A SnackBar has been shown.'),
/// ),
/// );
/// },
/// child: const Text('Show SnackBar'),
/// );
/// }
/// ```
/// {@end-tool}
ScaffoldFeatureController
<
SnackBar
,
SnackBarClosedReason
>
showSnackBar
(
SnackBar
snackBar
)
{
_snackBarController
??=
SnackBar
.
createAnimationController
(
vsync:
this
)
..
addStatusListener
(
_handleStatusChanged
);
if
(
_snackBars
.
isEmpty
)
{
assert
(
_snackBarController
!.
isDismissed
);
_snackBarController
!.
forward
();
}
late
ScaffoldFeatureController
<
SnackBar
,
SnackBarClosedReason
>
controller
;
controller
=
ScaffoldFeatureController
<
SnackBar
,
SnackBarClosedReason
>.
_
(
// 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
<
SnackBarClosedReason
>(),
()
{
assert
(
_snackBars
.
first
==
controller
);
hideCurrentSnackBar
(
reason:
SnackBarClosedReason
.
hide
);
},
null
,
// SnackBar doesn't use a builder function so setState() wouldn't rebuild it
);
setState
(()
{
_snackBars
.
addLast
(
controller
);
});
_updateScaffolds
();
return
controller
;
}
void
_handleStatusChanged
(
AnimationStatus
status
)
{
switch
(
status
)
{
case
AnimationStatus
.
dismissed
:
assert
(
_snackBars
.
isNotEmpty
);
setState
(()
{
_snackBars
.
removeFirst
();
});
_updateScaffolds
();
if
(
_snackBars
.
isNotEmpty
)
{
_snackBarController
!.
forward
();
}
break
;
case
AnimationStatus
.
completed
:
setState
(()
{
assert
(
_snackBarTimer
==
null
);
// build will create a new timer if necessary to dismiss the snackBar.
});
_updateScaffolds
();
break
;
case
AnimationStatus
.
forward
:
break
;
case
AnimationStatus
.
reverse
:
break
;
}
}
void
_updateScaffolds
()
{
for
(
final
ScaffoldState
scaffold
in
_scaffolds
)
{
scaffold
.
_updateSnackBar
();
}
}
/// Removes the current [SnackBar] (if any) immediately from registered
/// [Scaffold]s.
///
/// The removed snack bar does not run its normal exit animation. If there are
/// any queued snack bars, they begin their entrance animation immediately.
void
removeCurrentSnackBar
({
SnackBarClosedReason
reason
=
SnackBarClosedReason
.
remove
})
{
assert
(
reason
!=
null
);
if
(
_snackBars
.
isEmpty
)
return
;
final
Completer
<
SnackBarClosedReason
>
completer
=
_snackBars
.
first
.
_completer
;
if
(!
completer
.
isCompleted
)
completer
.
complete
(
reason
);
_snackBarTimer
?.
cancel
();
_snackBarTimer
=
null
;
// This will trigger the animation's status callback.
_snackBarController
!.
value
=
0.0
;
}
/// Removes the current [SnackBar] by running its normal exit animation.
///
/// The closed completer is called after the animation is complete.
void
hideCurrentSnackBar
({
SnackBarClosedReason
reason
=
SnackBarClosedReason
.
hide
})
{
assert
(
reason
!=
null
);
if
(
_snackBars
.
isEmpty
||
_snackBarController
!.
status
==
AnimationStatus
.
dismissed
)
return
;
final
Completer
<
SnackBarClosedReason
>
completer
=
_snackBars
.
first
.
_completer
;
if
(
_accessibleNavigation
!)
{
_snackBarController
!.
value
=
0.0
;
completer
.
complete
(
reason
);
}
else
{
_snackBarController
!.
reverse
().
then
<
void
>((
void
value
)
{
assert
(
mounted
);
if
(!
completer
.
isCompleted
)
completer
.
complete
(
reason
);
});
}
_snackBarTimer
?.
cancel
();
_snackBarTimer
=
null
;
}
@override
Widget
build
(
BuildContext
context
)
{
assert
(
debugCheckHasMediaQuery
(
context
));
final
MediaQueryData
mediaQuery
=
MediaQuery
.
of
(
context
)!;
_accessibleNavigation
=
mediaQuery
.
accessibleNavigation
;
if
(
_snackBars
.
isNotEmpty
)
{
final
ModalRoute
<
dynamic
>?
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
);
});
}
}
}
return
_ScaffoldMessengerScope
(
scaffoldMessengerState:
this
,
child:
widget
.
child
,
);
}
@override
void
dispose
()
{
_snackBarController
?.
dispose
();
_snackBarTimer
?.
cancel
();
_snackBarTimer
=
null
;
super
.
dispose
();
}
}
class
_ScaffoldMessengerScope
extends
InheritedWidget
{
const
_ScaffoldMessengerScope
({
Key
?
key
,
required
Widget
child
,
required
ScaffoldMessengerState
scaffoldMessengerState
,
})
:
_scaffoldMessengerState
=
scaffoldMessengerState
,
super
(
key:
key
,
child:
child
);
final
ScaffoldMessengerState
_scaffoldMessengerState
;
@override
bool
updateShouldNotify
(
_ScaffoldMessengerScope
old
)
=>
_scaffoldMessengerState
!=
old
.
_scaffoldMessengerState
;
}
/// The geometry of the [Scaffold] after all its contents have been laid out
/// except the [FloatingActionButton].
///
...
...
@@ -835,11 +1232,11 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr
/// Implements the basic material design visual layout structure.
///
/// This class provides APIs for showing drawers
, snack bars,
and bottom sheets.
/// This class provides APIs for showing drawers and bottom sheets.
///
/// To display a
snackbar or a
persistent bottom sheet, obtain the
/// To display a persistent bottom sheet, obtain the
/// [ScaffoldState] for the current [BuildContext] via [Scaffold.of] and use the
/// [ScaffoldState.show
SnackBar] and [ScaffoldState.showBottomSheet] functions
.
/// [ScaffoldState.show
BottomSheet] function
.
///
/// {@tool dartpad --template=stateful_widget_material}
/// This example shows a [Scaffold] with a [body] and [FloatingActionButton].
...
...
@@ -916,7 +1313,7 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr
/// Widget build(BuildContext context) {
/// return Scaffold(
/// appBar: AppBar(
/// title: Text('Sample Code'),
/// title:
const
Text('Sample Code'),
/// ),
/// body: Center(
/// child: Text('You have pressed the button $_count times.'),
...
...
@@ -1008,8 +1405,6 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr
/// * [BottomNavigationBar], which is a horizontal array of buttons typically
/// shown along the bottom of the app using the [bottomNavigationBar]
/// property.
/// * [SnackBar], which is a temporary notification typically shown near the
/// bottom of the app using the [ScaffoldState.showSnackBar] method.
/// * [BottomSheet], which is an overlay typically shown near the bottom of the
/// app. A bottom sheet can either be persistent, in which case it is shown
/// using the [ScaffoldState.showBottomSheet] method, or modal, in which case
...
...
@@ -1017,7 +1412,6 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr
/// * [ScaffoldState], which is the state associated with this widget.
/// * <https://material.io/design/layout/responsive-layout-grid.html>
/// * Cookbook: [Add a Drawer to a screen](https://flutter.dev/docs/cookbook/design/drawer)
/// * Cookbook: [Display a snackbar](https://flutter.dev/docs/cookbook/design/snackbars)
/// * See our
/// [Scaffold Sample Apps](https://flutter.dev/docs/catalog/samples/Scaffold).
class
Scaffold
extends
StatefulWidget
{
...
...
@@ -1389,7 +1783,7 @@ class Scaffold extends StatefulWidget {
/// ),
/// home: Scaffold(
/// body: MyScaffoldBody(),
/// appBar: AppBar(title: Text('Scaffold.of Example')),
/// appBar: AppBar(title:
const
Text('Scaffold.of Example')),
/// ),
/// color: Colors.white,
/// );
...
...
@@ -1403,12 +1797,30 @@ class Scaffold extends StatefulWidget {
/// Widget build(BuildContext context) {
/// return Center(
/// child: ElevatedButton(
/// child:
Text('SHOW A SNACKBAR
'),
/// child:
const Text('SHOW BOTTOM SHEET
'),
/// onPressed: () {
/// Scaffold.of(context).showSnackBar(
/// SnackBar(
/// content: Text('Have a snack!'),
/// ),
/// Scaffold.of(context).showBottomSheet<void>(
/// (BuildContext context) {
/// return Container(
/// alignment: Alignment.center,
/// height: 200,
/// color: Colors.amber,
/// child: Center(
/// child: Column(
/// mainAxisSize: MainAxisSize.min,
/// children: <Widget>[
/// const Text('BottomSheet'),
/// ElevatedButton(
/// child: const Text('Close BottomSheet'),
/// onPressed: () {
/// Navigator.pop(context);
/// },
/// )
/// ],
/// ),
/// ),
/// );
/// },
/// );
/// },
/// ),
...
...
@@ -1429,20 +1841,38 @@ class Scaffold extends StatefulWidget {
/// ```dart
/// Widget build(BuildContext context) {
/// return Scaffold(
/// appBar: AppBar(
/// title: Text('Demo')
/// ),
/// appBar: AppBar(title: const Text('Demo')),
/// body: Builder(
/// // Create an inner BuildContext so that the onPressed methods
/// // can refer to the Scaffold with Scaffold.of().
/// builder: (BuildContext context) {
/// return Center(
/// child: ElevatedButton(
/// child:
Text('SHOW A SNACKBAR
'),
/// child:
const Text('SHOW BOTTOM SHEET
'),
/// onPressed: () {
/// Scaffold.of(context).showSnackBar(SnackBar(
/// content: Text('Have a snack!'),
/// ));
/// Scaffold.of(context).showBottomSheet<void>(
/// (BuildContext context) {
/// return Container(
/// alignment: Alignment.center,
/// height: 200,
/// color: Colors.amber,
/// child: Center(
/// child: Column(
/// mainAxisSize: MainAxisSize.min,
/// children: <Widget>[
/// const Text('BottomSheet'),
/// ElevatedButton(
/// child: const Text('Close BottomSheet'),
/// onPressed: () {
/// Navigator.pop(context);
/// },
/// )
/// ],
/// ),
/// ),
/// );
/// },
/// );
/// },
/// ),
/// );
...
...
@@ -1559,7 +1989,7 @@ class Scaffold extends StatefulWidget {
/// See also:
///
/// * [Scaffold.of], which provides access to the [ScaffoldState] object as a
/// whole, from which you can show
snackbars,
bottom sheets, and so forth.
/// whole, from which you can show bottom sheets, and so forth.
static
bool
hasDrawer
(
BuildContext
context
,
{
bool
registerForUpdates
=
true
})
{
assert
(
registerForUpdates
!=
null
);
assert
(
context
!=
null
);
...
...
@@ -1578,8 +2008,8 @@ class Scaffold extends StatefulWidget {
/// State for a [Scaffold].
///
/// Can display [
SnackBar]s and [BottomSheet]s. Retrieve a [ScaffoldState] from
///
the current
[BuildContext] using [Scaffold.of].
/// Can display [
BottomSheet]s. Retrieve a [ScaffoldState] from the current
/// [BuildContext] using [Scaffold.of].
class
ScaffoldState
extends
State
<
Scaffold
>
with
TickerProviderStateMixin
{
// DRAWER API
...
...
@@ -1669,13 +2099,15 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
}
// SNACKBAR API
final
Queue
<
ScaffoldFeatureController
<
SnackBar
,
SnackBarClosedReason
>>
_snackBars
=
Queue
<
ScaffoldFeatureController
<
SnackBar
,
SnackBarClosedReason
>>();
AnimationController
?
_snackBarController
;
Timer
?
_snackBarTimer
;
bool
?
_accessibleNavigation
;
ScaffoldMessengerState
?
_scaffoldMessenger
;
/// Shows a [SnackBar] at the bottom of the scaffold.
/// [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
...
...
@@ -1684,12 +2116,14 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
///
/// To control how long a [SnackBar] remains visible, use [SnackBar.duration].
///
/// To remove the [SnackBar] with an exit animation, use [hideCurrentSnackBar]
/// or call [ScaffoldFeatureController.close] on the returned
/// [ScaffoldFeatureController]. To remove a [SnackBar] suddenly (without an
/// animation), use [removeCurrentSnackBar].
/// 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 [Scaffold.of] for information about how to obtain the [ScaffoldState].
/// See [ScaffoldMessenger.of] for information about how to obtain the
/// [ScaffoldMessengerState].
///
/// {@tool dartpad --template=stateless_widget_scaffold_center}
///
...
...
@@ -1699,17 +2133,22 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
/// Widget build(BuildContext context) {
/// return OutlinedButton(
/// onPressed: () {
/// Scaffold.of(context).showSnackBar(
/// Scaffold
Messenger
.of(context).showSnackBar(
/// SnackBar(
/// content: Text('A SnackBar has been shown.'),
/// content:
const
Text('A SnackBar has been shown.'),
/// ),
/// );
/// },
/// child: Text('Show SnackBar'),
/// child:
const
Text('Show SnackBar'),
/// );
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
// TODO(Piinks): Deprecate & defer to ScaffoldMessenger after customers are migrated.
ScaffoldFeatureController
<
SnackBar
,
SnackBarClosedReason
>
showSnackBar
(
SnackBar
snackbar
)
{
_snackBarController
??=
SnackBar
.
createAnimationController
(
vsync:
this
)
..
addStatusListener
(
_handleSnackBarStatusChange
);
...
...
@@ -1758,12 +2197,39 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
}
}
/// Removes the current [SnackBar] (if any) immediately.
/// [ScaffoldMessengerState.removeCurrentSnackBar] removes the current
/// [SnackBar] (if any) immediately. This method should not be used, and will
/// be depracted 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.
// TODO(Piinks): Deprecate & defer to ScaffoldMessenger after customers are migrated
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
<
SnackBarClosedReason
>
completer
=
_snackBars
.
first
.
_completer
;
...
...
@@ -1774,11 +2240,38 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
_snackBarController
!.
value
=
0.0
;
}
/// Removes the current [SnackBar] by running its normal exit animation.
/// [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.
// TODO(Piinks): Deprecate & defer to ScaffoldMessenger after customers are migrated.
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
)!;
...
...
@@ -1797,6 +2290,18 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
_snackBarTimer
=
null
;
}
// The _messengerSnackBar represents the current SnackBar being managed by
// the ScaffoldMessenger, instead of the Scaffold.
ScaffoldFeatureController
<
SnackBar
,
SnackBarClosedReason
>?
_messengerSnackBar
;
// This is used to update the _messengerSnackBar by the ScaffoldMessenger.
void
_updateSnackBar
()
{
setState
(()
{
_messengerSnackBar
=
_scaffoldMessenger
!.
_snackBars
.
isNotEmpty
?
_scaffoldMessenger
!.
_snackBars
.
first
:
null
;
});
}
// PERSISTENT BOTTOM SHEET API
...
...
@@ -2010,7 +2515,9 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
/// const Text('BottomSheet'),
/// ElevatedButton(
/// child: const Text('Close BottomSheet'),
/// onPressed: () => Navigator.pop(context),
/// onPressed: () {
/// Navigator.pop(context);
/// }
/// )
/// ],
/// ),
...
...
@@ -2198,6 +2705,19 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
@override
void
didChangeDependencies
()
{
// nullOk is valid here since both the Scaffold and ScaffoldMessenger are
// currently available for managing SnackBars.
final
ScaffoldMessengerState
?
_currentScaffoldMessenger
=
ScaffoldMessenger
.
of
(
context
,
nullOk:
true
);
// If our ScaffoldMessenger has changed, unregister with the old one first.
if
(
_scaffoldMessenger
!=
null
&&
(
_currentScaffoldMessenger
==
null
||
_scaffoldMessenger
!=
_currentScaffoldMessenger
))
{
_scaffoldMessenger
?.
_unregister
(
this
);
}
// Register with the current ScaffoldMessenger, if there is one.
_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
...
...
@@ -2210,15 +2730,18 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
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
();
for
(
final
_StandardBottomSheet
bottomSheet
in
_dismissedBottomSheets
)
{
bottomSheet
.
animationController
.
dispose
();
...
...
@@ -2228,6 +2751,7 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
}
_floatingActionButtonMoveController
.
dispose
();
_floatingActionButtonVisibilityController
.
dispose
();
_scaffoldMessenger
?.
_unregister
(
this
);
super
.
dispose
();
}
...
...
@@ -2341,8 +2865,9 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
final
MediaQueryData
mediaQuery
=
MediaQuery
.
of
(
context
)!;
final
ThemeData
themeData
=
Theme
.
of
(
context
)!;
final
TextDirection
textDirection
=
Directionality
.
of
(
context
)!;
_accessibleNavigation
=
mediaQuery
.
accessibleNavigation
;
// TODO(Piinks): Remove old SnackBar API after migrating ScaffoldMessenger
_accessibleNavigation
=
mediaQuery
.
accessibleNavigation
;
if
(
_snackBars
.
isNotEmpty
)
{
final
ModalRoute
<
dynamic
>?
route
=
ModalRoute
.
of
(
context
);
if
(
route
==
null
||
route
.
isCurrent
)
{
...
...
@@ -2417,6 +2942,38 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
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.'
);
// SnackBar set by ScaffoldMessenger
if
(
_messengerSnackBar
!=
null
)
{
final
SnackBarBehavior
snackBarBehavior
=
_messengerSnackBar
?.
_widget
.
behavior
??
themeData
.
snackBarTheme
.
behavior
??
SnackBarBehavior
.
fixed
;
isSnackBarFloating
=
snackBarBehavior
==
SnackBarBehavior
.
floating
;
snackBarWidth
=
_messengerSnackBar
?.
_widget
.
width
;
_addIfNonNull
(
children
,
_messengerSnackBar
?.
_widget
,
_ScaffoldSlot
.
snackBar
,
removeLeftPadding:
false
,
removeTopPadding:
true
,
removeRightPadding:
false
,
removeBottomPadding:
widget
.
bottomNavigationBar
!=
null
||
widget
.
persistentFooterButtons
!=
null
,
maintainBottomViewPadding:
!
_resizeToAvoidBottomInset
,
);
}
// 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
...
...
@@ -2590,7 +3147,8 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
/// An interface for controlling a feature of a [Scaffold].
///
/// Commonly obtained from [ScaffoldState.showSnackBar] or [ScaffoldState.showBottomSheet].
/// Commonly obtained from [ScaffoldMessengerState.showSnackBar] or
/// [ScaffoldState.showBottomSheet].
class
ScaffoldFeatureController
<
T
extends
Widget
,
U
>
{
const
ScaffoldFeatureController
.
_
(
this
.
_widget
,
this
.
_completer
,
this
.
close
,
this
.
setState
);
final
T
_widget
;
...
...
packages/flutter/lib/src/material/snack_bar.dart
View file @
64c845c5
...
...
@@ -32,7 +32,7 @@ const Curve _snackBarFadeOutCurve = Interval(0.72, 1.0, curve: Curves.fastOutSlo
/// Specify how a [SnackBar] was closed.
///
/// The [ScaffoldState.showSnackBar] function returns a
/// The [Scaffold
Messenger
State.showSnackBar] function returns a
/// [ScaffoldFeatureController]. The value of the controller's closed property
/// is a Future that resolves to a SnackBarClosedReason. Applications that need
/// to know how a snackbar was closed can use this value.
...
...
@@ -40,7 +40,7 @@ const Curve _snackBarFadeOutCurve = Interval(0.72, 1.0, curve: Curves.fastOutSlo
/// Example:
///
/// ```dart
/// Scaffold.of(context).showSnackBar(
/// Scaffold
Messenger
.of(context).showSnackBar(
/// SnackBar( ... )
/// ).closed.then((SnackBarClosedReason reason) {
/// ...
...
...
@@ -57,10 +57,10 @@ enum SnackBarClosedReason {
swipe
,
/// The snack bar was closed by the [ScaffoldFeatureController] close callback
/// or by calling [ScaffoldState.hideCurrentSnackBar] directly.
/// or by calling [Scaffold
Messenger
State.hideCurrentSnackBar] directly.
hide
,
/// The snack bar was closed by an call to [ScaffoldState.removeCurrentSnackBar].
/// The snack bar was closed by an call to [Scaffold
Messenger
State.removeCurrentSnackBar].
remove
,
/// The snack bar was closed because its timer expired.
...
...
@@ -150,8 +150,8 @@ class _SnackBarActionState extends State<SnackBarAction> {
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=zpO6n_oZWw0}
///
/// To display a snack bar, call `Scaffold
.of(context).showSnackBar()`, passing
/// an instance of [SnackBar] that describes the message.
/// To display a snack bar, call `Scaffold
Messenger.of(context).showSnackBar()`,
///
passing
an instance of [SnackBar] that describes the message.
///
/// To control how long the [SnackBar] remains visible, specify a [duration].
///
...
...
@@ -160,11 +160,11 @@ class _SnackBarActionState extends State<SnackBarAction> {
///
/// See also:
///
/// * [Scaffold
.of], to obtain the current [ScaffoldState], which manages the
/// display and animation of snack bars.
/// * [ScaffoldState.showSnackBar], which displays a [SnackBar].
/// * [Scaffold
State.removeCurrentSnackBar], which abruptly hides the currently
/// displayed snack bar, if any, and allows the next to be displayed.
/// * [Scaffold
Messenger.of], to obtain the current [ScaffoldMessengerState],
///
which manages the
display and animation of snack bars.
/// * [Scaffold
Messenger
State.showSnackBar], which displays a [SnackBar].
/// * [Scaffold
MessengerState.removeCurrentSnackBar], which abruptly hides the
///
currently
displayed snack bar, if any, and allows the next to be displayed.
/// * [SnackBarAction], which is used to specify an [action] button to show
/// on the snack bar.
/// * [SnackBarThemeData], to configure the default property values for
...
...
@@ -293,7 +293,7 @@ class SnackBar extends StatefulWidget {
///
/// See also:
///
/// * [ScaffoldState.removeCurrentSnackBar], which abruptly hides the
/// * [Scaffold
Messenger
State.removeCurrentSnackBar], which abruptly hides the
/// currently displayed snack bar, if any, and allows the next to be
/// displayed.
/// * <https://material.io/design/components/snackbars.html>
...
...
@@ -305,7 +305,7 @@ class SnackBar extends StatefulWidget {
/// Called the first time that the snackbar is visible within a [Scaffold].
final
VoidCallback
?
onVisible
;
// API for Scaffold.showSnackBar():
// API for Scaffold
MessengerState
.showSnackBar():
/// Creates an animation controller useful for driving a snack bar's entrance and exit animation.
static
AnimationController
createAnimationController
({
required
TickerProvider
vsync
})
{
...
...
@@ -342,7 +342,6 @@ class SnackBar extends StatefulWidget {
State
<
SnackBar
>
createState
()
=>
_SnackBarState
();
}
class
_SnackBarState
extends
State
<
SnackBar
>
{
bool
_wasVisible
=
false
;
...
...
@@ -560,6 +559,9 @@ class _SnackBarState extends State<SnackBar> {
);
}
return
ClipRect
(
child:
snackBarTransition
);
return
Hero
(
child:
ClipRect
(
child:
snackBarTransition
),
tag:
'<SnackBar Hero tag -
${widget.content}
>'
,
);
}
}
packages/flutter/lib/src/widgets/animated_list.dart
View file @
64c845c5
...
...
@@ -510,6 +510,7 @@ class AnimatedListState extends State<AnimatedList> with TickerProviderStateMixi
/// class _SliverAnimatedListSampleState extends State<SliverAnimatedListSample> {
/// final GlobalKey<SliverAnimatedListState> _listKey = GlobalKey<SliverAnimatedListState>();
/// final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
/// final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
/// ListModel<int> _list;
/// int _selectedItem;
/// int _nextItem; // The next item inserted when the user presses the '+' button.
...
...
@@ -567,7 +568,7 @@ class AnimatedListState extends State<AnimatedList> with TickerProviderStateMixi
/// _selectedItem = null;
/// });
/// } else {
/// _scaffoldKey.currentState.showSnackBar(SnackBar(
/// _scaffold
Messenger
Key.currentState.showSnackBar(SnackBar(
/// content: Text(
/// 'Select an item to remove from the list.',
/// style: TextStyle(fontSize: 20),
...
...
@@ -579,6 +580,7 @@ class AnimatedListState extends State<AnimatedList> with TickerProviderStateMixi
/// @override
/// Widget build(BuildContext context) {
/// return MaterialApp(
/// scaffoldMessengerKey: _scaffoldMessengerKey,
/// home: Scaffold(
/// key: _scaffoldKey,
/// body: CustomScrollView(
...
...
packages/flutter/lib/src/widgets/framework.dart
View file @
64c845c5
...
...
@@ -2112,33 +2112,52 @@ typedef ElementVisitor = void Function(Element element);
/// widget can be used: the build context passed to the [Builder.builder]
/// callback will be that of the [Builder] itself.
///
/// For example, in the following snippet, the [ScaffoldState.show
SnackBar
]
/// For example, in the following snippet, the [ScaffoldState.show
BottomSheet
]
/// method is called on the [Scaffold] widget that the build method itself
/// creates. If a [Builder] had not been used, and instead the `context`
/// argument of the build method itself had been used, no [Scaffold] would have
/// been found, and the [Scaffold.of] function would have returned null.
///
/// ```dart
/// @override
/// Widget build(BuildContext context) {
/// // here, Scaffold.of(context) returns null
/// return Scaffold(
/// appBar: AppBar(title: Text('Demo')),
/// body: Builder(
/// builder: (BuildContext context) {
/// return TextButton(
/// child: Text('BUTTON'),
/// onPressed: () {
/// // here, Scaffold.of(context) returns the locally created Scaffold
/// Scaffold.of(context).showSnackBar(SnackBar(
/// content: Text('Hello.')
/// ));
/// }
/// );
/// }
/// )
/// );
/// }
/// @override
/// Widget build(BuildContext context) {
/// // here, Scaffold.of(context) returns null
/// return Scaffold(
/// appBar: const AppBar(title: Text('Demo')),
/// body: Builder(
/// builder: (BuildContext context) {
/// return TextButton(
/// child: const Text('BUTTON'),
/// onPressed: () {
/// Scaffold.of(context).showBottomSheet<void>(
/// (BuildContext context) {
/// return Container(
/// alignment: Alignment.center,
/// height: 200,
/// color: Colors.amber,
/// child: Center(
/// child: Column(
/// mainAxisSize: MainAxisSize.min,
/// children: <Widget>[
/// const Text('BottomSheet'),
/// ElevatedButton(
/// child: const Text('Close BottomSheet'),
/// onPressed: () {
/// Navigator.pop(context),
/// },
/// )
/// ],
/// ),
/// ),
/// );
/// },
/// );
/// },
/// );
/// },
/// )
/// );
/// }
/// ```
///
/// The [BuildContext] for a particular widget can change location over time as
...
...
packages/flutter/test/material/debug_test.dart
View file @
64c845c5
...
...
@@ -168,6 +168,8 @@ void main() {
' _InheritedTheme
\n
'
' Theme
\n
'
' AnimatedTheme
\n
'
' _ScaffoldMessengerScope
\n
'
' ScaffoldMessenger
\n
'
' Builder
\n
'
' DefaultTextStyle
\n
'
' CustomPaint
\n
'
...
...
@@ -204,4 +206,83 @@ void main() {
' or WidgetsApp widget at the top of your application widget tree.
\n
'
,
));
});
testWidgets
(
'debugCheckHasScaffoldMessenger control test'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
<
ScaffoldState
>
_scaffoldKey
=
GlobalKey
<
ScaffoldState
>();
final
GlobalKey
<
ScaffoldMessengerState
>
_scaffoldMessengerKey
=
GlobalKey
<
ScaffoldMessengerState
>();
final
SnackBar
snackBar
=
SnackBar
(
content:
const
Text
(
'Snack'
),
action:
SnackBarAction
(
label:
'Test'
,
onPressed:
()
{})
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
MediaQuery
(
data:
const
MediaQueryData
(),
child:
ScaffoldMessenger
(
key:
_scaffoldMessengerKey
,
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
Scaffold
(
key:
_scaffoldKey
,
body:
Container
(),
);
}
)
),
)
));
final
List
<
dynamic
>
exceptions
=
<
dynamic
>[];
final
FlutterExceptionHandler
oldHandler
=
FlutterError
.
onError
;
FlutterError
.
onError
=
(
FlutterErrorDetails
details
)
{
exceptions
.
add
(
details
.
exception
);
};
// ScaffoldMessenger shows SnackBar.
_scaffoldMessengerKey
.
currentState
.
showSnackBar
(
snackBar
);
await
tester
.
pumpAndSettle
();
// Pump widget to rebuild without ScaffoldMessenger
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
MediaQuery
(
data:
const
MediaQueryData
(),
child:
Scaffold
(
key:
_scaffoldKey
,
body:
Container
(),
),
),
));
// The Scaffold should assert we still have an ancestor ScaffoldMessenger in
// order to dismiss the SnackBar from the ScaffoldMessenger.
await
tester
.
tap
(
find
.
text
(
'Test'
));
FlutterError
.
onError
=
oldHandler
;
expect
(
exceptions
.
length
,
1
);
expect
(
exceptions
.
single
.
runtimeType
,
FlutterError
);
final
FlutterError
error
=
exceptions
.
first
as
FlutterError
;
expect
(
error
.
diagnostics
.
length
,
5
);
expect
(
error
.
diagnostics
[
2
],
isA
<
DiagnosticsProperty
<
Element
>>());
expect
(
error
.
diagnostics
[
3
],
isA
<
DiagnosticsBlock
>());
expect
(
error
.
diagnostics
[
4
].
level
,
DiagnosticLevel
.
hint
);
expect
(
error
.
diagnostics
[
4
].
toStringDeep
(),
equalsIgnoringHashCodes
(
'Typically, the ScaffoldMessenger widget is introduced by the
\n
'
'MaterialApp at the top of your application widget tree.
\n
'
,
),
);
expect
(
error
.
toStringDeep
(),
equalsIgnoringHashCodes
(
'FlutterError
\n
'
' No ScaffoldMessenger widget found.
\n
'
' Scaffold widgets require a ScaffoldMessenger widget ancestor.
\n
'
' The specific widget that could not find a ScaffoldMessenger
\n
'
' ancestor was:
\n
'
' Scaffold-[LabeledGlobalKey<ScaffoldState>#00829]
\n
'
' The ancestors of this widget were:
\n
'
' MediaQuery
\n
'
' Directionality
\n
'
' [root]
\n
'
' Typically, the ScaffoldMessenger widget is introduced by the
\n
'
' MaterialApp at the top of your application widget tree.
\n
'
));
});
}
packages/flutter/test/material/floating_action_button_location_test.dart
View file @
64c845c5
...
...
@@ -649,7 +649,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
FloatingActionButton
(
onPressed:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
'Snacky!'
)),
);
},
...
...
packages/flutter/test/material/scaffold_test.dart
View file @
64c845c5
...
...
@@ -2034,6 +2034,117 @@ void main() {
await
tester
.
pumpAndSettle
();
});
});
testWidgets
(
'ScaffoldMessenger.of can return null'
,
(
WidgetTester
tester
)
async
{
ScaffoldMessengerState
scaffoldMessenger
;
const
Key
tapTarget
=
Key
(
'tap-target'
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
MediaQuery
(
data:
const
MediaQueryData
(),
child:
Scaffold
(
body:
Builder
(
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
scaffoldMessenger
=
ScaffoldMessenger
.
of
(
context
,
nullOk:
true
);
},
behavior:
HitTestBehavior
.
opaque
,
child:
Container
(
height:
100.0
,
width:
100.0
,
key:
tapTarget
,
),
);
}
),
),
),
));
await
tester
.
tap
(
find
.
byKey
(
tapTarget
));
await
tester
.
pump
();
expect
(
scaffoldMessenger
,
isNull
);
});
testWidgets
(
'ScaffoldMessenger.of will assert if !nullOk'
,
(
WidgetTester
tester
)
async
{
const
Key
tapTarget
=
Key
(
'tap-target'
);
final
List
<
dynamic
>
exceptions
=
<
dynamic
>[];
final
FlutterExceptionHandler
oldHandler
=
FlutterError
.
onError
;
FlutterError
.
onError
=
(
FlutterErrorDetails
details
)
{
exceptions
.
add
(
details
.
exception
);
};
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
MediaQuery
(
data:
const
MediaQueryData
(),
child:
Scaffold
(
body:
Builder
(
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
ScaffoldMessenger
.
of
(
context
);
},
behavior:
HitTestBehavior
.
opaque
,
child:
Container
(
height:
100.0
,
width:
100.0
,
key:
tapTarget
,
),
);
}
),
),
),
));
await
tester
.
tap
(
find
.
byKey
(
tapTarget
));
FlutterError
.
onError
=
oldHandler
;
expect
(
exceptions
.
length
,
1
);
expect
(
exceptions
.
single
.
runtimeType
,
FlutterError
);
final
FlutterError
error
=
exceptions
.
first
as
FlutterError
;
expect
(
error
.
diagnostics
.
length
,
5
);
expect
(
error
.
diagnostics
[
2
],
isA
<
DiagnosticsProperty
<
Element
>>());
expect
(
error
.
diagnostics
[
3
],
isA
<
DiagnosticsBlock
>());
expect
(
error
.
diagnostics
[
4
].
level
,
DiagnosticLevel
.
hint
);
expect
(
error
.
diagnostics
[
4
].
toStringDeep
(),
equalsIgnoringHashCodes
(
'Typically, the ScaffoldMessenger widget is introduced by the
\n
'
'MaterialApp at the top of your application widget tree.
\n
'
,
),
);
expect
(
error
.
toStringDeep
(),
equalsIgnoringHashCodes
(
'FlutterError
\n
'
' No ScaffoldMessenger widget found.
\n
'
' Builder widgets require a ScaffoldMessenger widget ancestor.
\n
'
' The specific widget that could not find a ScaffoldMessenger
\n
'
' ancestor was:
\n
'
' Builder
\n
'
' The ancestors of this widget were:
\n
'
' _BodyBuilder
\n
'
' MediaQuery
\n
'
' LayoutId-[<_ScaffoldSlot.body>]
\n
'
' CustomMultiChildLayout
\n
'
' AnimatedBuilder
\n
'
' DefaultTextStyle
\n
'
' AnimatedDefaultTextStyle
\n
'
' _InkFeatures-[GlobalKey#342d0 ink renderer]
\n
'
' NotificationListener<LayoutChangedNotification>
\n
'
' PhysicalModel
\n
'
' AnimatedPhysicalModel
\n
'
' Material
\n
'
' PrimaryScrollController
\n
'
' _ScaffoldScope
\n
'
' Scaffold
\n
'
' MediaQuery
\n
'
' Directionality
\n
'
' [root]
\n
'
' Typically, the ScaffoldMessenger widget is introduced by the
\n
'
' MaterialApp at the top of your application widget tree.
\n
'
));
});
}
class
_GeometryListener
extends
StatefulWidget
{
...
...
packages/flutter/test/material/snack_bar_test.dart
View file @
64c845c5
...
...
@@ -4,6 +4,7 @@
// @dart = 2.8
import
'package:flutter/foundation.dart'
show
FlutterExceptionHandler
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
...
...
@@ -54,6 +55,51 @@ void main() {
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
(
home:
Scaffold
(
body:
Builder
(
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
helloSnackBar
),
duration:
Duration
(
seconds:
2
),
));
},
behavior:
HitTestBehavior
.
opaque
,
child:
Container
(
height:
100.0
,
width:
100.0
,
key:
tapTarget
,
),
);
}
),
),
));
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 twice test'
,
(
WidgetTester
tester
)
async
{
int
snackBarCount
=
0
;
const
Key
tapTarget
=
Key
(
'tap-target'
);
...
...
@@ -129,6 +175,81 @@ void main() {
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
(
home:
Scaffold
(
body:
Builder
(
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
snackBarCount
+=
1
;
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
'bar
$snackBarCount
'
),
duration:
const
Duration
(
seconds:
2
),
));
},
behavior:
HitTestBehavior
.
opaque
,
child:
Container
(
height:
100.0
,
width:
100.0
,
key:
tapTarget
,
),
);
}
),
),
));
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 cancel test'
,
(
WidgetTester
tester
)
async
{
int
snackBarCount
=
0
;
const
Key
tapTarget
=
Key
(
'tap-target'
);
...
...
@@ -215,6 +336,92 @@ void main() {
expect
(
find
.
text
(
'bar2'
),
findsNothing
);
});
testWidgets
(
'SnackBar cancel test - ScaffoldMessenger'
,
(
WidgetTester
tester
)
async
{
int
snackBarCount
=
0
;
const
Key
tapTarget
=
Key
(
'tap-target'
);
int
time
;
ScaffoldFeatureController
<
SnackBar
,
SnackBarClosedReason
>
lastController
;
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
Builder
(
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
snackBarCount
+=
1
;
lastController
=
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
'bar
$snackBarCount
'
),
duration:
Duration
(
seconds:
time
),
));
},
behavior:
HitTestBehavior
.
opaque
,
child:
Container
(
height:
100.0
,
width:
100.0
,
key:
tapTarget
,
),
);
}
),
),
));
expect
(
find
.
text
(
'bar1'
),
findsNothing
);
expect
(
find
.
text
(
'bar2'
),
findsNothing
);
time
=
1000
;
await
tester
.
tap
(
find
.
byKey
(
tapTarget
));
// queue bar1
final
ScaffoldFeatureController
<
SnackBar
,
SnackBarClosedReason
>
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 dismiss test'
,
(
WidgetTester
tester
)
async
{
int
snackBarCount
=
0
;
const
Key
tapTarget
=
Key
(
'tap-target'
);
...
...
@@ -260,7 +467,91 @@ void main() {
expect
(
find
.
text
(
'bar2'
),
findsOneWidget
);
});
testWidgets
(
'SnackBar cannot be tapped twice'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'SnackBar dismiss test - ScaffoldMessenger'
,
(
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
(
onTap:
()
{
snackBarCount
+=
1
;
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
'bar
$snackBarCount
'
),
duration:
const
Duration
(
seconds:
2
),
));
},
behavior:
HitTestBehavior
.
opaque
,
child:
Container
(
height:
100.0
,
width:
100.0
,
key:
tapTarget
,
),
);
}
),
),
));
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
await
tester
.
drag
(
find
.
text
(
'bar1'
),
const
Offset
(
0.0
,
50.0
));
await
tester
.
pump
();
// bar1 dismissed, bar2 begins animating
expect
(
find
.
text
(
'bar1'
),
findsNothing
);
expect
(
find
.
text
(
'bar2'
),
findsOneWidget
);
});
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
(
...
...
@@ -268,7 +559,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
'I am a snack bar.'
),
duration:
const
Duration
(
seconds:
2
),
action:
SnackBarAction
(
...
...
@@ -306,23 +597,23 @@ void main() {
theme:
lightTheme
,
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:
()
{
},
),
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:
()
{
},
),
);
},
child:
const
Text
(
'X'
),
);
}
),
);
},
child:
const
Text
(
'X'
),
);
}
),
),
),
...
...
@@ -333,7 +624,7 @@ void main() {
await
tester
.
pump
(
const
Duration
(
milliseconds:
750
));
final
RenderPhysicalModel
renderModel
=
tester
.
renderObject
(
find
.
widgetWithText
(
Material
,
'I am a snack bar.'
).
first
find
.
widgetWithText
(
Material
,
'I am a snack bar.'
).
first
);
// There is a somewhat complicated background color calculation based
// off of the surface color. For the default light theme it
...
...
@@ -467,6 +758,53 @@ void main() {
);
});
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
(
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:
()
{
ScaffoldMessenger
.
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
(
'Snackbar padding can be customized'
,
(
WidgetTester
tester
)
async
{
const
double
padding
=
20.0
;
await
tester
.
pumpWidget
(
...
...
@@ -476,7 +814,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
'I am a snack bar.'
),
padding:
EdgeInsets
.
all
(
padding
),
...
...
@@ -520,7 +858,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
'I am a snack bar.'
),
width:
width
,
...
...
@@ -558,7 +896,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
'I am a snack bar.'
),
duration:
const
Duration
(
seconds:
2
),
...
...
@@ -611,7 +949,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
'I am a snack bar.'
),
duration:
const
Duration
(
seconds:
2
),
action:
SnackBarAction
(
label:
'ACTION'
,
onPressed:
()
{
}),
...
...
@@ -666,7 +1004,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
'I am a snack bar.'
),
duration:
const
Duration
(
seconds:
2
),
action:
SnackBarAction
(
label:
'ACTION'
,
onPressed:
()
{}),
...
...
@@ -717,7 +1055,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
'I am a snack bar.'
),
duration:
const
Duration
(
seconds:
2
),
action:
SnackBarAction
(
label:
'ACTION'
,
onPressed:
()
{}),
...
...
@@ -771,7 +1109,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
'I am a snack bar.'
),
duration:
const
Duration
(
seconds:
2
),
action:
SnackBarAction
(
label:
'ACTION'
,
onPressed:
()
{}),
...
...
@@ -829,7 +1167,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
'I am a snack bar.'
),
duration:
const
Duration
(
seconds:
2
),
action:
SnackBarAction
(
label:
'ACTION'
,
onPressed:
()
{}),
...
...
@@ -861,18 +1199,18 @@ void main() {
});
testWidgets
(
'SnackBarClosedReason'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
<
Scaffold
State
>
scaffoldKey
=
GlobalKey
<
Scaffold
State
>();
final
GlobalKey
<
Scaffold
MessengerState
>
scaffoldMessengerKey
=
GlobalKey
<
ScaffoldMessenger
State
>();
bool
actionPressed
=
false
;
SnackBarClosedReason
closedReason
;
await
tester
.
pumpWidget
(
MaterialApp
(
scaffoldMessengerKey:
scaffoldMessengerKey
,
home:
Scaffold
(
key:
scaffoldKey
,
body:
Builder
(
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
'snack'
),
duration:
const
Duration
(
seconds:
2
),
action:
SnackBarAction
(
...
...
@@ -917,14 +1255,14 @@ void main() {
// Pop up the snack bar and then remove it.
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
750
));
scaffoldKey
.
currentState
.
removeCurrentSnackBar
();
scaffold
Messenger
Key
.
currentState
.
removeCurrentSnackBar
();
await
tester
.
pumpAndSettle
(
const
Duration
(
seconds:
1
));
expect
(
closedReason
,
equals
(
SnackBarClosedReason
.
remove
));
// Pop up the snack bar and then hide it.
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
750
));
scaffoldKey
.
currentState
.
hideCurrentSnackBar
();
scaffold
Messenger
Key
.
currentState
.
hideCurrentSnackBar
();
await
tester
.
pumpAndSettle
(
const
Duration
(
seconds:
1
));
expect
(
closedReason
,
equals
(
SnackBarClosedReason
.
hide
));
...
...
@@ -979,34 +1317,118 @@ void main() {
expect
(
find
.
text
(
'ACTION'
),
findsNothing
);
});
testWidgets
(
'contributes dismiss semantics'
,
(
WidgetTester
tester
)
async
{
final
SemanticsHandle
handle
=
tester
.
ensureSemantics
();
testWidgets
(
'accessible navigation behavior with action - ScaffoldMessenger'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
<
ScaffoldState
>
scaffoldKey
=
GlobalKey
<
ScaffoldState
>();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
MediaQuery
(
data:
const
MediaQueryData
(
accessibleNavigation:
true
),
child:
Scaffold
(
home:
MediaQuery
(
data:
const
MediaQueryData
(
accessibleNavigation:
true
),
child:
ScaffoldMessenger
(
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
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'
),
);
body:
GestureDetector
(
onTap:
()
{
ScaffoldMessenger
.
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
(
'contributes dismiss semantics'
,
(
WidgetTester
tester
)
async
{
final
SemanticsHandle
handle
=
tester
.
ensureSemantics
();
final
GlobalKey
<
ScaffoldState
>
scaffoldKey
=
GlobalKey
<
ScaffoldState
>();
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
<
ScaffoldState
>
scaffoldKey
=
GlobalKey
<
ScaffoldState
>();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
MediaQuery
(
data:
const
MediaQueryData
(
accessibleNavigation:
true
),
child:
ScaffoldMessenger
(
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
Scaffold
(
key:
scaffoldKey
,
body:
GestureDetector
(
onTap:
()
{
ScaffoldMessenger
.
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
();
...
...
@@ -1031,7 +1453,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
helloSnackBar
),
));
},
...
...
@@ -1071,6 +1493,54 @@ void main() {
});
testWidgets
(
'SnackBar handles updates to accessibleNavigation'
,
(
WidgetTester
tester
)
async
{
Future
<
void
>
boilerplate
({
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
<
void
>
boilerplate
({
bool
accessibleNavigation
})
{
return
tester
.
pumpWidget
(
MaterialApp
(
home:
MediaQuery
(
...
...
@@ -1080,7 +1550,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
'test'
),
action:
SnackBarAction
(
label:
'foo'
,
onPressed:
()
{
}),
));
...
...
@@ -1126,7 +1596,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
Text
(
nonconst
(
'hello'
)),
duration:
null
,
));
...
...
@@ -1156,7 +1626,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
'hello'
),
duration:
const
Duration
(
seconds:
1
),
onVisible:
()
{
...
...
@@ -1193,14 +1663,14 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
'hello'
),
duration:
const
Duration
(
seconds:
1
),
onVisible:
()
{
called
+=
1
;
},
));
Scaffold
.
of
(
context
).
showSnackBar
(
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
'hello 2'
),
duration:
const
Duration
(
seconds:
1
),
onVisible:
()
{
...
...
@@ -1247,8 +1717,8 @@ void main() {
),
);
final
Scaffold
State
scaffoldState
=
tester
.
state
(
find
.
byType
(
Scaffold
));
scaffoldState
.
showSnackBar
(
snackBar
);
final
Scaffold
MessengerState
scaffoldMessengerState
=
tester
.
state
(
find
.
byType
(
ScaffoldMessenger
));
scaffold
Messenger
State
.
showSnackBar
(
snackBar
);
await
tester
.
pumpAndSettle
();
// Have the SnackBar fully animate out.
...
...
@@ -1278,8 +1748,8 @@ void main() {
),
);
final
Scaffold
State
scaffoldState
=
tester
.
state
(
find
.
byType
(
Scaffold
));
scaffoldState
.
showSnackBar
(
snackBar
);
final
Scaffold
MessengerState
scaffoldMessengerState
=
tester
.
state
(
find
.
byType
(
ScaffoldMessenger
));
scaffold
Messenger
State
.
showSnackBar
(
snackBar
);
await
tester
.
pumpAndSettle
();
// Have the SnackBar fully animate out.
...
...
@@ -1298,9 +1768,8 @@ void main() {
testWidgets
(
'Padding of
$behavior
is not consumed by viewInsets'
,
(
WidgetTester
tester
)
async
{
final
Widget
child
=
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Scaffold
(
final
Widget
child
=
MaterialApp
(
home:
Scaffold
(
resizeToAvoidBottomInset:
false
,
floatingActionButton:
FloatingActionButton
(
child:
const
Icon
(
Icons
.
send
),
...
...
@@ -1310,7 +1779,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
'I am a snack bar.'
),
duration:
const
Duration
(
seconds:
2
),
...
...
@@ -1374,8 +1843,8 @@ void main() {
),
);
final
Scaffold
State
scaffoldState
=
tester
.
state
(
find
.
byType
(
Scaffold
));
scaffoldState
.
showSnackBar
(
final
Scaffold
MessengerState
scaffoldMessengerState
=
tester
.
state
(
find
.
byType
(
ScaffoldMessenger
));
scaffold
Messenger
State
.
showSnackBar
(
const
SnackBar
(
content:
Text
(
'Snackbar text'
),
behavior:
SnackBarBehavior
.
fixed
,
...
...
@@ -1410,7 +1879,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
'I am a snack bar.'
),
duration:
const
Duration
(
seconds:
2
),
action:
SnackBarAction
(
label:
'ACTION'
,
onPressed:
()
{}),
...
...
@@ -1452,8 +1921,8 @@ void main() {
),
);
final
Scaffold
State
scaffoldState
=
tester
.
state
(
find
.
byType
(
Scaffold
));
scaffoldState
.
showSnackBar
(
final
Scaffold
MessengerState
scaffoldMessengerState
=
tester
.
state
(
find
.
byType
(
ScaffoldMessenger
));
scaffold
Messenger
State
.
showSnackBar
(
const
SnackBar
(
content:
Text
(
'SnackBar text'
),
behavior:
SnackBarBehavior
.
fixed
,
...
...
@@ -1489,8 +1958,8 @@ void main() {
),
);
final
Scaffold
State
scaffoldState
=
tester
.
state
(
find
.
byType
(
Scaffold
));
scaffoldState
.
showSnackBar
(
final
Scaffold
MessengerState
scaffoldMessengerState
=
tester
.
state
(
find
.
byType
(
ScaffoldMessenger
));
scaffold
Messenger
State
.
showSnackBar
(
const
SnackBar
(
content:
Text
(
'SnackBar text'
),
behavior:
SnackBarBehavior
.
floating
,
...
...
@@ -1506,4 +1975,107 @@ void main() {
},
);
});
testWidgets
(
'SnackBars hero across transitions when using ScaffoldMessenger'
,
(
WidgetTester
tester
)
async
{
const
String
snackBarText
=
'hello snackbar'
;
const
String
firstHeader
=
'home'
;
const
String
secondHeader
=
'second'
;
const
Key
snackTarget
=
Key
(
'snack-target'
);
const
Key
transitionTarget
=
Key
(
'transition-target'
);
Widget
_buildApp
()
{
return
MaterialApp
(
routes:
<
String
,
WidgetBuilder
>
{
'/'
:
(
BuildContext
context
)
{
return
Scaffold
(
appBar:
AppBar
(
title:
const
Text
(
firstHeader
)),
body:
Center
(
child:
ElevatedButton
(
key:
transitionTarget
,
child:
const
Text
(
'PUSH'
),
onPressed:
()
{
Navigator
.
of
(
context
).
pushNamed
(
'/second'
);
},
),
),
floatingActionButton:
FloatingActionButton
(
key:
snackTarget
,
onPressed:
()
async
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
snackBarText
),
),
);
},
child:
const
Text
(
'X'
),
),
);
},
'/second'
:
(
BuildContext
context
)
=>
Scaffold
(
appBar:
AppBar
(
title:
const
Text
(
secondHeader
)),
),
}
);
}
await
tester
.
pumpWidget
(
_buildApp
());
expect
(
find
.
text
(
snackBarText
),
findsNothing
);
expect
(
find
.
text
(
firstHeader
),
findsOneWidget
);
expect
(
find
.
text
(
secondHeader
),
findsNothing
);
// Present SnackBar
await
tester
.
tap
(
find
.
byKey
(
snackTarget
));
await
tester
.
pump
();
// schedule animation
expect
(
find
.
text
(
snackBarText
),
findsOneWidget
);
await
tester
.
pump
();
// begin animation
expect
(
find
.
text
(
snackBarText
),
findsOneWidget
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
750
));
expect
(
find
.
text
(
snackBarText
),
findsOneWidget
);
// Push new route
await
tester
.
tap
(
find
.
byKey
(
transitionTarget
));
await
tester
.
pump
();
expect
(
find
.
text
(
snackBarText
),
findsOneWidget
);
expect
(
find
.
text
(
firstHeader
),
findsOneWidget
);
expect
(
find
.
text
(
secondHeader
,
skipOffstage:
false
),
findsOneWidget
);
await
tester
.
pump
();
expect
(
find
.
text
(
snackBarText
),
findsOneWidget
);
expect
(
find
.
text
(
firstHeader
),
findsOneWidget
);
expect
(
find
.
text
(
secondHeader
),
findsOneWidget
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
750
));
expect
(
find
.
text
(
snackBarText
),
findsOneWidget
);
expect
(
find
.
text
(
firstHeader
),
findsNothing
);
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
<
dynamic
>
exceptions
=
<
dynamic
>[];
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.'
));
});
}
packages/flutter/test/material/snack_bar_theme_test.dart
View file @
64c845c5
...
...
@@ -73,7 +73,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
text
),
duration:
const
Duration
(
seconds:
2
),
action:
SnackBarAction
(
label:
'ACTION'
,
onPressed:
()
{}),
...
...
@@ -111,7 +111,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
text
),
duration:
const
Duration
(
seconds:
2
),
action:
SnackBarAction
(
label:
action
,
onPressed:
()
{}),
...
...
@@ -155,7 +155,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
backgroundColor:
backgroundColor
,
elevation:
elevation
,
shape:
shape
,
...
...
@@ -202,7 +202,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
'I am a snack bar.'
),
duration:
const
Duration
(
seconds:
2
),
action:
SnackBarAction
(
label:
'ACTION'
,
onPressed:
()
{}),
...
...
@@ -244,7 +244,7 @@ void main() {
builder:
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
SnackBar
(
Scaffold
Messenger
.
of
(
context
).
showSnackBar
(
SnackBar
(
content:
const
Text
(
'I am a snack bar.'
),
duration:
const
Duration
(
seconds:
2
),
action:
SnackBarAction
(
label:
'ACTION'
,
onPressed:
()
{}),
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment