This tutorial is about how to show, hide, or remove SnackBar
s in Flutter using ScaffoldMessenger
.
Flutter 2 introduces a new widget called ScaffoldMessenger
. It's a widget for managing SnackBar
s for descendant Scaffold
s. It has the APIs for showing, hiding, and removing SnackBar
s. The introduction of ScaffoldMessnger
replaces the old way of displaying SnackBar
s using ScaffoldMessenger.of(context)
which has a few issues. With ScaffoldMessenger
, now it becomes possible to make Snackbar
s persist between Scaffold
transitions and create a SnackBar
in response to an AppBar
action.
Using ScaffoldMessenger
Widget
First you need to understand how ScaffoldMessenger
works. It works by creating a scope in which the descendant Scaffold
s register so that they can receive SnackBar
s. Therefore, it allows SnackBar
s to persist across different Scaffold
s.
The MaterialApp
widget provides a root ScaffoldMessenger
. If you show a SnackBar
using the root ScaffoldMessenger
, it becomes possible for all descendant Scaffold
s to receive the SnackBar
. However, you can control which Scaffold
s can receive the SnackBar
.
In this tutorial, we are going to create a simple application that involves navigation between pages where the pages use different Scaffold
s, so that you can understand how the ScaffoldMessenger
widget works. In addition, I'm going to show you how to limit a SnackBar
to be displayed on particular Scaffold
s.
Show Snackbar
This tutorial doesn't explain how to create and customize the look of the SnackBar
. We already have a tutorial about how to create a SnackBar
which also explains the supported named arguments that you can pass for customization. Below is a simple SnackBar
used in this tutorial. It uses the default styles and has a duration of 5 seconds.
final SnackBar _snackBar = SnackBar(
content: const Text('Message from page one'),
duration: const Duration(seconds: 5),
);
Displaying a Snackbar
can be done by calling the showSnackBar
method of ScaffoldMessengerState
.
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showSnackBar(SnackBar snackBar)
To get the instance of ScaffoldMessengerState
, you can use the method below
static ScaffoldMessengerState of(BuildContext context)
It returns the state of the nearest instance that encloses the given context. That means the passed BuildContext
must be below a Scaffold
widget in the tree. After that, you can call the showSnackBar
method.
ScaffoldMessenger.of(context).showSnackBar(_snackBar);
Below is a simple application consisting of two pages: PageOne
and PageTwo
. Both pages use the ScaffoldMessenger
from the MaterialApp
(instead of creating a new one). As the result, a SnackBar
triggered from PageOne
is also displayed on PageTwo
and vice versa.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/one',
routes: {
'/one': (context) => PageOne(),
'/two': (context) => PageTwo(),
},
);
}
}
class PageOne extends StatelessWidget {
final SnackBar _snackBar = SnackBar(
content: const Text('Message from page one'),
duration: const Duration(seconds: 5),
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.teal,
title: Text('Woolha.com - Page one'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
OutlinedButton(
child: Text('Go to page two'),
onPressed: () {
Navigator.pushNamed(context, '/two');
},
),
OutlinedButton(
child: Text('Show message!'),
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(_snackBar);
},
),
],
),
),
);
}
}
class PageTwo extends StatelessWidget {
final SnackBar _snackBar = SnackBar(
content: const Text('Message from page two'),
duration: const Duration(seconds: 5),
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.teal,
title: Text('Woolha.com - Page Two'),
),
body: Builder(
builder: (BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
OutlinedButton(
child: Text('Go to page one'),
onPressed: () {
Navigator.pop(context);
},
),
OutlinedButton(
child: Text('Show message!'),
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(_snackBar);
},
),
],
),
);
}
),
);
}
}
Output:
ScaffoldMessengerState
uses FIFO queue system. If there's a displayed SnackBar
and the showSnackBar
is triggered, the new SnackBar
will be pushed to the queue and wait for the current one to finish (also the others in the queue if any).
Hide Snackbar
If you need to hide the displayed SnackBar
, ScaffoldMessengerState
has a method named hideCurrentSnackBar
you can use for that purpose. The method hides the current SnackBar
by running its normal exit operation. As the result, you can see the animation when the SnackBar
is being hidden.
void hideCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.hide })
Getting the ScaffoldMessengerState
instance is the same as the above example for showing a SnackBar
. Then you need to call the hideCurrentSnackBar
method. There is an optional argument reason
which is used to specify how a SnackBar
is closed. The default value, if you don't pass the argument, is hide
.
For example, add the button below as another child of the Column
widget on the PageTwo
.
OutlinedButton(
child: Text('Hide!'),
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar(
reason: SnackBarClosedReason.action
);
},
),
Since it uses the same ScaffoldMessengerState
with the one used for displaying the SnackBar
in PageOne
, it can be used to hide the SnackBar
displayed from PageOne
. If there is any SnackBar
in the queue, the first one (the oldest one) will be displayed immediately.
Output:
Remove Snackbar
ScaffoldMessengerState
has another method named removeCurrentSnackBar
. Unlike the previous method, triggering this method doesn't run the normal exit operation of the SnackBar
. You will see that the SnackBar
disappears immediately without animation.
void removeCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.remove })
It also supports the optional reason
argument whose default value is remove
. As soon as the current SnackBar
has been removed, the next one will be shown if the queue is not empty.
ScaffoldMessenger.of(context).removeCurrentSnackBar(
reason: SnackBarClosedReason.action
);
To try the above method, hust add the button below as another child of the Column
widget on the PageTwo
.
OutlinedButton(
child: Text('Remove!'),
onPressed: () {
ScaffoldMessenger.of(context).removeCurrentSnackBar(
reason: SnackBarClosedReason.action
);
},
),
Output:
Control SnackBar
Appearance
What if you want to display a SnackBar
on particular Scaffold
s only. For example, you want to show the SnackBar
from PageOne
on that page only and not displayed when the user is on PageTwo
, even if the display duration has not over yet. If you want that behavior, you can instantiate your own ScaffoldMessenger
instead of using the MaterialApp
's root ScaffoldMessenger
.
Let's change the PageOne
widget above by wrapping it as the child of a ScaffoldMessenger
and leave the PageTwo
widget as it is now.
class PageOneWithOwnScaffoldMessenger extends StatelessWidget {
final SnackBar _snackBar = SnackBar(
content: const Text('Message from page one'),
duration: const Duration(seconds: 5),
);
@override
Widget build(BuildContext context) {
return ScaffoldMessenger(
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.teal,
title: Text('Woolha.com - Page one'),
),
body: Builder(
builder: (BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
OutlinedButton(
child: Text('Go to page two'),
onPressed: () {
Navigator.pushNamed(context, '/two');
},
),
OutlinedButton(
child: Text('Show message!'),
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(_snackBar);
},
),
],
),
);
},
),
),
);
}
}
By creating a different ScaffoldMessenger
and get its state (instead of using the MaterialApp
's ScaffoldMessenger
), the SnackBar
is only visible on the Scaffold
of PageOne
.
Output:
Be careful to use the appropriate BuildContext
when calling ScaffoldMessenger.of(context)
. In the above code, I use the BuildContext
passed when the builder
method of the Builder
widget is invoked. Therefore, the context is under the PageOne
's ScaffoldMessenger
widget in the tree. Otherwise, if the used BuildContext
is the one from Widget build(BuildContext context)
, the context is above the PageOne
's ScaffoldMessenger
widget in the tree and hence the ScaffoldMessenger.of
will return the MaterialApp
's ScaffoldMessengerState
. As the result, the SnackBar
is still displayed on PageTwo
, but not on PageOne
, which is not expected.
Use Key
to Get ScaffoldMessengerState
The examples above use ScaffoldMessenger.of
method to get the nearest instance of ScaffoldMessengerState
. There is another way to get ScaffoldMessengerState
. What you need to do is pass the key
argument when calling the constructor of ScaffoldMessenger
. For example, you can create a GlobalKey
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
Then pass it as the key
argument.
ScaffoldMessenger(
key: scaffoldMessengerKey,
child: ...
);
And you can get the state from the key's currentState
property.
scaffoldMessengerKey.currentState.showSnackBar(_snackBar)
If you want to get the root ScaffoldMessengerState
of the MaterialApp
, first you need to create a key for it.
final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
Then, pass it as scaffoldMessengerKey
argument when calling the constructor of MaterialApp
.
MaterialApp(
scaffoldMessengerKey: rootScaffoldMessengerKey,
...
)
Full Code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
final GlobalKey rootScaffoldMessengerKey = GlobalKey();
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
scaffoldMessengerKey: rootScaffoldMessengerKey,
initialRoute: '/one',
routes: {
// replace [PageOne] with [PageOneWithOwnScaffoldMessenger] for example
// that creates a different [ScaffoldMessenger]
'/one': (context) => PageOne(),
'/two': (context) => PageTwo(),
},
);
}
}
class PageOne extends StatelessWidget {
final SnackBar _snackBar = SnackBar(
content: const Text('Message from page one'),
duration: const Duration(seconds: 5),
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.teal,
title: Text('Woolha.com - Page one'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
OutlinedButton(
child: Text('Go to page two'),
onPressed: () {
Navigator.pushNamed(context, '/two');
},
),
OutlinedButton(
child: Text('Show message!'),
onPressed: () {
// You can replace the code below with rootScaffoldMessengerKey.currentState.showSnackBar(_snackBar);
ScaffoldMessenger.of(context).showSnackBar(_snackBar);
},
),
],
),
),
);
}
}
class PageOneWithOwnScaffoldMessenger extends StatelessWidget {
final GlobalKey scaffoldMessengerKey = GlobalKey();
final SnackBar _snackBar = SnackBar(
content: const Text('Message from page one'),
duration: const Duration(seconds: 5),
);
@override
Widget build(BuildContext context) {
return ScaffoldMessenger(
key: scaffoldMessengerKey,
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.teal,
title: Text('Woolha.com - Page one'),
),
body: Builder(
builder: (BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
OutlinedButton(
child: Text('Go to page two'),
onPressed: () {
Navigator.pushNamed(context, '/two');
},
),
OutlinedButton(
child: Text('Show message!'),
onPressed: () {
// You can replace the code below with scaffoldMessengerKey.currentState.showSnackBar(_snackBar);
// and you don't need to wrap this inside the [Builder] widget.
scaffoldMessengerKey.currentState.showSnackBar(_snackBar);
},
),
],
),
);
},
),
),
);
}
}
class PageTwo extends StatelessWidget {
final SnackBar _snackBar = SnackBar(
content: const Text('Message from page two'),
duration: const Duration(seconds: 5),
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.teal,
title: Text('Woolha.com - Page Two'),
),
body: Builder(
builder: (BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
OutlinedButton(
child: Text('Go to page one'),
onPressed: () {
Navigator.pop(context);
},
),
OutlinedButton(
child: Text('Show message!'),
onPressed: () {
// You can replace the code below with rootScaffoldMessengerKey.currentState.showSnackBar(_snackBar);
ScaffoldMessenger.of(context).showSnackBar(_snackBar);
},
),
OutlinedButton(
child: Text('Hide!'),
onPressed: () {
print(ScaffoldMessenger.of(context));
// You can replace the code below with rootScaffoldMessengerKey.currentState.hideCurrentSnackBar();
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
),
OutlinedButton(
child: Text('Remove!'),
onPressed: () {
// You can replace the code below with rootScaffoldMessengerKey.currentState.removeCurrentSnackBar();
ScaffoldMessenger.of(context).removeCurrentSnackBar();
},
),
],
),
);
}
),
);
}
}
Summary
That's how to manage SnackBar
in Flutter. The MaterialApp
provides a ScaffoldMessenger
which can be used to make a SnackBar
persistent across its descendant Scaffold
s. However, you control which descendants can receive a SnackBar
by creating your own ScaffoldMessenger
.