打开 SnackBar 内部函数发送到 AppBar

Posted

技术标签:

【中文标题】打开 SnackBar 内部函数发送到 AppBar【英文标题】:Open SnackBar inside function sent to AppBar 【发布时间】:2020-09-30 04:13:06 【问题描述】:

我有一个带有表单的页面。一旦用户点击保存,它应该会显示一个 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);

非常感谢任何帮助/指导!

【问题讨论】:

【参考方案1】:

一旦用户点击保存,它应该会显示一个 SnackBar

但是你的 save 方法处理了脚手架并返回到上一页,所以它不显示小吃栏(脚手架没有安装在下一帧中)

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

如果你真的想显示一个 Scaffold,你应该使用你希望它显示的 ScaffoldWidget 的 GlobalKey(在上一页的例子中)。还要避免在 build 方法中创建 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 稳定版

2.0稳定版发布ScaffoldMessenger.of(context)后不再需要之前的逻辑

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

【讨论】:

非常感谢!这有帮助。发送以前的脚手架钥匙就可以了! :) 只有在调用 Scaffold 的 currentState 的函数不在 AppBar 中时才有效。 我不明白你说它在 appbar 上不起作用是什么意思,但是这段代码已经被弃用了,应该使用 ScaffoldMessenger 来避免进一步的问题【参考方案2】:

问题是您正在显示小吃栏,然后弹出屏幕。您可以做的是在推送屏幕的页面上等待,然后在那里显示小吃栏。在颤振文档中有一个类似的用例示例:

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

查看完整示例here。

【讨论】:

是的,你完全正确。但是,我想在此页面中显示 SnackBar,而不返回上一页。因此,我从上一页传递了 ScaffoldKey 并使用了它。【参考方案3】:

另一种不需要您使用必须有脚手架的小吃店的快速方法是使用名为 Flushbar 的包。它真的很简单,并且消除了所有样板代码 Flushbar Link

【讨论】:

感谢您的意见。我实际上想使用 SnackBar(不是替代品)【参考方案4】:

在自定义 AppBar 文件中,更改

final Function saveAndAddNew;
final Function save;

final void Function() saveAndAddNew;
final void Function() save; 

【讨论】:

即使我改变了这个,由于页面被弹出,脚手架不会显示。因此,我使用了发送前几页的 ScaffoldKey 并在其中使用它的方法。成功了!

以上是关于打开 SnackBar 内部函数发送到 AppBar的主要内容,如果未能解决你的问题,请参考以下文章

从服务内部显示 Snackbar

如何将变量从 JavaScript 函数发送到文本框? [复制]

如何将 jchararray 作为参数发送到 C 函数

如何动态地将变量从一个函数发送到另一个函数

如何将具有值的函数发送到子组件 React?

将状态更新从 C++ 中的函数发送到 C#