0

I have a page with a form. Once the user clicks on Save, it should display a SnackBar. The save button is in a separate custom AppBar widget (in a separate file) and it has 2 functions that is sent from the page with the form. The AppBar is separated for reusable purposes.

I have tried to use the Builder method but it doesn't work. Then I used the Global Key method and it won't give me an error, but still no SnackBar.

import 'package:flutter/material.dart';

import '../models/author.dart';
import '../widgets/edit_app_bar.dart';

class AuthorEditPage extends StatefulWidget {
  static const PAGE_TITLE = 'Edit Author';
  static const ROUTE_NAME = '/author-edit';

  @override
  _AuthorEditPageState createState() => _AuthorEditPageState();
}

class _AuthorEditPageState extends State<AuthorEditPage> {
  @override
  Widget build(BuildContext context) {
    final _operation = ModalRoute.of(context).settings.arguments as String;
    final GlobalKey<ScaffoldState> _scaffoldKey =
        new GlobalKey<ScaffoldState>();

    Author _initialize() {
      return Author(
        id: null,
        name: 'Test',
        nameOther: null,
        facebook: null,
        website: null,
        lastUpdated: DateTime.now().toIso8601String(),
      );
    }

    void _displaySnackBar(Author author) {
      _scaffoldKey.currentState.showSnackBar(
        SnackBar(
          content: Text(
            'Author: ${author.name} added',
          ),
        ),
      );
    }

    return Scaffold(
      key: _scaffoldKey,
      appBar: EditAppBar(
        title: AuthorEditPage.PAGE_TITLE,
        saveAndAddNew: () async {
          Author author = _initialize();
          bool result = await author.createUpdateDelete(_operation);
          if (result) {
            _displaySnackBar(author);
            setState(() {});
          }
        },
        save: () async {
          Author author = _initialize();
          bool result = await author.createUpdateDelete(_operation);
          if (result) {
            _displaySnackBar(author);
            Navigator.of(context).pop();
          }
        },
      ),
      body: null,
    );
  }
}

The Custom AppBar

import 'package:flutter/material.dart';

class EditAppBar extends StatelessWidget with PreferredSizeWidget {
  EditAppBar({
    Key key,
    @required this.title,
    @required this.saveAndAddNew,
    @required this.save,
  }) : super(key: key);

  final String title;
  final Function saveAndAddNew;
  final Function save;

  @override
  Widget build(BuildContext context) {
    return AppBar(
      title: Text(
        title,
      ),
      actions: <Widget>[
        IconButton(
          icon: Icon(
            Icons.add,
          ),
          onPressed: saveAndAddNew,
        ),
        IconButton(
          icon: Icon(
            Icons.save,
          ),
          onPressed: save,
        ),
      ],
    );
  }

  @override
  Size get preferredSize => Size.fromHeight(kToolbarHeight);
}

Any help/guidance is highly appreciated!

4 Answers4

1

The problem is that you're showing the snackbar and then popping the screen. What you can do is await on the page that pushes your screen and then display the snackbar there. There's a similar example of your use case in the flutter docs:

_navigateAndDisplaySelection(BuildContext context) async {
  final result = await Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => SelectionScreen()),
  );

  // After the Selection Screen returns a result, hide any previous snackbars
  // and show the new result.
  Scaffold.of(context)
    ..removeCurrentSnackBar()
    ..showSnackBar(SnackBar(content: Text("$result")));
}

Check the complete example here.

Sami Haddad
  • 1,356
  • 10
  • 15
  • Yes, you're absolutely correct. However, I wanted to show the SnackBar within this page without returning to the previous page. Hence I passed the ScaffoldKey from the previous page and used it. – Charitha De Silva Jun 11 '20 at 11:47
1

Once the user clicks on Save, it should display a SnackBar

but your save method dispose of the scaffold and return to the previous page, so it doesn't show a snackbar (The Scaffold is not mounted in the next frame)

save: () async {
  Author author = _initialize();
  bool result = await author.createUpdateDelete(_operation);
  if (result) {
    _displaySnackBar(author);
    Navigator.of(context).pop(); //disposing the Scaffold widget 
  }
},

If you really want to show a Scaffold you should use the GlobalKey of the ScaffoldWidget you want it to display (in this case of the previous page). Also avoid creating the GlobalKey in the build method, it will create a new one each time you call setState. Here is an example with 2 pages and 2 GloabalKeys, the first page give the second one the globalKey so it can use it if its necesary.

class Page1 extends StatelessWidget{
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      body: Center(
       child: FlatButton(
         child: Text('SecondPage'),
         onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => AuthorEditPage(_scaffoldKey)))
       )
      )
    );
    
  }
  
}

class AuthorEditPage extends StatefulWidget {
  static const PAGE_TITLE = 'Edit Author';
  static const ROUTE_NAME = '/author-edit';
  final GlobalKey<ScaffoldState> previousScaffold;
  
  AuthorEditPage(this.previousScaffold);

  @override
  _AuthorEditPageState createState() => _AuthorEditPageState();
}

class _AuthorEditPageState extends State<AuthorEditPage> {
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); //initialize variables here
  
  Author _initialize() {
      return Author(
        id: null,
        name: 'Test',
        nameOther: null,
        facebook: null,
        website: null,
        lastUpdated: DateTime.now().toIso8601String(),
      );
    }
  
  //give the GlobalKey of the scaffold you want to display the snackbar
  void _displaySnackBar(Author author, GlobalKey<ScaffoldState> scaffold) {
      scaffold?.currentState?.showSnackBar(
        SnackBar(
          content: Text(
            'Author: ${author.name} added',
          ),
        ),
      );
    }
  
  @override
  Widget build(BuildContext context) {
  final _operation = ModalRoute.of(context).settings.arguments as String; //This one requires the context so it's fine to be here

  //Avoid creating objects or methods not related to the build method here, you can make them in the class
    return Scaffold(
      key: _scaffoldKey,
      appBar: EditAppBar(
        title: AuthorEditPage.PAGE_TITLE,
        saveAndAddNew: () async {
          Author author = _initialize();
          bool result = await author.createUpdateDelete(_operation);
          print(result);
          if (result) {
            _displaySnackBar(author, _scaffoldKey);
            setState(() {});
          }
        },
        save: () async {
          Author author = _initialize();
          bool result = await author.createUpdateDelete(_operation);
          print(result);
          if (result) {
            _displaySnackBar(author, widget.previousScaffold); //I sue the globalKey of the scaffold of the first page
            Navigator.of(context).pop();
          }
        },
      ),
      body: null,
    );
  }
}

Flutter 2.0 Stable

The previous logic is not needed anymore after the release of ScaffoldMessenger.of(context) in the stable version 2.0

class AuthorEditPage extends StatefulWidget {
  static const PAGE_TITLE = 'Edit Author';
  static const ROUTE_NAME = '/author-edit';
  //final GlobalKey<ScaffoldState> previousScaffold; not needed anymore

  AuthorEditPage();

  @override
  _AuthorEditPageState createState() => _AuthorEditPageState();
}

class _AuthorEditPageState extends State<AuthorEditPage> {
  //final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); //initialize variables here //not needed anymore

  Author _initialize() {
      return Author(
        id: null,
        name: 'Test',
        nameOther: null,
        facebook: null,
        website: null,
        lastUpdated: DateTime.now().toIso8601String(),
      );
    }

  @override
  Widget build(BuildContext context) {
  final _operation = ModalRoute.of(context).settings.arguments as String; //This one requires the context so it's fine to be here

  //Avoid creating objects or methods not related to the build method here, you can make them in the class
    return Scaffold(
      appBar: Builder( //Wrap it in a builder to get the context of the scaffold you're currently in
        builder (context) {
           return EditAppBar(
        title: AuthorEditPage.PAGE_TITLE,
        saveAndAddNew: () async {
          Author author = _initialize();
          bool result = await author.createUpdateDelete(_operation);
          print(result);
          if (result) {
            ScaffoldMessenger.maybeOf(context)?.showSnackBar(
              SnackBar(
                content: Text(
                 'Author: ${author.name} added',
                ),
              ),
            );
            setState(() {});
          }
        },
        save: () async {
          Author author = _initialize();
          bool result = await author.createUpdateDelete(_operation);
          print(result);
          if (result) {
            // It should keep the snackbar across pages
            ScaffoldMessenger.maybeOf(context)?.showSnackBar(
              SnackBar(
                content: Text(
                 'Author: ${author.name} added',
                ),
              ),
            );
            Navigator.of(context).pop();
          }
        },
      );
        }
      ),
      body: null,
    );
  }
}
EdwynZN
  • 4,895
  • 2
  • 12
  • 15
  • Thanks a lot! That helped. Sending the previous Scaffold Key did the trick! :) – Charitha De Silva Jun 11 '20 at 11:44
  • This only works when the function calling the currentState of the Scaffold is not in the AppBar. –  Mar 28 '21 at 20:02
  • I don't understand what you mean when you say it doesn't work wih the appbar, but this code is already deprecated and ScaffoldMessenger should be used instead to avoid further problems – EdwynZN Mar 28 '21 at 22:46
0

Another quick way that doesn't require you to use snackbar that must hav a scaffold is to use a package called Flushbar. It's really simple and eliminates all boilerplate code Flushbar Link

Codedruid13
  • 43
  • 1
  • 11
0

In the custom AppBar file, change

final Function saveAndAddNew;
final Function save;

to

final void Function() saveAndAddNew;
final void Function() save; 
ByteMe
  • 1,575
  • 1
  • 12
  • 23
  • Even though I changed this, the Scaffold would not be shown due to the page being popped. I hence used the method of sending the previous pages' ScaffoldKey and using it within this. That did the trick! – Charitha De Silva Jun 11 '20 at 11:46