FCM - Firebase Cloud Messaging HTTP v1 Notification Receiving and Sending Guide on Flutter

24.03.2021 20:31
#FLUTTER #FİREBASE #FCM #APİ

FCM - Firebase Cloud Messaging HTTP v1 Notification Receiving and Sending Guide on Flutter

Of course, we have thought of sending and receiving instant notifications on an application we developed with Flutter SDK. Maybe we already use it. This post will be a guide for those who have never used it, and an update guide for those using the lagacy HTTP version of FCM.

Firebase Cloud Messaging (FCM) has been in use for a long time. However, with the updated FCM HTTP v1, some updates and changes have been made. I, who is suffering from these differences, could not send a notification via API. After long trials, I solved the problem and if I forget it, or for the convenience of people who encounter the same problem, I will explain Firebase Cloud Messaging HTTP v1 through a project.

Firebase Cloud Messaging HTTP v1 Sample Application


I will explain our sample application by performing it on android studio.

1. As a first step we create an empty flutter project.

2. After the project is created, we add firebase_messaging, firebase_core, googleapis_auth and http packages under dependencies in the pubspec.yaml file and save the changes by saying Pub Get.

Note: If you wish, you can check the setup steps on the Cloud Messaging Usage page.

3. Then we create a new project on firebase.

4. We are adding an android project to the firebase project we have created.

5. We enter our project's package_name, app_name and sha-1 code. (You can refer to this post to easily add SHA-1 code to the project.)

6. After saving our application, we download the google-services.json file and put it under android / app.

7. We open the build.gradle file under Project and check for the following lines.

buildscript {
  repositories
{
   
// Check that you have the following line (if not, add it):
    google
()  // Google's Maven repository
 
}
  dependencies
{
   
...
   
// Add this line
    classpath
'com.google.gms:google-services:4.3.5'
 
}
}

allprojects
{
 
...
  repositories
{
   
// Check that you have the following line (if not, add it):
    google
()  // Google's Maven repository
   
...
 
}
}

8. Then we open the build.gradle file under the app and check the following lines exist.

apply plugin: 'com.android.application'
// Add this line
apply plugin
: 'com.google.gms.google-services'

dependencies
{
 
// Import the Firebase BoM
  implementation platform
('com.google.firebase:firebase-bom:26.7.0')

 
// Add the dependency for the Firebase SDK for Google Analytics
 
// When using the BoM, don't specify versions in Firebase dependencies
  implementation
'com.google.firebase:firebase-analytics'
  implementation
'com.google.firebase:firebase-messaging'

 
// Add the dependencies for any other desired Firebase products
 
// https://firebase.google.com/docs/android/setup#available-libraries
}

9. After these additions, we open the main.dart file and add the following codes. Only with these additions are we ready to receive notifications with FCM's default notification structure (title and body).

Future<void> _onBackgroundMessage(RemoteMessage message) async {

// This function will work when the onBackgroundMessage function is triggered.
// The requested operations will be performed by reading the RemoteMessage object received as a parameter.
print(
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>_onBackgroundMessage triggered");
print(
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>message.notification.title:" +
message.
notification.title);
print(
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>message.notification.body:" +
message.
notification.body);
return;
}

String token="";

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

// In this section, we will listen to notifications from FCM.

// First of all, we start the firebase service.
// Since this is a future, we will make the necessary listening in the then method.
Firebase.initializeApp().then((value) {

// The getToken function is used to get the token information of the device. It returns the token as Future <String>.
FirebaseMessaging.instance.getToken().then((value) {
print(
"Device Token: $value");
token=value;
});

// You can send a notification to all devices that are members of this topic.
FirebaseMessaging.instance.subscribeToTopic("default");
// We have 3 methods to be used for actual notification operations. These are as follows, respectively.
// 1. onMessage: This strem structure listens for notifications when the application is open and is triggered when received.
// 2. onBackgroundMessage: This method is triggered when a notification is received while the application is closed or in the background.
// 3. onMessageOpenedApp: This stream structure listens to see if the notification is clicked and triggers when it is clicked.

FirebaseMessaging.onMessage.listen((message) {
//Since the onMessage structure is a stream, we listen with the listen
//
method and read the RemoteMessage object that it will return when triggered.
print(
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>onMessage triggered");
print(
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>message.notification.title:" +
message.
notification.title);
print(
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>message.notification.body:" +
message.
notification.body);
});

FirebaseMessaging.onMessageOpenedApp.listen((message) {
//Since the onMessageOpenedApp structure is also a stream, we listen with the listen method
//
and read the RemoteMessage object that it will return when triggered.
print(
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>onMessageOpenedApp triggered");
print(
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>message.notification.title:" +
message.
notification.title);
print(
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>message.notification.body:" +
message.
notification.body);
});

//onBackgroundMessage is not a stream but a Future <void> type function.
//
The important thing is that he works in an independent field. In other words, a function defined outside the class must be called.
FirebaseMessaging.onBackgroundMessage(_onBackgroundMessage);

});
}

10. First of all, we will verify that we have received a notification via firebase. For this, we open the Cloud Messaging tab under the Engage menu on the firebase and click the Send your first message button.

11. On the screen that opens, we enter a sample notification title and text. You can also choose a notification image if you wish.

12. When you say Send test message, you are expected to add a test device. Since we get the device token with the getToken function and print it with print, we paste the printed device token into the Add an FCM registration token field and save it by pressing the + button.

13. If we press the test button, we have 2 options.

    13.1. If the application is open, the onMessage method will be triggered and the details printed with print will appear on the console.

    13.2. If the application is closed or in the background, the onBackgroundMessage method will be triggered and the details we have printed will appear on the console.

I hope you have received a sample notification through firebase so far and continue. The most important part for us is to be able to send and receive notifications automatically, not via firebase. For this, we need to use FCM HTTP v1 API.


Sending Notifications on Flutter with FCM HTTP v1 API


There is a short process we need to do and a function we need to write to send notifications with FCM HTTP v1 API. We are then ready to send a notification.

1. First of all, we enter the firebase console and open the project settings.

2. Click the Generate new private key button under the firebase Admin SDK in the Service Accounts section to download the json file that contains the data that will enable us to gain API access.

3. We create a new dart file with send_notification or any name you want and fill in the content as follows.

import 'dart:convert';
import 'package:googleapis_auth/auth_io.dart';
import'package:http/http.dart' as http;

// We take the desired values from the downloaded json file and place them here.
var serviceAccountJson ={
  "type": "service_account",
"project_id": "your-project-id",
"private_key_id": "your-private-key-id",
"private_key": "your-private-key",
"client_email": "your-client-email",
"client_id": "your-client-id",
};

Future<bool>
sendNotification(Map<String, dynamic> notification) async {
/*
* With the sendNotification function, we aim to send a notification using the notification object we received as a parameter.
*/

//By passing the json file we created above as a parameter, we obtain an accountCredential.
//
This credential will give us the accessToken required for the API.
final accountCredentials =
ServiceAccountCredentials.fromJson(serviceAccountJson);

// We provide information on which platform we want to access with scope.
List<String> scopes = ["https://www.googleapis.com/auth/cloud-platform"];

//The function, where we give the credential and scopes variables as parameters, returns a client that contains the access token.
//
Since the future is a method, we will perform the notification sending process in then method.

try {
clientViaServiceAccount(accountCredentials
, scopes)
.then((AuthClient client)
async {
// We determine the address of the post operation that we will perform with the prepared uri. // You will write your current project name in firebase in the your-project-name part of the URL. // (If you look at the URL in the browser while in the Firebase console, your project name is there.)
Uri _url = Uri.https(
"fcm.googleapis.com", "/v1/projects/your-project-name/messages:send");

print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<accessToken:${client.credentials.accessToken}");

// We define a new post operation with client. // We specify where to post with the uri defined above // In the headers section, we place the accessToken we received for the Authorization process. // As a load, we set the notification object as json and send it.
http.Response _response = await client.post(
_url
,
headers: {"Authorization": "Bearer ${client.credentials.accessToken}"},
body: json.encode(notification),
);
        print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<${_response.body}");
client.close();
});
} catch (e, s) {
print(s);
}
return true;
}

After creating the notification function, the only missing part to send a notification is to create the notification json object and call the function. Let's look at the different scenarios we can use to create the json object.

Create Json Payload for FCM HTTP v1 API

Notifications are sent to users via Firebase using topics and tokens. To give an example to these;

1. Topic Usage: All devices that follow Topic will be notified.

With the code below, the default topic is followed by devices.

// You can send a notification to all devices that are members of this topic.
FirebaseMessaging.instance.subscribeToTopic("default");

When sending notifications to users who are members of this topic, the notification object will be set as follows.

Map<String, dynamic> _notification = {
"message": {
"topic": "default",
"notification": {"title": _title, "body": _body},
"data": {
"firstName": _firstName,
"lastName": _lastName,
"eMail": _eMail,
"webSite": _webSite,
}
}
};


2. Using Multiple Topic: With the query to be created, a notification is sent to the devices that are members of one or more topics.

For example 'default' in topics || 'test' in topics = default or sent to devices that are members of the test topics.

For example ('cats' in topics && 'dogs' in topics) || 'monkeys' in topics = cats and dogs or monkeys are sent to devices that subscribe to their topic.

When sending notifications to users who are members of this topic, the notification object will be set as follows.

Map<String, dynamic> _notification = {
"message": {
"condition":"('cats' in topics && 'dogs' in topics) || 'monkeys' in topics",
"notification": {"title": _title, "body": _body},
"data": {
"firstName": _firstName,
"lastName": _lastName,
"eMail": _eMail,
"webSite": _webSite,
}
}
};


3. Token Usage: A notification is sent to the device pointed to by the specified token.

For this, the notification will be set as follows.

Map<String, dynamic> _notification = {
"message": {
"token": "your-device-token",
"notification": {"title": _title, "body": _body},
"data": {
"firstName": _firstName,
"lastName": _lastName,
"eMail": _eMail,
"webSite": _webSite,
}
}
};


Sending Customized Notifications with FCM

In some cases, default notifications may useless. In some cases, it may be desirable to customize the notification. In this case, we need to add the flutter_local_notifications package to our pubspec.yaml file.

This pack offers a large number of different notification types and content. In this way, it can be a more useful choice instead of default notifications.

You can find package details and usage types here.

The event that we need to pay attention to when using this package should not be a notification area in the notification you send. Because when there is a notification area, default notification is created automatically and two notifications are received. Your Notification object should look like this. 

Map<String, dynamic> _notification = {
"message": {
"token""your-device-token",
"data": {
"firstName"_firstName,
"lastName"_lastName,
"eMail"_eMail,
"webSite"_webSite,
}
}
};

flutter_local_notifications paketini projemize eklemek için aşağıdaki adımları izlememiz gerekiyor.

1. First of all, we create a file custom_notifications.dart or any name you want.

2. Then we include the following codes in the file we created.

import 'dart:convert';

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

// Plugin definition has been made.
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();

// We're reducing complexity by making our customized notifications a class. // We will get customized notifications just by saying CustomNotifications.showNotification (message).
class CustomNotifications {

// We are bringing the class to a singleton structure so that it does not create a new object each time.

static final CustomNotifications _singleton = CustomNotifications._internal();
factory CustomNotifications() {
return _singleton;
}
CustomNotifications.
_internal();

// Necessary definitions for customized notifications are made in this function.
initializeNotification() async {

// app_icon keeps icon information for your notification and you should put a file named app_icon.png
// in android/app/src/main/res/drawable folder.

const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('app_icon');

// iOS settings are being made.
final IOSInitializationSettings initializationSettingsIOS =
IOSInitializationSettings(
requestAlertPermission:
false,
requestBadgePermission: false,
requestSoundPermission: false,
onDidReceiveLocalNotification: _onDidReceiveLocalNotification,
);

// Adjustments for iOS and Android are combined into a general adjustment object.
final InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid
,
iOS: initializationSettingsIOS,
// There is also a setting option for macOS. For details, you can check the package details on the pub.dev page.
);

// With the prepared settings, custom notification is made. // With the Onselect method, the data sent with the notification is received.
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: _onSelectNotification);
}

Future<
void> _onDidReceiveLocalNotification(
int id
, String title, String body, String payload) {
print(
'notification payload: $payload');
return null;
}

Future<
void> _onSelectNotification(String payload) {
if (payload != null) {
print(
'notification payload: $payload');
}
return null;
}


/*
* We perform the process of showing notification with the showNotification method.
*
When you enter the package page, you can find a large number of different customizable notification types.
* */
static showNotification(RemoteMessage message) async {
//Our RemoteMessage type parameter is the message data coming from
//
the onMessage, onbackGroundMessage and onMessageOpenedApp methods created by FCM.
//
CustomNotification is created by processing here after the notification is received by FCM.

const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
'1234567890', 'Channel Name', 'Channel Description', // Channel part is the information you will see when you enter the application information from the settings.
importance: Importance.max,
priority: Priority.high,
ticker: 'ticker');

const NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);

//This is the method to show our notification. We add the fields we want from our message data, which is sent as parameters, here.
//
message.data ["firstName"] refers to the notification title.
//
message.data ["webSite"] refers to the notification body.
//
Payload part refers to the data you want to be processed after clicking the notification.
await flutterLocalNotificationsPlugin.show(0, message.data["firstName"],
message.data["webSite"], platformChannelSpecifics,
payload: json.encode(message.data));
}

}

3. After making the necessary additions, we open our main.dart file and add the following to the main method.

runApp(MyApp());
CustomNotifications().initializeNotification(); // This is the line to add.

4. Later, we need to add the following line to the onMessage, onBackgroundMessage and onMessageOpenedApp methods that we read the notifications by FCM.

CustomNotifications.showNotification(message);

Now that we are ready to receive customized notifications, we can provide the necessary controls with a few trials via PostMan.

Sending Notifications with FCM HTTP v1 API via PostMan 

The most convenient method to test the functions you have prepared while your application is closed is to send a notification via PostMan. For this, you need to follow the steps below.

1. First of all, we enter the PostMan site. If we do not have a membership, we create it for free.

2. After clicking the Workspaces menu, we click the new workspace button.

3. After creating the Workspace, we click the new button in the upper left corner and click the request button to create and name a new request.

4. We set the request type from Get to post and write https://fcm.googleapis.com/v1/projects/your-project-name/messages:send on the side. (Don't forget to fix your-project-name.)

5. Come to the Headers tab and add the Authorization key value. We write Bearer your_access_token in front of it. You can get the access token value from the following line on the send_notification.dart page. (Access token will appear on the console once you send a notification in the application.)

print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<accessToken:${client.credentials.accessToken}");

6. Under Authorization, we create a new key named Content-Type. On the opposite, we write application / json value.

7. We add our notification object to the Body section as follows. (It would be better if you choose raw format for adding.)

{
"message": {
"condition":"('default' in topics && 'test' in topics) || 'monkeys' in topics",
"notification": {"title": _title, "body": _body},
"data": {
"firstName": _firstName,
"lastName": _lastName,
"eMail": _eMail,
"webSite": _webSite,
}
}
}

8. If you press the send button later, if there is no problem, you should be able to send a notification. When the notification is sent, you should receive a response as follows.

{
    "name""projects/your-project-name/messages/1234567891011121314"
}


After completing this step, we have completed our notifications sending and receiving processes. Now we can easily send and receive notifications in our applications.

You can find the github repo of the project here.