show snackbar on fcm notification on every screen

2,544

Solution 1

The problem is with registering scaffolds for your NotificationManager widget since every time a new scaffold is added to the stack for a new screen, you need to register that screen's scaffold in the NotificationManager. This is because the line:

Scaffold.of(_context).showSnackBar(snackBar);

in your NoticicationManager will only look up the widget tree until the first scaffold it finds and call it there. Since you call NotificationManger.init(context: context); in your HomeScreen widget and pass the context of the HomeScreen, it will only live inside that scaffold. So, if you navigate away from the HomeScreen to a new widget with a different scaffold it will not have the NotificationManager as a child.

To fix the issue be sure you call Fcm.initConfigure(); in the first page that loads for the app, and for any pages you navigate to call NotificationManger.init(context: context); in either the initState() method for stateful widgets to register the current scaffold of that page or if they are stateless widgets, you can add it in the build method before returning the scaffold.

Solution 2

Copying the same code on all screens may not be ideal. If you have to change something, you will have to change everything or you will see inconsistencies. For snackbars, you can use this library and solve your problem in literally 1 line of code:

on your onMessage method:

onMessage(message){
Get.snackbar("message", message); // this line
}

Yes, the snackbar will be displayed regardless of the screen the user is on, and you won't need the scaffold's context, nor any context, just call the library method and that's it.

lib: https://pub.dev/packages/get

Share:
2,544
BeHappy
Author by

BeHappy

Trying to be a good TS Developer

Updated on December 19, 2022

Comments

  • BeHappy
    BeHappy over 1 year

    I want to show snackbar when in app notification arrive. But when I configure firebase on first screen, snackbar shows only when user is on that screen. I try to create a class to get BuildContext and show snackbar based on it but doesn't work and not show snackbar.


    This is my HomeScreen.dart:

    class _HomeScreenState extends State<HomeScreen> {
      @override
      void initState() {
        super.initState();
        Future.delayed(Duration.zero, () {
          NotificationManger.init(context: context);
          Fcm.initConfigure();
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return StoreConnector<AppState, Store<AppState>>(
          converter: (store) => store,
          onInit: (store) => initApp(store),
          builder: (context, store) {
            return BlocProvider<HomeBloc>(
              create: (context) {
                return HomeBloc(homeRepository: homeRepository)..add(ScreenOpened());
              },
              child: BlocListener<HomeBloc, HomeState>(
                listener: (context, state) async {},
                child: BlocBuilder<HomeBloc, HomeState>(
                  builder: (context, state) {
                    return Scaffold(
                      key: _scaffoldKey,
                      ...
                    );
                  },
                ),
              ),
            );
          },
        );
      }
    }
    

    This is my Fcm.dart

    Future<dynamic> myBackgroundMessageHandler(Map<String, dynamic> message) {
      if (message.containsKey('data')) {
        final dynamic data = message['data'];
      }
      if (message.containsKey('notification')) {
        final dynamic notification = message['notification'];
      }
    }
    
    class Fcm {
      static final FirebaseRepository repository = FirebaseRepository();
      static final FirebaseMessaging _fcm = FirebaseMessaging();
    
      static initConfigure() {
        if (Platform.isIOS) _iosPermission();
    
        _fcm.requestNotificationPermissions();
        _fcm.autoInitEnabled();
    
        _fcm.configure(
          onMessage: (Map<String, dynamic> message) async => NotificationManger.onMessage(message),
          onLaunch: (Map<String, dynamic> message) async => NotificationManger.onLaunch(message),
          onResume: (Map<String, dynamic> message) async => NotificationManger.onResume(message),
          onBackgroundMessage: myBackgroundMessageHandler,
        );
    
        _fcm.getToken().then((String token) {
          print('token: $token');
          repository.setUserNotifToken(token);
        });
      }
    
      static _iosPermission() {
        _fcm.requestNotificationPermissions(IosNotificationSettings(sound: true, badge: true, alert: true));
        _fcm.onIosSettingsRegistered.listen((IosNotificationSettings settings) {
          print("Settings registered: $settings");
        });
      }
    }
    

    and this is my NotificationManager.dart:

    class NotificationManger {
      static BuildContext _context;
    
      static init({@required BuildContext context}) {
        _context = context;
      }
    
      static onMessage(Map<String, dynamic> message) {
        print(message);
        _showSnackbar(data: message);
      }
    
      static onLaunch(Map<String, dynamic> message) {
        print(message);
      }
    
      static onResume(Map<String, dynamic> message) {
        print(message);
      }
    
      static _showSnackbar({@required Map<String, dynamic> data}) {
        // showDialog(context: _context, builder: (_) => );
        SnackBar snackBar = SnackBar(
          content: Text(
            data['data']['title'],
            style: TextStyle(
              fontFamily: 'Vazir',
              fontSize: 16.0,
            ),
          ),
          backgroundColor: ColorPalette.primary,
          behavior: SnackBarBehavior.floating,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(45.0),
          ),
          elevation: 3.0,
        );
        Scaffold.of(_context).showSnackBar(snackBar);
      }
    }
    

    main.dart

    class App extends StatelessWidget {
      final Store<AppState> store;
      App(this.store);
      @override
      Widget build(BuildContext context) {
        return StoreProvider(
          store: store,
          child: MaterialApp(
            ...
          ),
        );
      }
    }
    

    I am using redux and bloc, so any approach with these tools is ok for me.

    This is my sample screen:

    
    class Reminders extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: appBar,
          body: Center(
            child: Text('reminders'),
          ),
        );
      }
    }
    

    SOLUTION: Add NotificationManger.init(globalKey: _scaffoldKey); to all screens solve the problem.

    class Reminders extends StatelessWidget {
      final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
    
      @override
      Widget build(BuildContext context) {
        NotificationManger.init(globalKey: _scaffoldKey);
    
        return Scaffold(
          key: _scaffoldKey,
          appBar: appBar,
          body: Center(
            child: Text('reminders'),
          ),
        );
      }
    }
    
    

    SOLUTION 2

    UsingGet library to using only one function and no need to add it in all screen: https://pub.dev/packages/get