DEV Community

loading...
Cover image for Flutter Firebase Messaging: Where everyone makes mistakes!

Flutter Firebase Messaging: Where everyone makes mistakes!

aqueel profile image Aqueel Aboobacker ・5 min read

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
  3. Download GoogleService-info.list and drag and drop it inside the runner folder using Xcode Alt Text
  4. Now a window will appear, make sure you have checked everything like in the following image. Alt Text
  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>
Enter fullscreen mode Exit fullscreen mode

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.
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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>"
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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.
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

📝 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
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (1)

pic
Editor guide
Collapse
ralph_shillington_0e0377d profile image
Ralph Shillington

Any chance for an update on this post, given the changes to the SDK since the original writing?