Flutter Notifications: Routing to specific screens on notification click

Flutter Notifications: Routing to specific screens on notification click

1_bXQYWBMf__b2JViL0vEM4w.png Working with Firebase messaging, i encountered a few problems handling notification clicks in flutter. Firebase messaging is generally easy to implement but can be a pain when some details are left out. It took me a while to figure out exactly what i was doing wrong and i decided to write this article to help someone on the same journey and hopefully save them from wasting valuable time.

Assumption made while writing this:

  • You know how to set up cloud messaging in your Firebase console. If you don’t, click here

  • You know how to deliver notification payload from cloud functions. if you don’t, click here

  • you know how to handle incoming messages in your flutter app. if you don’t, click here

In order to handle notification clicks and to perform some sort of operations when the app starts/resumes, there are two key factors to consider when delivering the notification payload:

  • The click_action key: set the click action key to “click_action”:“FLUTTER_NOTIFICATION_CLICK”, This passes the payload to the Message Handler in your flutter app.

  • The data key: When your app is running in the background, and your notification payload contains only the message key, the payload gets delivered only to the system’s notification tray and your app receives no payload on start/resume when the user clicks on the notification. Adding the data tag in the payload passes all the necessary data to your app

//payload with message tag
var message = {
   message:{
      title: “Home Page”,
      body: “Hello from the Home Page notification system”,
  },
  token: fcmToken
}
//payload with data tag
var message = {
   data:{
      title: “My title”,
      body: “My body”,

      click_action: “FLUTTER_NOTIFICATION_CLICK”
   },
   token: fcmToken
}

To deliver the payload to both the system’s notification tray and your app, combine both tags.

var message = {
   notification:{
      title: “My title”,
      body: “My body”,
   },
   data:{
      title: “My title”,
      body: “My body”,
      click_action: “FLUTTER_NOTIFICATION_CLICK”
   },
   token: fcmToken
}

I will demonstrate routing to specific screens with a quick flutter project

Create a new flutter project

In the my main.dart file, create three screens

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Home Page"),
      ),
      body: Container(),
    );
  }
}

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Second Page"),
      ),
      body:Container(),
    );
  }
}

class ThirdPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Third Page"),
      ),
      body:Container(),
    );
  }
}

Setup the notification trigger using Firebase cloud functions or whatever back-end you are using.

The payload template for the three screens created in the main.dart file is shown below. Include the screen key in the data section. The screen key tells our app what page to route to when the user clicks on the notification

var message = {
   notification:{
     title: "My title",
      body: "My body",
   },
   data:{
      title: "My title",
      body: "My body",
      click_action: "FLUTTER_NOTIFICATION_CLICK",
      screen: "homePage",//or secondScreen or thirdScreen
  },
  token: regToken
}

Setup the message handler in the message_handler.dart file to extract the data from incoming notification and handle routing.

class MessageHandler extends StatefulWidget {
  final Widget child;
  MessageHandler({this.child});
  @override
  State createState() => MessageHandlerState();
}

class MessageHandlerState extends State<MessageHandler> {
  final FirebaseMessaging fm = FirebaseMessaging();
  Widget child;
  @override
  void initState() {
    super.initState();
    child = widget.child;
    fm.configure(
      onMessage: (Map<String, dynamic> message) async {
        print("onMessage: ${message['data']['screen']}");
        String screen = message['data']['screen'];
        if (screen == "secondScreen") {
          Navigator.of(context).pushNamed("secondScreen");
        } else if (screen == "thirdScreen") {
          Navigator.of(context).pushNamed("thirdScreen");
        } else {
         //do nothing
        }
      },
      onResume: (Map<String, dynamic> message) async {
        print("onMessage: ${message['data']['screen']}");
        String screen = message['data']['screen'];
        if (screen == "secondScreen") {
          Navigator.of(context).pushNamed("secondScreen");
        } else if (screen == "thirdScreen") {
          Navigator.of(context).pushNamed("thirdScreen");
        } else {
          //do nothing
        }
      },
      onLaunch: (Map<String, dynamic> message) async {
        print("onMessage: ${message['data']['screen']}");
        String screen = message['data']['screen'];
        if (screen == "secondScreen") {
          Navigator.of(context).pushNamed("secondScreen");
        } else if (screen == "thirdScreen") {
          Navigator.of(context).pushNamed("thirdScreen");
        } else {
          //do nothing
        }
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return child;
  }
}

Register the route names with the MyApp widget and set the initial route to “homePage”

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      routes: {
        "homePage": (context) => MyHomePage(),
        "secondScreen": (context) => SecondPage(),
        "thirdScreen": (context) => ThirdPage(),
      },      
     initialRoute: "homePage",
    );
  }
}

Wrap the MyHomePage widget with the MessageHandler widget

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      routes: {
    //wrap the MyHomePage widget with the MessageHandler widget
        "homePage":(context) =>MessageHandler(child: MyHomePage()),
        "secondScreen": (context) => SecondPage(),
        "thirdScreen": (context) => ThirdPage(),
      },      
     initialRoute: "homePage",
    );
  }
}

And that's it. Full code and complete implementation for the mini project can be found here