Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification

by Didin J. on Feb 17, 2020 Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification

A comprehensive step by step Flutter tutorial on integrating Firebase Cloud Messaging (FCM) push notification to Android and iOS Apps

In this Flutter tutorial, we will show you how to integrate Firebase Cloud Messaging (FCM) push notification to Android and iOS Apps. This time we will use a FlutterFire plugin with the name firebase_messaging. 

This tutorial divided into several steps:

The following tools, frameworks, and libraries are required for this tutorial:

  1. Flutter SDK
  2. Firebase Cloud Messaging for Flutter
  3. Android SDK
  4. XCode
  5. Terminal (on Mac/Linux) or CMD (on Windows)
  6. IDE (Android Studio/IntelliJ/Visual Studio Code)

Also, you can watch this tutorial on our YouTube channel here. Please give it like, comment, share, and subscribe.

Let get started to the main steps!


Step #1: Preparation

Install Flutter SDK

To install flutter SDK, first, we have to download flutter_macos_v1.12.13+hotfix.8-stable.zip.
Extract the file to your desired location.

cd ~/development
unzip ~/Downloads/flutter_macos_v1.12.13+hotfix.8-stable.zip

Next, add flutter tools to the path.

export PATH="$PATH:~/development/flutter/bin"

Next, to make iOS and Android binaries can be downloaded ahead of time, type this Flutter command.

flutter precache

Check the Required Dependencies

To check the environment and displays a report to the terminal window to find dependencies that required to install, type this command.

flutter doctor

We have this summary in the Terminal.

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.14.6 18G2022,
    locale en-ID)

[!] Android toolchain - develop for Android devices (Android SDK version
    29.0.0-rc1)
    ! Some Android licenses not accepted.  To resolve this, run: flutter doctor
      --android-licenses
[✓] Xcode - develop for iOS and macOS (Xcode 10.3)
[✓] Android Studio (version 3.4)
[✓] VS Code (version 1.37.1)
[!] Connected device
    ! No devices available

! Doctor found issues in 2 categories.

To fixes, the issues like this, just connect the Android device and update the Android license by type this command.

flutter doctor --android-licenses

Type `y` for every question that displayed in the terminal. Next, check the connected Android device by type this command.

adb devices
List of devices attached
FUZDON75YLOVVC5S    device

Setup IDE

We need to set up the IDE to make it working with the Flutter app easily and compatibly. Start the Android Studio (we will use this IDE) then open Android Studio Menu -> Preference.

Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification - Setup IDE

Choose plugins in the left pane.

Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification - Choose Plugin

Type Flutter in the plugins marketplace then presses Enter. Next, click the Install button on the Flutter. If there's a prompt to install Dart, click Yes. Restart the IDE when prompt to ask restart.

Now, the Flutter is ready to develop Android and iOS mobile apps.


Step #2: Setup Google Firebase Cloud Messaging (FCM)

We are using Firebase Cloud Messaging (FCM) because it's a cross-platform messaging solution that lets you reliably deliver messages at no cost. Open your browser then go to Google Firebase Console https://console.firebase.google.com/ then log in using your Google account.

Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification - Firebase Console

From that page, click "+" add project button to create a Google Firebase project then it will be redirected to this page.

Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification - Firebase create project

After filling the project name text field which our project name is "My Flutter" then click continue button and it will be redirected to this page.

Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification - Firebase step 2

Scroll down then choose to not add Firebase analytics for now then click Create Project button. Now, you have a Google Firebase Project ready to use.

Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification - Firebase finish

After clicking the Continue button it will be redirected to this page.

Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification - Firebase dashboard

Next, click the iOS button to add a new iOS application.

Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification - Register iOS

Fill the iOS bundle ID field (ours: "com.djamware.myflutter") then click the Register App button.

Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification - Download config file

Download the GoogleService-info.plist file then click the Next Button several times to the last steps of the wizard then click the Continue to Console button to finish it. Do the same way for the Android application, so, you will have a configuration google-service.json file. Keep those configuration files for use with the Flutter app later.

Next, click the Gear button then choose project settings.

Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification - Firebase settings

Click the Cloud Messaging tab then it will take to this page.

Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification - Firebase Cloud Messaging Settings

Write down the server key to your notepad or text editor. That Firebase server key will use to send the push notification from your server or just using Postman application.


Step #3: Create a Flutter Application

After the IDE setup complete, it will back to the Android starting dialog.

Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification - Android start

Choose `Start a New Flutter Project` then choose `Flutter Application`.

Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification - New Flutter App

Click the Next Button then fill the required fields and choose the previously installed Flutter SDK path. The application name should same as the name in the package or bundle ID when setup Firebase Cloud Messaging.

Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification - Configure new flutter

Click the next button then fill the package name with your own domain.

Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification - Set package name

Click the Finish button and the Flutter application creation in progress. Next, run the Flutter application for the first time. In the Android Studio toolbar, choose the device and main.dart then click the play button.

Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification - Android Studio toolbar1


Step #4: Setup FlutterFire firebase_messaging on Android/iOS

To install the FlutterFire firebase_messaging, simply open and edit `pubscpec.yaml` then add firebase_messaging,  under other dependencies.

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  firebase_analytics: any
  firebase_messaging: 6.0.9
  firebase_core: ^0.4.0

Run Flutter command `Packages get` to install the newly added dependency.

Android Integration

To integrate FlutterFire firebase_messaging in Android, do the following steps:

1. Copy the downloaded `google-service.json` file when setup Firebase Cloud Messaging to the `android/app` folder.
2. Open and edit `android/build.gradle` file then add `com.google.gms:google-services` classpath inside dependencies bracket.

    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.0'
        classpath 'com.google.gms:google-services:4.3.2'
    }

3. Open and edit `android/app/build.gradle` then add `com.google.gms.google-services` apply the plugin to the bottom of the file content.

apply plugin: 'com.google.gms.google-services'

4. To handle a message in the background add firebase-messaging implementation to dependencies bracket.

dependencies {
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
    implementation 'com.google.firebase:firebase-messaging:20.1.0'
}

5. Open and edit `android/app/src/main/AndroidManifest.xml` then add this intent to the main activity.

    <application
        ...>
        <activity
            android:name=".MainActivity"
            ...>
            ...
            <intent-filter>
                <action android:name="FLUTTER_NOTIFICATION_CLICK" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        ...
    </application>

6. Create a new Java file to `android/src/main/com/djamware/myflutter/Application.java`. Open and edit that file then replace all Java codes with these.

package com.djamware.myflutter;

import io.flutter.app.FlutterApplication;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback;
import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService;

public class Application extends FlutterApplication implements PluginRegistrantCallback {

    @Override
    public void onCreate() {
        super.onCreate();
        FlutterFirebaseMessagingService.setPluginRegistrant(this);
    }

    @Override
    public void registerWith(PluginRegistry registry) {
        FirebaseCloudMessagingPluginRegistrant.registerWith(registry);
    }
}

7. On the same directory with Application.java, create a new Java class `android/src/main/com/djamware/myflutter/FirebaseCloudMessagingPluginRegistrant.java` then replace all Java codes with these.

package com.djamware.myflutter;

import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin;

public final class FirebaseCloudMessagingPluginRegistrant{
    public static void registerWith(PluginRegistry registry) {
        if (alreadyRegisteredWith(registry)) {
            return;
        }
        FirebaseMessagingPlugin.registerWith(registry.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin"));
    }

    private static boolean alreadyRegisteredWith(PluginRegistry registry) {
        final String key = FirebaseCloudMessagingPluginRegistrant.class.getCanonicalName();
        if (registry.hasPlugin(key)) {
            return true;
        }
        registry.registrarFor(key);
        return false;
    }
}

8. Open and edit `android/src/main/com/djamware/myflutter/MainActivity.java` then remove or commenting configureFlutterEngine methods and it's imported. So the MainActivity.java looks like this.

package com.djamware.myflutter;

//import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
//import io.flutter.embedding.engine.FlutterEngine;
//import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {
//  @Override
//  public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
//    GeneratedPluginRegistrant.registerWith(flutterEngine);
//  }
}

7. Back to `AndroidManifest.xml`, change the <application> name to point the newly created Application class.

    <application
        android:name=".Application"
        ...
    </application>

iOS Integration

The FCM (Firebase Cloud Messaging) will be working with the iOS applications if you have an Apple Developer Enrollment account and can generate or download the Apple Push Notification certificate. Also, you need to download and use the Provisioning Profile that points to the Bundle ID the same as the previously created when setting up Firebase Cloud Messaging. After all requirements ready, do the following steps to integrate it with the iOS app.

1. Copy the downloaded GoogleService-Info.plist to ios folder.
2. Start the XCode then open the `ios/Runner.xcworkspace`.
3. Right-click the Runner project name -> Runner folder in the left pane then click `Add files to Runner...` then choose the GoogleService-Info.plist file to add.
4. Open and edit `ios/Runner/AppDelegate.m` or `ios/Runner/AppDelegate. swift` then add this line inside (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method.

   Objective-C

   if (@available(iOS 10.0, *)) {
      [UNUserNotificationCenter currentNotificationCenter].delegate = (id<UNUserNotificationCenterDelegate>) self;
   }

   Swift

   if #available(iOS 10.0, *) {
     UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
   }


Step #5: Implementing FCM Push Notification on Flutter/Dart

We implementing the Firebase Push Messaging on the main.dart which shows a main view or screen, a dialog that displays push notification, and a detailed widget that displays or redirected from the push notification dialog. Next, open and edit `lib/main.dart` then add these imports.

import 'dart:async';
import 'package:firebase_messaging/firebase_messaging.dart';

Add a method on the top level of the Dart file (after the imports) that handle the received push notification when the app in the background.

Future<dynamic> myBackgroundMessageHandler(Map<String, dynamic> message) {
  if (message.containsKey('data')) {
    // Handle data message
    final dynamic data = message['data'];
  }

  if (message.containsKey('notification')) {
    // Handle notification message
    final dynamic notification = message['notification'];
  }

  // Or do other work.
}

Add a string map after the above method that extracts the JSON body payload from the FCM push notification.

final Map<String, Item> _items = <String, Item>{};
Item _itemForMessage(Map<String, dynamic> message) {
  final dynamic data = message['data'] ?? message;
  final String itemId = data['id'];
  final Item item = _items.putIfAbsent(itemId, () => Item(itemId: itemId))
    .._matchteam = data['matchteam']
    .._score = data['score'];
  return item;
}

Add an object that mapped from the Firebase push notification message extraction.

class Item {
  Item({this.itemId});
  final String itemId;

  StreamController<Item> _controller = StreamController<Item>.broadcast();
  Stream<Item> get onChanged => _controller.stream;

  String _matchteam;
  String get matchteam => _matchteam;
  set matchteam(String value) {
    _matchteam = value;
    _controller.add(this);
  }

  String _score;
  String get score => _score;
  set score(String value) {
    _score = value;
    _controller.add(this);
  }

  static final Map<String, Route<void>> routes = <String, Route<void>>{};
  Route<void> get route {
    final String routeName = '/detail/$itemId';
    return routes.putIfAbsent(
      routeName,
          () => MaterialPageRoute<void>(
        settings: RouteSettings(name: routeName),
        builder: (BuildContext context) => DetailPage(itemId),
      ),
    );
  }
}

Add a widget that displaying the message details.

class DetailPage extends StatefulWidget {
  DetailPage(this.itemId);
  final String itemId;
  @override
  _DetailPageState createState() => _DetailPageState();
}

class _DetailPageState extends State<DetailPage> {
  Item _item;
  StreamSubscription<Item> _subscription;

  @override
  void initState() {
    super.initState();
    _item = _items[widget.itemId];
    _subscription = _item.onChanged.listen((Item item) {
      if (!mounted) {
        _subscription.cancel();
      } else {
        setState(() {
          _item = item;
        });
      }
    });
  }
  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text("Match ID ${_item.itemId}"),
      ),
      body: SingleChildScrollView(
        child: Container(
          padding: EdgeInsets.all(20.0),
          child: Card(
            child: Container(
                padding: EdgeInsets.all(10.0),
                child: Column(
                  children: <Widget>[
                    Container(
                      margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                      child: Column(
                        children: <Widget>[
                          Text('Today match:', style: TextStyle(color: Colors.black.withOpacity(0.8))),
                          Text( _item.matchteam, style: Theme.of(context).textTheme.title)
                        ],
                      ),
                    ),
                    Container(
                      margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                      child: Column(
                        children: <Widget>[
                          Text('Score:', style: TextStyle(color: Colors.black.withOpacity(0.8))),
                          Text( _item.score, style: Theme.of(context).textTheme.title)
                        ],
                      ),
                    ),
                  ],
                )
            ),
          ),
        ),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

Modify the home widget and the message dialog widget to this widget. Also, run all FirebaseMessaging methods when this widget is initiated. So, the whole HomePage and MyApp class will be like this.

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  bool _topicButtonsDisabled = false;

  final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
  final TextEditingController _topicController =
  TextEditingController(text: 'topic');

  Widget _buildDialog(BuildContext context, Item item) {
    return AlertDialog(
      content: Text("${item.matchteam} with score: ${item.score}"),
      actions: <Widget>[
        FlatButton(
          child: const Text('CLOSE'),
          onPressed: () {
            Navigator.pop(context, false);
          },
        ),
        FlatButton(
          child: const Text('SHOW'),
          onPressed: () {
            Navigator.pop(context, true);
          },
        ),
      ],
    );
  }

  void _showItemDialog(Map<String, dynamic> message) {
    showDialog<bool>(
      context: context,
      builder: (_) => _buildDialog(context, _itemForMessage(message)),
    ).then((bool shouldNavigate) {
      if (shouldNavigate == true) {
        _navigateToItemDetail(message);
      }
    });
  }

  void _navigateToItemDetail(Map<String, dynamic> message) {
    final Item item = _itemForMessage(message);
    // Clear away dialogs
    Navigator.popUntil(context, (Route<dynamic> route) => route is PageRoute);
    if (!item.route.isCurrent) {
      Navigator.push(context, item.route);
    }
  }

  @override
  void initState() {
    super.initState();
    _firebaseMessaging.configure(
      onMessage: (Map<String, dynamic> message) async {
        print("onMessage: $message");
        _showItemDialog(message);
      },
      onBackgroundMessage: myBackgroundMessageHandler,
      onLaunch: (Map<String, dynamic> message) async {
        print("onLaunch: $message");
        _navigateToItemDetail(message);
      },
      onResume: (Map<String, dynamic> message) async {
        print("onResume: $message");
        _navigateToItemDetail(message);
      },
    );
    _firebaseMessaging.requestNotificationPermissions(
        const IosNotificationSettings(
            sound: true, badge: true, alert: true, provisional: true));
    _firebaseMessaging.onIosSettingsRegistered
        .listen((IosNotificationSettings settings) {
      print("Settings registered: $settings");
    });
    _firebaseMessaging.getToken().then((String token) {
      assert(token != null);
      print("Push Messaging token: $token");
    });
    _firebaseMessaging.subscribeToTopic("matchscore");
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: const Text('My Flutter FCM'),
      ),
      body: SingleChildScrollView(
        child: Container(
          padding: EdgeInsets.all(20.0),
          child: Card(
            child: Container(
              padding: EdgeInsets.all(10.0),
              child: Column(
                children: <Widget>[
                  Container(
                    margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                    child: Column(
                      children: <Widget>[
                        Text('Welcome to this Flutter App:', style: TextStyle(color: Colors.black.withOpacity(0.8))),
                        Text('You already subscribe to the matchscore topic', style: Theme.of(context).textTheme.title)
                      ],
                    ),
                  ),
                  Container(
                    margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
                    child: Column(
                      children: <Widget>[
                        Text('Now you will receive the push notification from the matchscore topics', style: TextStyle(color: Colors.black.withOpacity(0.8)))
                      ],
                    ),
                  ),
                ],
              )
            ),
          ),
        ),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

Finally, modify the main method to be like this.

void main() {
  runApp(
    MaterialApp(
      home: MyHomePage(),
    ),
  );
}


Step #6: Run and Test Flutter FCM Push Notification

To run this Flutter apps to Android, simply click the play button in the Android Studio toolbar. Make sure your Android device is connected to your computer and appear in Android Studio Toolbar. And here the working Flutter Firebase Push Notification (FCM) apps on Android device look like.

Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification - Demo

As we mention before, to run the Firebase push notification on the iOS, you need an enrolled Apple Developer account. So, you will have the Apple Push Notification certificate and provisioning profile. After that, you just enable push notifications and background service in the Capabilities.

That it's, the Flutter Tutorial: Firebase Cloud Messaging FCM Push Notification. You can get the full source code from our GitHub.

That just the basic. If you need more deep learning about Flutter, Dart, or related you can take the following cheap course:

Thanks!

Loading…