How to show errors from ChangeNotifier using Provider in Flutter
Solution 1
Edit 2022
I ported (and reworked) this package also for river pod if anyone is interested
https://pub.dev/packages/riverpod_messages/versions/1.0.0
EDIT 2020-06-05
I developed a slightly better approach to afford this kink of situations.
It can be found at This repo on github so you can see the implementation there, or use this package putting in your pubspec.yaml
provider_utilities:
git:
url: https://github.com/quantosapplications/flutter_provider_utilities.git
So when you need to present messages to the view you can:
-
extend your
ChangeNotifier
withMessageNotifierMixin
that gives yourChangeNotifier
two properties,error
andinfo
, and two methods,notifyError()
andnotifyInfo()
. -
Wrap your Scaffold with a MessageListener that will present a Snackbar when it gets called notifyError() or NotifyInfo()
I'll give you an example:
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');
}
}
}
View
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])
),
),
)
)
);
}
}
OLD ANSWER
thank you.
Maybe I found a simpler way to handle this, using the powerful property "child" of Consumer.
With a custom stateless widget (I called it ErrorListener but it can be changed :))
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();
}
}
This widget must be put under a scaffold if you want to use a snackbar.
I use a mixin here to be sure that model has a error
property and a clarError()
method.
mixin ErrorNotifierMixin on ChangeNotifier {
String _error;
String get error => _error;
void notifyError(dynamic error) {
_error = error.toString();
notifyListeners();
}
void clearError() {
_error = null;
}
}
So for example we can use this way
class _PageState extends State<Page> {
// ...
@override
Widget build(BuildContext context) =>
ChangeNotifierProvider(
builder: (context) => MyModel(),
child: Scaffold(
body: ErrorListener<MyModel>(
child: MyBody()
)
)
);
}
Solution 2
You can create a custom StatelessWidget
to launch the snackbar when the view model changes. For example:
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);
}
}
We can only display the snackbar once all widgets are built, that's why we have the WidgetsBinding.instance.addPostFrameCallback()
call above.
Now we can add SnackBarLauncher
to our screen:
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),
),
],
),
);
}
}
Fabrizio Tognetto
Updated on December 01, 2022Comments
-
Fabrizio Tognetto over 1 year
I'm trying to find the best way to show errors from a Change Notifier Model with Provider through a Snackbar.
Is there any built-in way or any advice you could help me with?
I found this way that is working but I don't know if it's correct.
Suppose I have a simple Page where I want to display a list of objects and a Model where I retrieve those objects from api. In case of error I notify an error String and I would like to display that error with a 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(); } } }
Thank you
-
Henrique over 4 yearsThat's a nice approach! Thanks for sharing :)
-
Ignacio Giagante almost 4 yearsI like this approach, but what about if I use MultiProvider? That would changes a lot or not?
-
Fabrizio Tognetto almost 4 years@IgnacioGiagante I currently use this with MultiProvider and there's no problem about that. You can listen to one or more changenotifiers wrapping your scaffold with the listener. I developed a slightly better version of this approach that you can find in github.com/quantosapplications/flutter_provider_utilities. You can extend ChangeNotifier with MessageNotifierMixin that gives you notifyError and notifyInfo methods. Than you can wrap a Scaffold with MessageListener that will listen and present a snacker when you notify error or info
-
SuperCode over 3 yearsHave you considered publishing this as a package on pub.dev