Imagine I'm using a bloc to handle a network request. If the request fails, the way to handle the failure would be different depending on the platform. On my web app, I would like to redirect the user to an error page while on my IOS app I would like to show a dialog.
As bloc should only be used and shared to handle the business logic, and the error handling part has nothing to do with the business logic, we should ask the UI part to take care of the error handling.
The UI can send error callback to the bloc and the bloc will run it when an error happens. We can also handle the error in a platform-specific way by sending different callbacks in different platforms.
Then there come my two questions:
In flutter, we only have access to bloc after the initState
life cycle method(for we get bloc from builder context, which only comes after initState
). Then we can only send callback in the build method.
In this way, we will repetitively send callback to bloc every time rebuilding happens(these repetitions make no sense). With react, such one-time initialization could be done in life cycles such as componentDidMount
. In flutter how do we reach the goal of running these initialization only once?
Errors that don't occur within Flutter's callbacks can't be caught by the framework, but you can handle them by setting up a Zone . All errors caught by Flutter are routed to the FlutterError. onError handler. By default, this calls FlutterError.
The UI/Flutter layer can only talk to the BLoC layer. The BLoC layer receives input events, processes business logic using the data layer and responds with output events to the UI layers or other listeners. This structure can scale nicely as the app grows.
Bloc is a good pattern that will be suitable for almost all types of apps. It helps improve the code's quality and makes handling states in the app much more manageable. It might be challenging for someone who is just beginning to use Flutter because it uses advanced techniques like Stream and Reactive Programming.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With