Откройте SnackBar внутри функции, отправленной в AppBar

У меня есть страница с формой. Как только пользователь нажимает «Сохранить», должен отображаться SnackBar. Кнопка сохранения находится в отдельном настраиваемом виджете AppBar (в отдельном файле) и имеет 2 функции, которые отправляются со страницы с формой. AppBar отделен для многоразового использования.

Я пробовал использовать метод Builder, но он не работает. Затем я использовал метод Global Key, и он не выдаст мне ошибки, но 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,
    );
  }
}

Пользовательская панель приложений

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);
}

Любая помощь / руководство приветствуется!


person Charitha De Silva    schedule 10.06.2020    source источник


Ответы (4)


Как только пользователь нажимает кнопку «Сохранить», должен отображаться SnackBar.

но ваш метод сохранения избавляется от каркаса и возвращается на предыдущую страницу, поэтому на нем не отображается закусочная (подмости не монтируются в следующем кадре)

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

Если вы действительно хотите показать Scaffold, вы должны использовать GlobalKey ScaffoldWidget, который вы хотите отобразить (в данном случае на предыдущей странице). Также избегайте создания GlobalKey в методе сборки, он будет создавать новый каждый раз, когда вы вызываете setState. Вот пример с 2 страницами и 2 GloabalKeys, первая страница дает второй globalKey, чтобы она могла использовать его, если это необходимо.

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

Предыдущая логика больше не нужна после выпуска ScaffoldMessenger.of(context) в стабильной версии 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,
    );
  }
}
person EdwynZN    schedule 10.06.2020
comment
Большое спасибо! Это помогло. Отправка предыдущего ключа от эшафота сделала свое дело! :) - person Charitha De Silva; 11.06.2020
comment
Это работает только тогда, когда функция, вызывающая currentState скаффолда, не находится в AppBar. - person Richard McFriend Oluwamuyiwa; 28.03.2021
comment
Я не понимаю, что вы имеете в виду, когда говорите, что он не работает с панелью приложений, но этот код уже устарел, и вместо него следует использовать ScaffoldMessenger, чтобы избежать дальнейших проблем. - person EdwynZN; 29.03.2021

Проблема в том, что вы показываете закусочную, а затем открываете экран. Что вы можете сделать, так это дождаться на странице, которая толкает ваш экран, а затем отобразить там закусочную. В документации по flutter есть аналогичный пример вашего варианта использования:

_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")));
}

Ознакомьтесь с полным примером здесь.

person Sami Haddad    schedule 10.06.2020
comment
Да, вы абсолютно правы. Однако я хотел показать SnackBar на этой странице, не возвращаясь к предыдущей странице. Поэтому я передал ключ ScaffoldKey с предыдущей страницы и использовал его. - person Charitha De Silva; 11.06.2020

Еще один быстрый способ, который не требует использования закусочной, которая должна иметь подмостки, - это использовать пакет под названием Flushbar. Это действительно просто и исключает весь шаблонный код Ссылка на панель управления

person Codedruid13    schedule 10.06.2020
comment
Спасибо за вклад. Я действительно хотел использовать SnackBar (не альтернативу) - person Charitha De Silva; 11.06.2020

В настраиваемом файле AppBar измените

final Function saveAndAddNew;
final Function save;

to

final void Function() saveAndAddNew;
final void Function() save; 
person ByteMe    schedule 10.06.2020
comment
Несмотря на то, что я изменил это, Scaffold не будет отображаться из-за всплывающей страницы. Поэтому я использовал метод отправки ScaffoldKey предыдущих страниц и использовал его в этом. Это сработало! - person Charitha De Silva; 11.06.2020