13

我正在尝试找到通过 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();
    }
  }

}

谢谢

4

2 回答 2

12

编辑 2022

如果有人感兴趣,我也将这个包移植(并重新设计)用于河荚

https://pub.dev/packages/riverpod_messages/versions/1.0.0

编辑 2020-06-05

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

它可以在 github 上的 This repo中找到,因此您可以在那里查看实现,或者使用这个包放入您的 pubspec.yaml

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

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

  1. 扩展你的ChangeNotifierwithMessageNotifierMixin给你的ChangeNotifier两个属性,errorinfo,和两个方法,notifyError()notifyInfo()

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

我给你举个例子:

更改通知程序

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

}
于 2019-11-07T07:36:31.200 回答
6

您可以创建自定义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),
          ),
        ],
      ),
    );
  }
}
于 2019-11-06T08:52:26.243 回答