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
adc5f26b
Unverified
Commit
adc5f26b
authored
Sep 08, 2020
by
Hans Muller
Committed by
GitHub
Sep 08, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Re-land ScaffoldMessenger (#65416)
parent
c0675577
Changes
11
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
820 additions
and
302 deletions
+820
-302
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
+483
-152
snack_bar.dart
packages/flutter/lib/src/material/snack_bar.dart
+20
-18
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
+48
-0
floating_action_button_location_test.dart
...r/test/material/floating_action_button_location_test.dart
+1
-1
snack_bar_test.dart
packages/flutter/test/material/snack_bar_test.dart
+152
-81
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 @
adc5f26b
...
...
@@ -17,6 +17,7 @@ import 'floating_action_button.dart';
import
'icons.dart'
;
import
'material_localizations.dart'
;
import
'page.dart'
;
import
'scaffold.dart'
;
import
'theme.dart'
;
/// [MaterialApp] uses this [TextStyle] as its [DefaultTextStyle] to encourage
...
...
@@ -168,6 +169,7 @@ class MaterialApp extends StatefulWidget {
const
MaterialApp
({
Key
key
,
this
.
navigatorKey
,
this
.
scaffoldMessengerKey
,
this
.
home
,
this
.
routes
=
const
<
String
,
WidgetBuilder
>{},
this
.
initialRoute
,
...
...
@@ -215,6 +217,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
;
...
...
@@ -722,27 +733,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 @
adc5f26b
...
...
@@ -137,7 +137,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 @
adc5f26b
...
...
@@ -9,7 +9,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.
///
...
...
@@ -125,3 +125,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
.
widget
is
!
ScaffoldMessenger
&&
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 @
adc5f26b
This diff is collapsed.
Click to expand it.
packages/flutter/lib/src/material/snack_bar.dart
View file @
adc5f26b
...
...
@@ -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.
...
...
@@ -123,7 +123,7 @@ class _SnackBarActionState extends State<SnackBarAction> {
_haveTriggeredAction
=
true
;
});
widget
.
onPressed
();
Scaffold
.
of
(
context
).
hideCurrentSnackBar
(
reason:
SnackBarClosedReason
.
action
);
Scaffold
Messenger
.
of
(
context
).
hideCurrentSnackBar
(
reason:
SnackBarClosedReason
.
action
);
}
@override
...
...
@@ -146,8 +146,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].
///
...
...
@@ -156,11 +156,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
...
...
@@ -289,7 +289,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>
...
...
@@ -301,7 +301,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
})
{
...
...
@@ -516,14 +516,14 @@ class _SnackBarState extends State<SnackBar> {
container:
true
,
liveRegion:
true
,
onDismiss:
()
{
Scaffold
.
of
(
context
).
removeCurrentSnackBar
(
reason:
SnackBarClosedReason
.
dismiss
);
Scaffold
Messenger
.
of
(
context
).
removeCurrentSnackBar
(
reason:
SnackBarClosedReason
.
dismiss
);
},
child:
Dismissible
(
key:
const
Key
(
'dismissible'
),
direction:
DismissDirection
.
down
,
resizeDuration:
null
,
onDismissed:
(
DismissDirection
direction
)
{
Scaffold
.
of
(
context
).
removeCurrentSnackBar
(
reason:
SnackBarClosedReason
.
swipe
);
Scaffold
Messenger
.
of
(
context
).
removeCurrentSnackBar
(
reason:
SnackBarClosedReason
.
swipe
);
},
child:
snackBar
,
),
...
...
@@ -550,7 +550,9 @@ class _SnackBarState extends State<SnackBar> {
child:
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 @
adc5f26b
...
...
@@ -512,6 +512,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.
...
...
@@ -569,7 +570,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),
...
...
@@ -581,6 +582,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 @
adc5f26b
...
...
@@ -2084,33 +2084,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: AppBar(title: Text('Demo')),
/// body: Builder(
/// builder: (BuildContext context) {
/// return TextButton(
/// child: 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 @
adc5f26b
...
...
@@ -164,6 +164,8 @@ void main() {
' _InheritedTheme
\n
'
' Theme
\n
'
' AnimatedTheme
\n
'
' _ScaffoldMessengerScope
\n
'
' ScaffoldMessenger
\n
'
' Builder
\n
'
' DefaultTextStyle
\n
'
' CustomPaint
\n
'
...
...
@@ -196,4 +198,50 @@ 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
>();
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
MediaQuery
(
data:
const
MediaQueryData
(),
child:
Scaffold
(
key:
_scaffoldKey
,
body:
Container
(),
),
),
));
FlutterError
error
;
try
{
_scaffoldKey
.
currentState
.
showSnackBar
(
const
SnackBar
(
content:
Text
(
'Something is missing here'
)));
}
on
FlutterError
catch
(
e
)
{
error
=
e
;
}
finally
{
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>#d60fa]
\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 @
adc5f26b
...
...
@@ -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/snack_bar_test.dart
View file @
adc5f26b
This diff is collapsed.
Click to expand it.
packages/flutter/test/material/snack_bar_theme_test.dart
View file @
adc5f26b
...
...
@@ -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:
()
{}),
...
...
@@ -110,7 +110,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:
()
{}),
...
...
@@ -153,7 +153,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
,
...
...
@@ -200,7 +200,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:
()
{}),
...
...
@@ -242,7 +242,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