How to show errors from ChangeNotifier using Provider in Flutter How to show errors from ChangeNotifier using Provider in Flutter dart dart

How to show errors from ChangeNotifier using Provider in Flutter


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:

1) extend your ChangeNotifier with MessageNotifierMixin that gives your ChangeNotifier two properties, error and info, and two methods, notifyError() and notifyInfo().

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


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),          ),        ],      ),    );  }}