如何在 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
提供两个属性error
和info
,以及两个方法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 更改
如何在 Flutter 中将 textEditiing 控制器与 Provider 一起使用