Bloc: Is it possible to access the BuildContext from the BlocDelegate?

Created on 28 Jan 2020  路  4Comments  路  Source: felangel/bloc

I would like to use the BlocDelegate as a global error handler, using the onError(...) callback.

Specifically, I want to be able to display a snackbar with any error message for errors of a certain type, regardless of which Scaffold is active. It seems like this would be possible with a call to Scaffold.of(context).showSnackBar(...). However, the signature of onError is

void onError(Bloc bloc, Object error, StackTrace stacktrace);

Does anyone know of an approach for handling this?

question

Most helpful comment

Hi @barapa 馃憢
Thanks for opening an issue!

In general, I would recommend keeping the BlocDelegate platform-agnostic so that it can be reused. You can just inject an onError callback function into your BlocDelegate like

class MyBlocDelegate extends BlocDelegate {
  final void Function(Bloc, Object, StackTrace) onErrorCallback;

  MyBlocDelegate(this.onErrorCallback);

  @override
  void onError(Bloc bloc, Object error, StackTrace stacktrace) {
    super.onError(bloc, object, stacktrace);
    onErrorCallback?.call(bloc, error, stacktrace);
  }
}

Then in your App widget you can have something like:

class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
    BlocSupervisor.delegate = MyBlocDelegate(_onErrorCallback);
  }

  @override
  Widget build(BuildContext context) {...}

  void _onErrorCallback(Bloc bloc, Object Error, StackTrace stacktrace) {
    // you have access to BuildContext and can do whatever you'd like in response to errors
  }
}

Hope that helps 馃憤

All 4 comments

Hi @barapa 馃憢
Thanks for opening an issue!

In general, I would recommend keeping the BlocDelegate platform-agnostic so that it can be reused. You can just inject an onError callback function into your BlocDelegate like

class MyBlocDelegate extends BlocDelegate {
  final void Function(Bloc, Object, StackTrace) onErrorCallback;

  MyBlocDelegate(this.onErrorCallback);

  @override
  void onError(Bloc bloc, Object error, StackTrace stacktrace) {
    super.onError(bloc, object, stacktrace);
    onErrorCallback?.call(bloc, error, stacktrace);
  }
}

Then in your App widget you can have something like:

class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
    BlocSupervisor.delegate = MyBlocDelegate(_onErrorCallback);
  }

  @override
  Widget build(BuildContext context) {...}

  void _onErrorCallback(Bloc bloc, Object Error, StackTrace stacktrace) {
    // you have access to BuildContext and can do whatever you'd like in response to errors
  }
}

Hope that helps 馃憤

I'm not having much luck with that approach. This error handler is being defined in my root Widget, as suggested. When I get an error thrown in a bloc due to some action in a child widget, I get the following error:

E/flutter ( 4263): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: Scaffold.of() called with a context that does not contain a Scaffold.
E/flutter ( 4263): No Scaffold ancestor could be found starting from the context that was passed to Scaffold.of(). This usually happens when the context provided is from the same StatefulWidget as that whose build function actually creates the Scaffold widget being sought.
E/flutter ( 4263): There are several ways to avoid this problem. The simplest is to use a Builder to get a context that is "under" the Scaffold. For an example of this, please see the documentation for Scaffold.of():
E/flutter ( 4263):   https://api.flutter.dev/flutter/material/Scaffold/of.html
E/flutter ( 4263): A more efficient solution is to split your build function into several widgets. This introduces a new context from which you can obtain the Scaffold. In this solution, you would have an outer widget that creates the Scaffold populated by instances of your new inner widgets, and then in these inner widgets you would use Scaffold.of().
E/flutter ( 4263): A less elegant but more expedient solution is assign a GlobalKey to the Scaffold, then use the key.currentState property to obtain the ScaffoldState rather than using the Scaffold.of() function.

@barapa this is because you need to have a Scaffold in the parent context in order for this to work. In general you should not have global snack bars because they are designed to be used only on the current page. For a universal solution you should probably look into using overlays.

Thank you @felangel for your prompt assistance! For the time being, I will move the error handling to each individual bloc/widget.

Was this page helpful?
0 / 5 - 0 ratings