Flutter Firebase Messaging: Where everyone makes mistakes!
Tags: flutter, dart, programming
Published: 2021-02-01 14:36:17

When I first got to integrate firebase messaging to flutter, I read the official documentation and many Medium articles and still failed to do it in the right way. There are many reasons for that: Articles I read were not beginner-friendly and also they missed few things. After going through the Github issues and few developer forums I finally understood the right way of doing it.

In this tutorial, I will try to teach you how to integrate firebase messaging (push notification) into your flutter project without getting an error. So let’s get started.

####STEP 1: 💥 Create Firebase project

log in to firebase console https://console.firebase.google.com/u/0/
Click on add project, enter the name of your project and click the continue button, and follow the wizard with a couple of clicks and the project will be created

####STEP 2: 📱Add Android app

1. First, click on the Android icon to add Android app
Now a five-step wizard will appear. 

2. Enter the package name which will be in the `android/app/build.gralde` file, look for `applicationId` in that file. Also, provide a nickname for the app (optional). Now click register.

3. On the second step, download the `google-service.json` file and move it to `[project]/android/app` location

4. Add the classpath to the `[project]/android/build.gradle` file.```
dependencies {   
  // Example existing classpath   
  classpath 'com.android.tools.build:gradle:3.5.3'   
  // Add the google services classpath   
  classpath 'com.google.gms:google-services:4.3.4' 
}
```

5. Add the apply plugin to the `[project]/android/app/build.gradle` file.```
// ADD THIS AT THE BOTTOM 
apply plugin: 'com.google.gms.google-services'
```
6. If you want to be notified in your app (via `onResume` and `onLaunch`, don't worry we will be defining these functions in the following steps) when the user clicks on a notification in the system tray include the following intent-filter within the `<activity>` tag of your `android/app/src/main/AndroidManifest.xml` ```
  <intent-filter>
      <action android:name="FLUTTER_NOTIFICATION_CLICK" />
      <category 
        android:name="android.intent.category.DEFAULT" />
  </intent-filter>
```

####STEP 3: 📱Add IOS app

1. First, click on the IOS icon to add the IOS app
2. Now a five-step wizard will appear. on the first step; Enter the bundle id. Open `ios/Runner.xcworkspace` using Xcode, you can find the bundle id there. Provide a nickname for the app for identifying in the future (optional). Now click register.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/fck9g7ddu9b9v2jkcdc8.png)
3. Download GoogleService-info.list and drag and drop it inside the runner folder using Xcode
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/29wpzuiuooyrnj7u2bg4.png)
4. Now a window will appear, make sure you have checked everything like in the following image.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/815c4um9eyicwfmk0ouw.png)
5. We are good to go!. Skip all other steps.

####STEP 4: ⚡ Adding packages to Flutter

Install the following dependencies in your Flutter project.
```
dependencies:  
   firebase_core: <latest_version> 
   firebase_messaging: <latest_version>
```

####STEP 5: 🔥 Configuring firebase messaging

Now import the firebase messaging package and override the `initState` method of the home screen widget and add the following lines.

```
import 'package:flutter/material.dart';
import 'package:firebase_messaging/firebase_messaging.dart';

class MyHomePage extends StatefulWidget {
  
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  final _firebaseMessaging = FirebaseMessaging();

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  void initState() {
    
    // configure firebase messaging
    
    _firebaseMessaging.configure(
      onMessage: (Map<String, dynamic> message) async {
        print("onMessage: $message");
      },
      onLaunch: (Map<String, dynamic> message) async {
        print("onLaunch: $message");
      },
      onResume: (Map<String, dynamic> message) async {
        print("onResume: $message");
      },
    );

    // requesting permission, only for ios
    
    _firebaseMessaging.requestNotificationPermissions(
        const IosNotificationSettings(
            sound: true, badge: true, alert: true, provisional: true));


    _firebaseMessaging.onIosSettingsRegistered
        .listen((IosNotificationSettings settings) {
      print("Settings registered: $settings");
    });


    // getting registration id
    _firebaseMessaging.getToken().then((String token) async {
      assert(token != null);
      print('Registration Id: $token');
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(

        title: Text(widget.title),
      ),
      body: Center(

        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
```

####STEP 6: ✉️ Handling the received message

For example, the payload we are sending through the firebase API is:
```
https://fcm.googleapis.com/fcm/send 
Content-Type:application/json 
Authorization:key=<API_KEY>
{
   "notification": {
   "title": "New order recieved",
   "body": "2 items ordered"
   },
   "data": {
   "status": "done",
   "order_id": 2971,
   "click_action": "FLUTTER_NOTIFICATION_CLICK"
   },
   "to": "<device id>"
}
```

The structure of the message we receive on our phones is different for Android and IOS. So we need to convert it into a common structure before we access it. So we will write a function that takes the message as input and convert it based on the platform the code is running on.

```
Map<String, dynamic> _convertMessage(Map<String, dynamic> message) {
    try {
      if (Platform.isIOS) {
        return {
          'title': message['aps']['alert']['title'],
          'body': message['aps']['alert']['body'],
          'order_id': message['order_id'],
          'status': message['status'],
        };
      } else {
        return {
          'title': message['notification']['title'],
          'body': message['notification']['body'],
          'order_id': message['data']['order_id'],
          'status': message['data']['status'],
        };
      }
    } catch (e) {
      return null;
    }
  }
}
```
Now we converted the message into the same structure, so it will be easy to access and do whatever we want with the received message.

That's it!. We have now successfully integrated The final code after integration will be as follows.

```
import 'package:flutter/material.dart';
import 'package:firebase_messaging/firebase_messaging.dart';

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  final _firebaseMessaging = FirebaseMessaging();

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

	Map<String, dynamic> _convertMessage(Map<String, dynamic> message) {
	    try {
	      if (Platform.isIOS) {
	        return {
	          'title': message['aps']['alert']['title'],
	          'body': message['aps']['alert']['body'],
	          'order_id': message['order_id'],
	          'status': message['status'],
	        };
	      } else {
	        return {
	          'title': message['notification']['title'],
	          'body': message['notification']['body'],
	          'order_id': message['data']['order_id'],
	          'status': message['data']['status'],
	        };
	      }
	    } catch (e) {
	      return null;
	    }
	  }
	
	}

  @override
  void initState() {
    
    // configure firebase messaging
    
    _firebaseMessaging.configure(
      onMessage: (Map<String, dynamic> message) async {
        Map<String, dynamic> convertedMessage = _convertMessage(message);
				if(convertedMessage != null) {
					// now do your stuff here
				}
      },
      onLaunch: (Map<String, dynamic> message) async {
        Map<String, dynamic> convertedMessage = _convertMessage(message);
				if(convertedMessage != null) {
					// now do your stuff here
				}
      },
      onResume: (Map<String, dynamic> message) async {
	       Map<String, dynamic> convertedMessage = _convertMessage(message);
				if(convertedMessage != null) {
					// now do your stuff here
				}
      },
    );

    // requesting permission, only for ios
    _firebaseMessaging.requestNotificationPermissions(
        const IosNotificationSettings(
            sound: true, badge: true, alert: true, provisional: true));


    _firebaseMessaging.onIosSettingsRegistered
        .listen((IosNotificationSettings settings) {
      print("Settings registered: $settings");
    });


    // getting registration id
    _firebaseMessaging.getToken().then((String token) async {
      assert(token != null);
      print('Registration Id: $token');
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(

        title: Text(widget.title),
      ),
      body: Center(

        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
```

####📝 Things to note

By default background messaging is not enabled. Background messaging is not receiving messages while the app is running in the background, it is only used for special cases. And this feature is only available for Android. So I recommend you to not care too much about this. `run tab` in Android Studio will log the following message:

```
E/FlutterFcmService(15395): Fatal: failed to find callback
```

This error message is coming from startBackgroundIsolate which is used for allowing handling background messages. If you don’t want to handle background messages then you can safely ignore this error message.