baby.zard
baby.zard

Reputation: 234

Global snackbar/dialog utils class in flutter

I want to create a GlobalMessageUtils class that would open a material snackbar or dialog without having to pass the build context. The idea is that whenever there's any error (no network, bad request, etc) I am able to pop open a snackbar and relay the message to the user.Is there a concept of global context?

I was playing with the idea of making my GlobalMessageUtils class a singleton that takes in a build context and instantiate it at the MaterialApp level, but I haven't gotten this to work. Any body have any ideas? Is this even a good pattern in flutter? If not, how do you guys deal with error handling at a global level?

Upvotes: 9

Views: 4677

Answers (4)

Nagual
Nagual

Reputation: 2089

  1. define global variable e.g. inside main.dart
final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
  1. MaterialApp widget has scaffoldMessengerKey property so set this key to property
return MaterialApp(
  debugShowCheckedModeBanner: false,
  scaffoldMessengerKey: rootScaffoldMessengerKey,
  home: Scaffold(),
);
  1. now you're able to show SnackBar from any place in app
rootScaffoldMessengerKey.currentState?.showSnackBar(SnackBar(content: Text('some text')));

Upvotes: 5

Marty Spallone
Marty Spallone

Reputation: 51

For a Provider solution if you are using BaseWidget, then you can just create a base class method and use it everywhere you are using provider.

    class BaseWidget<T extends ChangeNotifier> extends StatefulWidget {
  final Widget Function(BuildContext context, T model, Widget child) builder;
.
.
.

showToast(BuildContext context, String message, {int durationSeconds = 5}) {
  final ScaffoldMessengerState scaffoldMessenger =
      ScaffoldMessenger.of(context);
  scaffoldMessenger.showSnackBar(SnackBar(
      content: Text(message),
      duration: Duration(seconds: durationSeconds),
      action: SnackBarAction(
        label: 'HIDE',
        onPressed: () {
          scaffoldMessenger.hideCurrentSnackBar();
        },
      )));
}

Then just call it from your views.

.
.
.
showToast(context, "Factor Model Created Successfully",
          durationSeconds: 30);
          Navigator.pop(context, 'save');

Upvotes: 0

Ephenodrom
Ephenodrom

Reputation: 1893

I wrote a Package that supports application wide message display.

EZ Flutter supports displaying a message to the user from anywhere inside the app with just one line of code. Global Messaging is handled with a BLOC and widget added as the body of a Scaffold.

Github : https://github.com/Ephenodrom/EZ-Flutter

dependencies:
  ez_flutter: ^0.2.0

Add the message wrapper

Add the EzGlobalMessageWrapper as the body to a Scaffold.

Scaffold{
  appBar: ...
  body: EzGlobalMessageWrapper(
    MyWidget(
      ...
    )
  )
}

Add message to the bloc

Load the EzMessageBloc via the EzBlocProvider using the get method.

Add a EzMessage to the bloc. The supported EzMessageTypes are :

  • SUCCESS (default color : Colors.green)
  • INFO (default color : Colors.blue)
  • WARNING (default color : Colors.orange)
  • ERROR (default color : Colors.red)
    EzBlocProvider.of<EzGlobalBloc>(context)
        .get<EzMessageBloc>(EzMessageBloc)
        .addition
        .add(EzMessage("This is a success message", EzMessageType.SUCCESS));

https://github.com/Ephenodrom/EZ-Flutter/blob/master/documentation/GLOBAL_MESSAGE.md

Upvotes: -1

baby.zard
baby.zard

Reputation: 234

Using the BLOC pattern and Rxdart, I created a UiErrorUtils class

class UiErrorUtils {
 // opens snackbar
  void openSnackBar(BuildContext context, String message) async {
    await Scaffold.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
      ),
    );
  }
  // subscribes to stream that triggers open snackbar
  void subscribeToSnackBarStream(BuildContext context, PublishSubject<String> stream){
    stream.listen((String message){
      openSnackBar(context, message);
    });
  }
}

In your StatefulWidget, you can use the context provided in the initState hook:

class WidgetThatUsesUIErrorUtils extends StatefulWidget {
  final UiErrorUtils uiErrorUtils;
  final  Bloc bloc;

  WidgetThatUsesUIErrorUtils({this.uiErrorUtils, this.bloc});

  WidgetThatUsesUIErrorUtils createState() => WidgetThatUsesUIErrorUtilsState(
        uiErrorUtils: uiErrorUtils,
        bloc: bloc,
      );
}

class WidgetThatUsesUIErrorUtilsState extends State<WidgetThatUsesUIErrorUtils> {
  final Bloc _bloc;
  final UiErrorUtils _uiErrorUtils;

  WidgetThatUsesUIErrorUtilsState({Bloc bloc, UiErrorUtils uiErrorUtils})
      : _bloc = bloc ?? Bloc(),
        _uiErrorUtils = uiErrorUtils ?? UiErrorUtils();

  @override
  void initState() {
    super.initState();
    // Subscribe to UI feedback streams from  provided _bloc
    _uiErrorUtils.subscribeToSnackBarStream(context, _bloc.snackBarSubject);

  }

}

BLOC

class Bloc extends BlocBase {
  // UI Feedback Subjects
  final PublishSubject<String> snackBarSubject = PublishSubject<String>();

  //  some function that gets data from network
  Future<bool> getDataRequest() async {
     try {
      // get request code here
      } catch(error) {
      this.snackBarSubject.add(error);
    }

  }

  @override
  void dispose() {
    snackBarSubject?.close();
  }
}

Now your widget has subscribed to the bloc's snackBarStream. So in your bloc whenever a request fails you can add the message to the snackBarStream and since your widget has subscribed via UiErrorUtils the snackbar will trigger with the message.

Upvotes: 4

Related Questions