【问题标题】:How to show errors from ChangeNotifier using Provider in Flutter如何在 Flutter 中使用 Provider 显示来自 ChangeNotifier 的错误
【发布时间】: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();
    }
  }

}

谢谢

【问题讨论】:

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

标签: flutter dart flutter-layout flutter-provider provider-model


【解决方案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),
          ),
        ],
      ),
    );
  }
}

【讨论】:

    猜你喜欢
    • 2021-01-21
    • 2020-12-17
    • 1970-01-01
    • 2020-11-03
    • 1970-01-01
    • 2021-07-20
    • 2021-01-29
    • 2020-09-05
    • 2020-12-02
    相关资源
    最近更新 更多