FCM - Firebase Cloud Messaging HTTP v1 İle Flutter'da Bildirim Alma ve Gönderme Rehberi

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

FCM - Firebase Cloud Messaging HTTP v1 İle Flutter'da Bildirim Alma ve Gönderme Rehberi

Flutter SDK ile gerçekleştirdiğimiz bir uygulama üzerinde elbette ki anlık bildirimler gönderip almayı düşünmüşüzdür. Belki de zaten kullanıyoruzdur. Bu gönderi hiç kullanmayanlar için bir rehber, FCM'nin lagacy HTTP versiyonunu kullananlar için ise bir güncelleme rehberi olacaktır.

Firebase Cloud Messaging(FCM) uzun zamandır kullanılıyordu. Fakat güncellenen FCM HTTP v1 ile birlikte bazı güncellemeler, kolaylıklar ve değişiklikler yapıldı. Bu farklılıklardan muzdarip olan ben, API üzerinden bir türlü bildirim gönderemedim. Uzun denemelerden sonra sorunu çözdüm ve eğer unutursam diye veya aynı sorunla karşılaşan insanlara kolaylık olması açısından bir proje üzerinden Firebase Cloud Messaging HTTP v1'i anlatacağım.

Firebase Cloud Messaging HTTP v1 Örnek Uygulaması


Örnek uygulamamızı anroid studio üzerinde gerçekleştirerek anlatacağım.

1. İlk adım olarak boş bir flutter projesi oluşturuyoruz.

2. Proje oluşturulduktan sonra firebase_messagingfirebase_core, googleapis_auth ve http paketlerini pubspec.yaml dosyasında dependencies altına ekliyoruz ve Pub Get diyerek değişiklikleri kaydediyoruz.

Not: Dilerseniz kurulum aşamalarını Cloud Messaging Usage sayfasından da kontrol edebilirsiniz.

3. Daha sonra firebase üzerinde yeni bir proje oluşturuyoruz.

4. Oluşturduğumuz firebase projesine bir android projesi ekliyoruz.

5. Projemizin package_name, app_name ve sha-1 kodunu giriyoruz. (Projeye kolayca SHA-1 kodu eklemek için bu gönderiye bakabilirsiniz.)

6. Uygulamamızı kaydettikten sonra google-services.json dosyasını indiriyoruz ve android/app altına atıyoruz.

7. Project altındaki build.gradle dosyasını açıyoruz ve aşağıdaki satırların varlığını kontrol ediyoruz.

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. Daha sonra app altındaki build.gradle dosyasını açıyoruz ve aşağıdaki satırların varlığını kontrol ediyoruz.

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. Bu eklemelerden sonra main.dart dosyasını açıyoruz ve aşağıda belirtilen kodları ekliyoruz. Sadece bu eklemeler ile FCM'nin varsayılan bildirim yapısı (title ve body) ile bildirimleri almaya hazırız. 

Future<void> _onBackgroundMessage(RemoteMessage message) async {

//onBackgroundMessage fonksiyonu tetiklendiği zaman bu fonksiyon çalışacak.
//Parametre olarak aldığı RemoteMessage nesnesi okunarak istenen işlemler gerçekleştirilecek.
print(
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>_onBackgroundMessage Tetiklendi");
print(
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>message.notification.title:" +
message.notification.title);
print(
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>message.notification.body:" +
message.notification.body);
return;
}

String token="";

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

// Bu kısımda FCM üzerinden gelecek bildirimleri dinleyeceğiz.

// Öncelikle firebase servisini başlatıyoruz.
// Bu bir future olduğu için then metodu içerisinde gerekli dinlemeleri yapacağız.
Firebase.initializeApp().then((value) {

//Cihaza ait token bilgisini almak için getToken fonksiyonu kullanılıyor. Future<String> olarak token'i döndürüyor.
FirebaseMessaging.instance.getToken().then((value) {
print("Device Token: $value");
token=value;
});

//bu topic'e üye olan tüm cihazlara bu topic üzerinden bildirim gönderebilirsiniz.
FirebaseMessaging.instance.subscribeToTopic("default");
//Asıl bildirim işlemleri için kullanılacak 3 adet metodumuz var. Bunlar sırasıyla şu şekildedir.
// 1. onMessage: Bu strem yapısı, uygulama açıkken bildirimleri dinliyor ve alındığında tetikleniyor.
// 2. onBackgroundMessage: Bu metod, uygulama kapalıyken veya arkaplandayken bildirim alındığında tetikleniyor.
// 3. onMessageOpenedApp: Bu stream yapısı ise, bildirim tıklandı mı diye dinliyor ve tıklandığı zaman tetikleniyor.

FirebaseMessaging.onMessage.listen((message) {
//onMessage yapısı bir stream olduğu için listen metodu ile dinliyoruz ve tetiklendiği zaman
//döndüreceği RemoteMessage nesnesini okuyoruz.
print(
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>onMessage Tetiklendi");
print(
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>message.notification.title:" +
message.notification.title);
print(
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>message.notification.body:" +
message.notification.body);
});

FirebaseMessaging.onMessageOpenedApp.listen((message) {
//onMessageOpenedApp yapısı da bir stream olduğu için listen metodu ile dinliyoruz ve tetiklendiği zaman
//döndüreceği RemoteMessage nesnesini okuyoruz.
print(
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>onMessageOpenedApp Tetiklendi");
print(
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>message.notification.title:" +
message.notification.title);
print(
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>message.notification.body:" +
message.notification.body);
});

//onBackgroundMessage bir stream değil Future<void> tipinde bir fonksiyondur.
//Önemli olan şart bağımsız bir alanda çalışıyor olmasıdır. Yani class dışında tanımlanan bir fonksiyon çağrılmalıdır.
FirebaseMessaging.onBackgroundMessage(_onBackgroundMessage);

});
}

10. Öncelikle firebase üzerinden bildirim göndererek bildirim aldığımızı doğrulayacağız. Bunun için firebase üzerinde Engage menüsü altındaki Cloud Messaging sekmesini açıp Send your first message butonuna tıklıyoruz.

11. Açılan ekranda örnek bir bildirim başlığı ve metni giriyoruz. Dilerseniz bir bildirim resmi de seçebilirsiniz.

12. Send test message dediğiniz zaman bir test cihazı eklemeniz bekleniyor. Biz getToken fonksiyonu ile cihaz tokenini alıp print ile yazdırdığımız için yazdırılan device tokenini Add an FCM registration token alanına yapıştırıyoruz ve + butonuna basarak kaydediyoruz.

13. Test butonuna bastığımız takdirde elimizde 2 adet seçenek oluyor.

    13.1. Uygulama açık ise onMessage metodu tetiklenecek ve print ile yazdırdığımız detaylar konsolda gözükecektir.

    13.2. Uygulama kapalı veya arkaplanda ise onBackgroundMessage metodu tetiklenecek ve yazdırdığımız detaylar konsolda gözükecektir.

Buraya kadar firebase üzerinden örnek bir bildirim alabildiğinizi ümit ediyorum ve devam ediyorum. Bizim için asıl önemli kısım firebase üzerinden değil de, otomatik olarak bildirim gönderip alabilmek. Bunun için de FCM HTTP v1 API'ı kullanmamız gerekiyor.

FCM HTTP v1 API ile Flutter'da Bildirim Göndermek


FCM HTTP v1 API ile bildirim göndermek için yapmamız gereken kısa bir işlem ve yazmamız gereken bir adet fonksiyon bulunuyor. Daha sonra bildirim göndermeye hazırız.

1. Öncelikle firebase konsola girip,  proje ayarlarını açıyoruz.

2. Service Accounts kısmında firebase Admin SDK altındaki Generate new private key butonuna tıklayarak API erişimi kazanmamızı sağlayacak olan verileri içeren json dosyasını indiriyoruz.

3. send_notification veya dilediğiniz bir isimde yeni bir dart dosyası oluşturuyoruz ve içeriği aşağıdaki gibi dolduruyoruz.

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

//İndirdiğimiz json dosyasından istenilen değerleri alıp buraya yerleştiriyoruz.
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 {
/*
* sendNotification fonksiyonu ile parametre olarak aldığımız notification nesnesini kullanarak bir bildirim göndermeyi hedefliyoruz.
*/

//yukarıda oluşturduğumuz json dosyasını parametre olarak geçerek bir accountCredential elde ediyoruz.
//Bu credential bize API için gerekli olan accessToken'i verecek.
final accountCredentials =
ServiceAccountCredentials.fromJson(serviceAccountJson);

//Scope ile hangi platforma erişmek istediğimize dair bir bilgilendirme yapıyoruz.
List<String> scopes = ["https://www.googleapis.com/auth/cloud-platform"];

//Oluşturduğumuz credential ve scopes değişkenlerini parametre olarak verdiğimiz fonksiyon bize access tokeni içeren bir client döndürüyor.
//Future bir metod olduğu için then metodu içerisinde bildirim gönderme işlemini gerçekleştireceğiz.
try {
try {
clientViaServiceAccount(accountCredentials, scopes)
.then((AuthClient client) async {
//Hazırlanan uri ile gerçekleştireceğimiz post işleminin adresini belirliyoruz.
//URL içerisindeki your-project-name kısmına firebase'de geçerli olan proje adınızı yazacaksınız.
//(Firebase konsoldayken tarayıcıdan URL'ye bakarsanız proje adnız orada bulunmaktadır.)
Uri _url = Uri.https(
"fcm.googleapis.com", "/v1/projects/your-project-name/messages:send");

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

//client ile yeni bir post işlemi tanımlıyoruz.
//Yukarıda tanımlanan uri ile nereye post işlemi yapacağımızı belirtiyoruz
//header kısmında gerekli Authorization işlemi için aldığımız accessToken'i yerleştiriyoruz.
// yük olarak ise notification nesnesini json olarak ayarlayıp gönderiyoruz.
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);
}
} catch (e, s) {
print(s);
}
return true;
}
Bildirim fonksiyonunu da oluşturduktan sonra bildirim göndermek için eksik kalan tek kısım notification json nesnesini oluşturmak ve fonksiyonu çağırmak oluyor. Şimdi json nesnesi oluşturmak için kullanabileceğimiz farklı senaryolara bakalım.

FCM HTTP v1 API İçin Json Yükü Oluşturma

Firebase üzerinden kullanıcılar topic ve token kullanarak bildirimler gönderilir. Bunlara örnek verilmek gerekirse;


1. Topic Kullanımı: Topic'i takip eden tüm cihazlara bildirim gönderilir.

Aşağıdaki kod ile default topic'i chiazlar tarafından takip edilir.

//bu topic'e üye olan tüm cihazlara bu topic üzerinden bildirim gönderebilirsiniz.
FirebaseMessaging.instance.subscribeToTopic("default");

Bu topic'e üye olan kullanıcılara bildirim gönderirken notification nesnesi ise aşağıdaki şekilde  ayarlanacaktır.

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


2. Çoklu Topic Kullanımı: Oluşturulacak sorgu ile bir veya birden daha fazla topic'e üye olan cihazlara bildirim gönderilir.

Örneğin 'default' in topics || 'test' in topics = default veya test topiclerine üye olan cihazlara gönderilir.

Örneğin ('cats' in topics && 'dogs' in topics) || 'monkeys' in topics = kediler ve köpekler ya da maymunlar topiclerine üye olan cihazlara gönderilir.

Bu topic'e üye olan kullanıcılara bildirim gönderirken notification nesnesi ise aşağıdaki şekilde  ayarlanacaktır.

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 Kullanımı: Belirtilen token'ın işaret ettiği cihaza bildirim gönderilir.

Bunun için notification nesnesi ise aşağıdaki şekilde  ayarlanacaktır.

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


FCM ile Özelleştirilmiş Bildirimler Gönderme

Bazı durumlarda default notificationlar işimizi görmeyebilir. Bazı durumlarda bildirim özelleştirilmek istenebilir. Bu durumda pubspec.yaml dosyamıza flutter_local_notifications paketini eklememiz gerekiyor.

Bu paket çok sayıda farklı bildirim türleri ve içerikleri sunuyor. Bu sayede default notifications yerine daha kullanışlı bir tercih olabiliyor.

Paket detaylarına ve kullanım çeşitlerine buradan ulaşabilirsiniz.

Bu paketi kullanırken dikkat etmemiz gereken olay, göndereceğiniz bildirimde notification alanı olmamalıdır. Çünkü notification alanı olduğu zaman otomatik olarak default notification da oluşturuluyor ve iki adet bildirim gelmiş oluyor. Notification nesneniz aşağıdaki gibi olmalıdır.

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.  Öncelikle custom_notifications.dart veya dilediğiniz bir isimde dosya oluşturuyoruz.

2. Daha sonra aşağıdaki kodları oluşturduğumuz dosyaya dahil ediyoruz.

import 'dart:convert';

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

//Plugin tanımlaması yapıldı.
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();

// Özelleştirilmiş bildirimlerimizi bir class haline getirerek karmaşıklığı azaltıyoruz.
// Sadece CustomNotifications.showNotification(message) diyerek özelleştirilmiş bildirimleri alacağız.
class CustomNotifications {

// Her defasında yeni bir nesne oluşturmaması için sınıfı singleton bir yapıya getiriyoruz.

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

// Bu fonksiyon içerisinde özelleştirilmiş bildirimlere ait gerekli tanımlamalar yapılmaktadaır.
initializeNotification() async {

// app_icon bildiriminize ait icon bilgisini tutar ve android/app/src/main/res/drawable klasörüne bir app_icon.png isimli dosya atmalısınız.
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('app_icon');

// iOS 'a ait ayarlamalar yapılıyor.
final IOSInitializationSettings initializationSettingsIOS =
IOSInitializationSettings(
requestAlertPermission: false,
requestBadgePermission: false,
requestSoundPermission: false,
onDidReceiveLocalNotification: _onDidReceiveLocalNotification,
);

//iOS ve android için yapılan ayarlamalar birleştirilerek genel bir ayarlama nesnesi haline getiriliyor.
final InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
//macOS için ayarlama seçeneği de bulunuyor. Detaylar için pub.dev sayfasından paket detaylarına bakabilirsiniz.
);

//Hazırlanan ayarlar ile custom notification hale getiriliyor.
//Onselect metodu ile bildirim ile birlikte gönderilen data alınıyor.
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;
}


/*
* showNotification metodu ile bildirim gösterme işlemini gerçekleştiriyoruz.
* Paket sayfasına girdiğiniz zaman çok sayıda farklı özelleştirilebilir bildirim çeşitleri bulabilirsiniz.
* */
static showNotification(RemoteMessage message) async {
//RemoteMessage türündeki parametremiz FCM tarafından oluşturulan
// onMessage, onbackGroundMessage ve onMessageOpenedApp metodlarından gelen message datasıdır.
//FCM tarafından bildirim alındıktan sonra burada işlenerek customNotification oluşturuluyor.

const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
'1234567890', 'Channel Name', 'Channel Description', //Channel kısmı ayarlardan uygulama bilgilerine girdiğinizde göreceğiniz bilgilerdir.
importance: Importance.max,
priority: Priority.high,
ticker: 'ticker');

const NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);

//Bildirimimizi gösterecek metod burası. Buraya parametre olarak gelen message verimizden istediğimiz alanları ekliyoruz.
// message.data["firstName"] bildirim başlığını ifade ediyor.
// message.data["webSite"] bildirim metnini ifade ediyor.
// payload kısmı ise bildirime tıklandıktan sonra işlenmesini istediğiniz verileri ifade ediyor.
await flutterLocalNotificationsPlugin.show(0, message.data["firstName"],
message.data["webSite"], platformChannelSpecifics,
payload: json.encode(message.data));
}

}

3. Gerekli eklemeleri yaptıktan sonra main.dart dosyamızı açıyoruz ve main metoduna aşağıdaki eklemeyi yapıyoruz.

runApp(MyApp());
CustomNotifications().initializeNotification(); //Eklenecek satır burası.

4. Daha sonra FCM tarafından bildirimleri okuduğumuz onMessage, onBackgroundMessage ve onMessageOpenedApp metodlarının içerisine aşağıdaki satırı eklememiz gerekiyor.

CustomNotifications.showNotification(message);

Artık özelleştirilmiş bildirimleri almaya da hazır olduğumuza göre PostMan üzerinden bir kaç deneme ile gerekli kontrolleri sağlayabiliriz.

PostMan Üzerinden FCM HTTP v1 API ile Bildirim Göndermek

Hazırladığınız fonksiyonları uygulamanız kapalı iken test etmek için en uygun yöntem PostMan üzerinden bildirim göndermektir. Bunun için aşağıdaki adımları uygulamanız gerekmektedir.

1. Öncelikle PostMan sitesine giriyoruz. Üyeliğimiz yoksa ücretsiz bir şekilde oluşturuyoruz.

2. Workspaces menüsüen tıkladıktan sonra new workspace butonuna tıklıyoruz.

3. Workspace oluşturduktan sonra sol üst köşede new butonuna tıkladıktan sonra request butonuna tıklaayrak yeni bir request oluşturup isimlendiriyoruz.

4.  Get olan istek tipini post olarak ayarlıyoruz ve yan tarafa https://fcm.googleapis.com/v1/projects/your-project-name/messages:send yazıyoruz. (your-project-name kısmını düzeltmeyi unutmayın.)

5. Headers sekmesine geliyoruz ve Authorization key değerini ekliyoruz. Karşısına ise Bearer your_access_token yazıyoruz. access token değerini send_notification.dart sayfasında bulunan  aşağıdaki satırdan elde edebilirsiniz. (Access token uygulama içerisinde bir kez bildirim gönderdiğiniz zaman konsolde gözükecektir.)

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

6. Authorization altında Content-Type isminde yeni bir key daha oluşturuyoruz. Karşısına ise application/json değerini yazıyoruz.

7. Body kısmına ise oluşturduğumuz notification nesnemizi aşağıdaki gibi ekliyoruz. (Ekleme yapmak için raw formatını seçmeniz daha iyi olacaktır.)

{
"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. Daha sonra send butonuna bastığınız takdirde herhangi bir sorun yoksa bildirim gönderebiliyor olmanız gerekiyor. Bildirim gönderildiği zaman aşağıdaki gibi bir geri dönüş almalısınız.

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


Bu aşamayı da tamamladıktan sonra bildirim gönderip alma işlemlerimizi tamamlamış bulunuyoruz. Artık uygulamalarımızda rahatlıkla bildirim gönderip alabiliriz.

Projeye ait github reposuna buradan ulaşabilirsiniz.