如何在 Flutter 中使用 Provider 显示来自 ChangeNotifier 的错误

Posted

技术标签:

【中文标题】如何在 Flutter 中使用 Provider 显示来自 ChangeNotifier 的错误【英文标题】:How to show errors from ChangeNotifier using Provider in Flutter 【发布时间】:2020-02-29 06:38:05 【问题描述】:

我正在尝试找到通过 Snackbar 显示来自具有提供程序的更改通知模型的错误的最佳方法。

有什么内置方法或建议可以帮助我吗?

我发现这种方式可行,但我不知道它是否正确。

假设我有一个简单的页面,我想在其中显示对象列表和一个模型,我从 api 中检索这些对象。如果出现错误,我会通知错误字符串,我想用 SnackBar 显示该错误。

page.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class Page extends StatefulWidget 
  Page(Key key) : super(key: key);

  @override
  _PageState createState() => _PageState();


class _PageState extends State< Page > 

  @override
  void initState()
    super.initState();
    Provider.of<Model>(context, listen: false).load();
  

  @override
  void didChangeDependencies() 
    super.didChangeDependencies();
    Provider.of< Model >(context, listen: false).addListener(_listenForErrors);
  

  @override
  Widget build(BuildContext context)
    super.build(context);
    return Scaffold(
      appBar: AppBar(),
      body: Consumer<Model>(
          builder: (context, model, child)
  
            if(model.elements != null)
              ...list
            
            else return LoadingWidget();
          
        )
      )
    );
  



  void _listenForErrors()
    final error = Provider.of<Model>(context, listen: false).error;
    if (error != null) 
      Scaffold.of(context)
        ..hideCurrentSnackBar()
        ..showSnackBar(
          SnackBar(
            backgroundColor: Colors.red[600],
            content: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Icon(Icons.error),
                Expanded(child: Padding( padding:EdgeInsets.only(left:16), child:Text(error) )),
              ],
            ),
          ),
        );
    
  

  @override
  void dispose()  
        Provider.of<PushNotificationModel>(context, listen: false).removeListener(_listenForErrors);
    super.dispose();
  


page_model.dart

import 'package:flutter/foundation.dart';

class BrickModel extends ChangeNotifier 

  List<String> _elements;
  List<String> get elements => _elements;

  String _error;
  String get error => _error;

  Future<void> load() async 
    try
      final elements = await someApiCall();
      _elements = [..._elements, ...elements];
    
    catch(e) 
      _error = e.toString();
    
    finally 
      notifyListeners();
    
  


谢谢

【问题讨论】:

我昨天也遇到了同样的问题。使用提供程序时,我想不出一种干净的方式来显示小吃吧。 【参考方案1】:

编辑 2020-06-05

我开发了一种更好的方法来应对这种情况。

它可以在This repo on github 找到,所以你可以在那里看到实现,或者使用这个包放在你的 pubspec.yaml 中

 provider_utilities:
    git:
      url: https://github.com/quantosapplications/flutter_provider_utilities.git

因此,当您需要向视图显示消息时,您可以:

1) 使用MessageNotifierMixin 扩展您的ChangeNotifier,从而为您的ChangeNotifier 提供两个属性errorinfo,以及两个方法notifyError()notifyInfo()

2) 用 MessageListener 包裹你的 Scaffold,当它被调用 notifyError() 或 NotifyInfo() 时会显示一个 Snackbar

我给你举个例子:

ChangeNotifier

import 'package:flutter/material.dart';
import 'package:provider_utilities/provider_utilities.dart';

class MyNotifier extends ChangeNotifier with MessageNotifierMixin 

  List<String> _properties = [];
  List<String> get properties => _properties;

  Future<void> load() async 

    try 
      /// Do some network calls or something else
      await Future.delayed(Duration(seconds: 1), ()

        _properties = ["Item 1", "Item 2", "Item 3"];
        notifyInfo('Successfully called load() method');

      );
    
    catch(e) 
      notifyError('Error calling load() method');
    

  


查看

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_utilities/provider_utilities.dart';

import 'notifier.dart';

class View extends StatefulWidget 
  View(Key key) : super(key: key);

  @override
  _ViewState createState() => _ViewState();


class _ViewState extends State<View> 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(),
      body: MessageListener<MyNotifier>(
        child: Selector<MyNotifier, List<String>>(
          selector: (ctx, model) => model.properties,
          builder: (ctx, properties, child) => ListView.builder(
            itemCount: properties.length,
            itemBuilder: (ctx, index) => ListTile(
              title: Text(properties[index])
            ),
          ),
        )
      )
    );
  

老答案

谢谢。

也许我找到了一种更简单的方法来处理这个问题,使用 Consumer 的强大属性“child”。

使用自定义无状态小部件(我称它为 ErrorListener,但它可以更改 :))

class ErrorListener<T extends ErrorNotifierMixin> extends StatelessWidget 

  final Widget child;

  const ErrorListener(Key key, @required this.child) : super(key: key);

  @override
  Widget build(BuildContext context) 
    return Consumer<T>(
      builder: (context, model, child)

        //here we listen for errors
        if (model.error != null)  
          WidgetsBinding.instance.addPostFrameCallback((_)
             _handleError(context, model); );
        

        // here we return child!
        return child;
      ,
      child: child
    );
  


  // this method will be called anytime an error occurs
  // it shows a snackbar but it could do anything you want
  void _handleError(BuildContext context, T model) 
    Scaffold.of(context)
    ..hideCurrentSnackBar()
    ..showSnackBar(
      SnackBar(
        backgroundColor: Colors.red[600],
        content: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Icon(Icons.error),
            Expanded(child: Padding( padding:EdgeInsets.only(left:16), child:Text(model.error) )),
          ],
        ),
      ),
    );

    // this will clear the error on model because it has been handled
    model.clearError();
  

如果你想使用小吃店,这个小部件必须放在脚手架下。

我在这里使用 mixin 来确保模型具有 error 属性和 clarError() 方法。

mixin ErrorNotifierMixin on ChangeNotifier 
  String _error;
  String get error => _error;

  void notifyError(dynamic error) 
    _error = error.toString();
    notifyListeners();
  

  void clearError() 
    _error = null;
  

所以例如我们可以这样使用

class _PageState extends State<Page> 

   // ...

@override 
  Widget build(BuildContext context) =>
    ChangeNotifierProvider(
      builder: (context) => MyModel(),
      child: Scaffold(
        body: ErrorListener<MyModel>(
          child: MyBody()
        )
      )
    );


【讨论】:

这是一个不错的方法!感谢分享:) 我喜欢这种方法,但是如果我使用 MultiProvider 呢?这会改变很多吗? @IgnacioGiagante 我目前将它与 MultiProvider 一起使用,这没有问题。您可以使用侦听器收听一个或多个将脚手架包裹起来的变更通知器。我开发了这种方法的稍微好一点的版本,您可以在 github.com/quantosapplications/flutter_provider_utilities 中找到它。您可以使用为您提供 notifyError 和 notifyInfo 方法的 MessageNotifierMixin 扩展 ChangeNotifier。比你可以用 MessageListener 包装一个 Scaffold,当你通知错误或信息时,它会监听并呈现一个snacker 你有没有考虑将它作为一个包发布到 pub.dev【参考方案2】:

您可以创建自定义StatelessWidget 以在视图模型更改时启动快餐栏。例如:

class SnackBarLauncher extends StatelessWidget 
  final String error;

  const SnackBarLauncher(
      Key key, @required this.error)
      : super(key: key);

  @override
  Widget build(BuildContext context) 
    if (error != null) 
      WidgetsBinding.instance.addPostFrameCallback(
          (_) => _displaySnackBar(context, error: error));
    
    // Placeholder container widget
    return Container();
  

  void _displaySnackBar(BuildContext context, @required String error) 
    final snackBar = SnackBar(content: Text(error));
    Scaffold.of(context).hideCurrentSnackBar();
    Scaffold.of(context).showSnackBar(snackBar);
  


我们只能在构建所有小部件后显示小吃栏,这就是我们在上面调用 WidgetsBinding.instance.addPostFrameCallback() 的原因。

现在我们可以在屏幕上添加SnackBarLauncher

class SomeScreen extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'Title',
        ),
      ),
      body: Stack(
        children: [
          // Other widgets here...

          Consumer<EmailLoginScreenModel>(
            builder: (context, model, child) =>
                SnackBarLauncher(error: model.error),
          ),
        ],
      ),
    );
  

【讨论】:

以上是关于如何在 Flutter 中使用 Provider 显示来自 ChangeNotifier 的错误的主要内容,如果未能解决你的问题,请参考以下文章

Flutter:如何在 Flutter 中使用 Switch 更改主题 - 我已经使用 Provider 实现了这个明暗主题,但不能使用 switch 更改

如何使用 Provider 实现 Flutter 本地化?

如何在 Flutter 中将 textEditiing 控制器与 Provider 一起使用

如何在 Flutter 中正确重用 Provider

如何使用 Flutter 中的 Provider 增加电子商务购物车中的商品数量

Flutter - Provider - 如何根据 Provider 的值更改 UI?