How to do error handling with bloc pattern in flutter?
This is how we handle it in my team:
First we build our main page (The navigation root) like this:
@override Widget build(BuildContext context) { return BlocBuilder<SuspectEvent, SuspectState>( bloc: _bloc, builder: (context, state) { if (state.cameras.isEmpty) _bloc.dispatch(GetCamerasEvent()); if (!_isExceptionHandled) { _shouldHandleException( hasException: state.hasException, handleException: state.handleException); } return Scaffold( ...
We declare the _shouldHandleException
like this (still on the main page):
_shouldHandleException( {@required bool hasException, @required Exception handleException}) { if (hasException) { if (handleException is AuthenticationException) { _isExceptionHandled = true; SchedulerBinding.instance.addPostFrameCallback((_) async { InfoDialog.showMessage( context: context, infoDialogType: DialogType.error, text: 'Please, do your login again.', title: 'Session expired') .then((val) { Navigator.popUntil(context, ModalRoute.withName('/')); this._showLogin(); }); }); } else if (handleException is BusinessException) { _isExceptionHandled = true; SchedulerBinding.instance.addPostFrameCallback((_) async { InfoDialog.showMessage( context: context, infoDialogType: DialogType.alert, text: handleException.toString(), title: 'Verify your fields') .then((val) { _bloc.dispatch(CleanExceptionEvent()); _isExceptionHandled = false; }); }); } else { _isExceptionHandled = true; SchedulerBinding.instance.addPostFrameCallback((_) async { InfoDialog.showMessage( context: context, infoDialogType: DialogType.error, text: handleException.toString(), title: 'Error on request') .then((val) { _bloc.dispatch(CleanExceptionEvent()); _isExceptionHandled = false; }); }); } } }
On our block we have:
@override Stream<SuspectState> mapEventToState(SuspectEvent event) async* { try { if (event is GetCamerasEvent) { ... //(our logic) yield (SuspectState.newValue(state: currentState) ..cameras = _cameras ..suspects = _suspects); } ... //(other events) } catch (error) { yield (SuspectState.newValue(state: currentState) ..hasException = true ..handleException = error); } }
In our error handling (on main page) the InfoDialog
is just a showDialog
(from Flutter) and it gets on top of any route. So the alert just needed to be called on the root route.
You can access the BLoC in the initState
method if you wrap it in a scheduleMicrotask
method, so that it runs after the initState
method completed:
@overridevoid initState() { super.initState(); // Do initialization here. scheduleMicrotask(() { // Do stuff that uses the BLoC here. });}
You can also check out this answer to a different question outlining the Simple BLoC pattern, which just calls asynchronous methods directly on the BLoC instead of putting events into sinks.
That would allow code like this:
Future<void> login() { try { // Do the network stuff, like logging the user in or whatever. Bloc.of(context).login(userController.text, emailController.text); } on ServerNotReachableException { // Redirect the user, display a prompt or change this // widget's state to display an error. It's up to you. }}
You can use superEnum package to create states and events for a Bloc.(and here you will declare a state for the Error by doing this :
@Data(fields: [DataField<Error>('error')]) OrderLoadingFailedState,
(If anyone need an example of how to use it, please tell me i will show you an example)