如何在 Flutter 中使用 Provider 显示来自 ChangeNotifier 的错误
How to show errors from ChangeNotifier using Provider in Flutter
我正在尝试找到通过 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();
}
}
}
谢谢
您可以创建自定义 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),
),
],
),
);
}
}
编辑 2022
如果有人感兴趣的话,我也为 river pod 移植(并重新制作)了这个包
https://pub.dev/packages/riverpod_messages/versions/1.0.0
编辑 2020-06-05
我开发了一种稍微好一点的方法来应对这种情况。
它可以在 This repo on github 找到,所以你可以在那里看到实现,或者使用这个包放入你的 pubspec.yaml
provider_utilities:
git:
url: https://github.com/quantosapplications/flutter_provider_utilities.git
因此,当您需要向视图显示消息时,您可以:
使用 MessageNotifierMixin
扩展您的 ChangeNotifier
,从而为您的 ChangeNotifier
提供两个属性,error
和 info
,以及两个方法, notifyError()
和 notifyInfo()
.
用 MessageListener 包装您的 Scaffold,当它被调用时会显示一个 Snackbar notifyError() 或 NotifyInfo()
我举个例子:
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])
),
),
)
)
);
}
}
旧答案
谢谢。
也许我找到了一种更简单的方法来处理这个问题,使用强大的 属性“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()
)
)
);
}
我正在尝试找到通过 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();
}
}
}
谢谢
您可以创建自定义 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),
),
],
),
);
}
}
编辑 2022
如果有人感兴趣的话,我也为 river pod 移植(并重新制作)了这个包
https://pub.dev/packages/riverpod_messages/versions/1.0.0
编辑 2020-06-05
我开发了一种稍微好一点的方法来应对这种情况。
它可以在 This repo on github 找到,所以你可以在那里看到实现,或者使用这个包放入你的 pubspec.yaml
provider_utilities:
git:
url: https://github.com/quantosapplications/flutter_provider_utilities.git
因此,当您需要向视图显示消息时,您可以:
使用
MessageNotifierMixin
扩展您的ChangeNotifier
,从而为您的ChangeNotifier
提供两个属性,error
和info
,以及两个方法,notifyError()
和notifyInfo()
.用 MessageListener 包装您的 Scaffold,当它被调用时会显示一个 Snackbar notifyError() 或 NotifyInfo()
我举个例子:
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])
),
),
)
)
);
}
}
旧答案
谢谢。
也许我找到了一种更简单的方法来处理这个问题,使用强大的 属性“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()
)
)
);
}