how to navigate when click background notification on Flutter?

Viewed 14850

Is it possible to navigate to a specified path when clicking background FCM notification?

I created a Top-Level function and add it to the navigator path but its not working, when clicking on a background notification, it just opens the app

I GUESS I FOUND AN ISSUE

Now, I changed fcm configuration from home page to splash screen. The foreground doesn't navigate to the page, I think its because the Splash Screen is no longer available. When I click on the notification message, it just opens the app.

FCM Configuration

onBackgroundMessage: backgroundMessageHandler

Top-Level function

Future<dynamic> backgroundMessageHandler(Map<String, dynamic> message) {
  if (message.containsKey('data')) {
    getIt<NavigationService>().navigateTo('/${message['data']['screen']}');
  }
}

Payload

const payload: admin.messaging.MessagingPayload = {
                notification:{
                    title: `New Enquiry`,
                    body:`${customerName} published to ${subName}`,
                    badge: '1',
                    sound: 'default'
                },
                data: {
                    click_action: `FLUTTER_NOTIFICATION_CLICK`,
                    sound: `default`,
                    status: `chat`,
                    screen: `homePage`
                  }
            }

main.dart

GetIt getIt = GetIt.instance;

void main() {
  setupLocator();
    runApp(MyApp());
}

MaterialApp

 return MaterialApp(
      navigatorKey: NavigationService().navigatorKey,
      onGenerateRoute: Router.generateRoute,
    );

NavigationService and setupLocator

class NavigationService {
  final GlobalKey<NavigatorState> navigatorKey =
      new GlobalKey<NavigatorState>();

  Future<dynamic> navigateTo(String routeName) {
    return navigatorKey.currentState.pushNamed(routeName);
  }
}

void setupLocator() {
  getIt.registerLazySingleton(() => NavigationService());
}
5 Answers

You can use this function to navigation when the app is killed and receive notifications in the background

  FirebaseMessaging.instance
        .getInitialMessage()
        .then((RemoteMessage message) {
      print("FirebaseMessaging.getInitialMessage");
      if (message != null) {

        Navigator.of(context).pushNamed('/call');
      }
    });

This function only run once when the app open and get the last message.

You can read more at this doc: https://github.com/FirebaseExtended/flutterfire/blob/62e09975f9b3d14141585e62ddab6b98e667b7cf/docs/messaging/notifications.mdx

To handle notifications coming from a background state you can use the stream exposed by the FirebaseMessaging library.

FirebaseMessaging.onMessageOpenedApp.listen((remoteMessage) {
  // Handle navigation or perform any logic here
});

In that example I used shared preference data like class id to check notification data contain this class id then it can navigate to a particular screen

void registerNotification() {
_firebaseMessaging.requestNotificationPermissions(
  const IosNotificationSettings(sound: true, badge: true, alert: true),
);

_firebaseMessaging.configure(onMessage: (Map<String, dynamic> message) {
  print('onMessage: $message');
 // print(message['data']['classID']+" onMessage");
  if(SharedClassID.toString().contains(message['data']['classID']))
  {
   
    Navigator.push(context, MaterialPageRoute(builder: 
 (context)=>BroadcastNotification()));
    print("found");
  }
  
  else if(SharedClassID.toString().contains(message['data']['classID']) )
  {
   
    Navigator.push(context, MaterialPageRoute(builder: (context)=>MuseGalaryNew()));
    print("found");
  }
  else
  {
    print("not found");
  }

  return;
}, onResume: (Map<String, dynamic> message) {
  if(SharedClassID.toString().contains(message['data']['classID']))
  {
   
    Navigator.push(context, MaterialPageRoute(builder: (context)=>BroadcastNotification()));
    print("found");
  }
  else if(SharedClassID.toString().contains(message['data']['classID']))
  {
 
    Navigator.push(context, MaterialPageRoute(builder: (context)=>StudentHomework()));
    print("found");
  }

  else
    {
      print("not found");
    }


  //print(message['data']['classID']+" onResume");
  print('onResume: $message');
  return;
}, onLaunch: (Map<String, dynamic> message) {
  if(SharedClassID.toString().contains(message['data']['classID'])  )
  {
            Navigator.push(context, MaterialPageRoute(builder: (context)=>BroadcastNotification()));
    print("found");
  }
  else if(SharedClassID.toString().contains(message['data']['classID']))
  {
          Navigator.push(context, MaterialPageRoute(builder: (context)=>StudentHomework()));
    print("found");
  }

  else
  {
    print("not found");
  }
  return;
});

}

I hope you have configured the required native side declaration required by firebase_messaging specfied here

Based on the payload you have given :

Payload

const payload: admin.messaging.MessagingPayload = {
                notification:{
                    title: `New Enquiry`,
                    body:`${customerName} published to ${subName}`,
                    badge: '1',
                    sound: 'default'
                },
                data: {
                    click_action: `FLUTTER_NOTIFICATION_CLICK`,
                    sound: `default`,
                    status: `chat`,
                    screen: `homePage`
                  }
            }

Have your onResume like this, similarly same for onLaunch

onResume :

onResume: (Map<String, dynamic> message) {
  if(SharedClassID.toString().contains(message['data']['classID']))
  {
   
    Navigator.push(context, MaterialPageRoute(builder: (context)=>BroadcastNotification()));
    print("found");
  }
  else if(SharedClassID.toString().contains(message['data']['classID']))
  {
 
    Navigator.push(context, MaterialPageRoute(builder: (context)=>StudentHomework()));
    print("found");
  }

  else
    {
      print("not found");
    }


  //print(message['data']['classID']+" onResume");
  print('onResume: $message');
  return;
}

I can find out the reason why it is not working because in the payload the key is screen:'homePage' and i can notice that you are not passing classID so what is happening is it failing to evaluate into the if else condition you have added.

You update the if/else condition like below :

if(message['data']['screen'] == 'homePage')
  {
    //TODO: route
  
  }else if(message['data']['screen'] == 'homeWorkPage'){
    //TODO: route
  }

If you want to route based on the SharedClassId then pass the classId in the payload and route. example snippet :

if(message['data']['classId'] == SharedClassId)
  {
    //TODO: route
  
  }else if(message['data']['classId'] == SharedClassId){
    //TODO: route
  }

Latest Code as of now AUG 2021. First of all your project dart version and firebase plugin version are important things to integrate firebase notification. If you are using a lower version, then you may face some issues. For my flutter project, I am using the latest null safe Firebase messaging plugin. If you use Flutter messaging 10.0.4 or higher version then you don't need to add any metadata on the android manifest file. You can see the complete code here.

  firebase_messaging: ^10.0.4
  firebase_core: ^1.4.0
  flutter_local_notifications: ^8.1.1

if the flutter app is closed then the below code will be called. It will be called only once & you should provide click_action as "FLUTTER_NOTIFICATION_CLICK" as custom data on the firebase console cloud messaging window.

  // when app is closed and it will be called only once
FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage? message) {
 print("fireabse closed app msg");

  if (message != null) {
    print( " firebase msg received closed add "+message.data.toString());
    Navigator.push(context, MaterialPageRoute(builder: (context){ return product(prodid: message.data["prodid"]);}));

    /*  Navigator.pushNamed(context, '/message',
        arguments: MessageArguments(message, true));
  */

  }
});

To display the notification icon on the status bar uses the below code.

FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      RemoteNotification? notification = message.notification;
      AndroidNotification? android = message.notification?.android;
      print(" firebase msg - "+message.data.length.toString());
      if (notification != null && android != null) {
        print("firebase msg body "+ notification.body.toString());
        flutterLocalNotificationsPlugin.show(
            notification.hashCode,
            notification.title,
            notification.body,
            NotificationDetails(
              android: AndroidNotificationDetails(
                channel.id,
                channel.name,
                channel.description,
                // TODO add a proper drawable resource to android, for now using
                //      one that already exists in example app.
                icon: 'noti_icon',
                color: Colors.red

              ),
            ));


      }
    });
Related