Flutter - Managing SnackBar with ScaffoldMessenger

This tutorial is about how to show, hide, or remove SnackBars in Flutter using ScaffoldMessenger.

Flutter 2 introduces a new widget called ScaffoldMessenger. It's a widget for managing SnackBars for descendant Scaffolds. It has the APIs for showing, hiding, and removing SnackBars. The introduction of ScaffoldMessnger replaces the old way of displaying SnackBars using ScaffoldMessenger.of(context) which has a few issues. With ScaffoldMessenger, now it becomes possible to make Snackbars 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 Scaffolds register so that they can receive SnackBars. Therefore, it allows SnackBars to persist across different Scaffolds.

The MaterialApp widget provides a root ScaffoldMessenger. If you show a SnackBar using the root ScaffoldMessenger, it becomes possible for all descendant Scaffolds to receive the SnackBar. However, you can control which Scaffolds 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 Scaffolds, 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 Scaffolds.

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:

Flutter - ScaffoldMessenger - Show

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:

Flutter - ScaffoldMessenger - Hide

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:

Flutter - ScaffoldMessenger - Remove

Control SnackBar Appearance

What if you want to display a SnackBar on particular Scaffolds 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:

Flutter - ScaffoldMessenger - Control Appearance

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 Scaffolds. However, you control which descendants can receive a SnackBar by creating your own ScaffoldMessenger.