Flutter uygulamasında mesaj alın

Gelen mesajlar, cihazın durumuna bağlı olarak farklı şekilde işlenir. Bu senaryoları ve FCM'nin kendi uygulamanıza nasıl entegre edileceğini anlamak için öncelikle bir cihazın bulunabileceği çeşitli durumları belirlemek önemlidir:

Eyalet Açıklama
Ön plan Uygulama açık, görünür ve kullanımdayken.
Arka plan Uygulama açık ancak arka planda (küçültülmüş) olduğunda. Bu durum genellikle kullanıcı cihazdaki "ana sayfa" düğmesine bastığında, uygulama değiştiriciyi kullanarak başka bir uygulamaya geçtiğinde veya uygulamayı farklı bir sekmede (web) açtığında ortaya çıkar.
Fesih Cihaz kilitliyken veya uygulama çalışmadığında.

Uygulamanın FCM üzerinden mesaj yükü alabilmesi için karşılanması gereken birkaç ön koşul vardır:

  • Uygulama en az bir kez açılmış olmalıdır (FCM'ye kaydolmaya izin vermek için).
  • iOS'te kullanıcı uygulamayı uygulama değiştiriciden kaydırırsa arka plan mesajlarının tekrar çalışmaya başlaması için uygulamanın manuel olarak yeniden açılması gerekir.
  • Android'de kullanıcı, uygulamayı cihaz ayarlarından kapatırsa mesajların çalışmaya başlaması için uygulamanın manuel olarak yeniden açılması gerekir.
  • Web'de, web push sertifikanızla (getToken() kullanarak) bir jeton istemiş olmanız gerekir.

Mesaj alma izni isteme

iOS, macOS, web ve Android 13 (veya sonraki sürümler) sürümlerinde, FCM yüklerinin cihazınızda alınabilmesi için önce kullanıcıdan izin almanız gerekir.

firebase_messaging paketi, requestPermission yöntemi aracılığıyla izin istemek için basit bir API sağlar. Bu API, istemek istediğiniz izin türlerini tanımlayan bir dizi adlandırılmış bağımsız değişkeni kabul eder. Örneğin, bildirim yükü içeren mesajlaşmanın ses tetikleyip tetikleyemeyeceği veya Siri aracılığıyla mesajları okuyup okuyamayacağı gibi. Yöntem varsayılan olarak makul varsayılan izinler ister. Referans API'de her iznin ne için kullanıldığına dair tam dokümanlar sağlanır.

Başlamak için uygulamanızdan yöntemi çağırın (iOS'te yerel bir modal görüntülenir, web'de ise tarayıcının yerel API akışı tetiklenir):

FirebaseMessaging messaging = FirebaseMessaging.instance;

NotificationSettings settings = await messaging.requestPermission(
  alert: true,
  announcement: false,
  badge: true,
  carPlay: false,
  criticalAlert: false,
  provisional: false,
  sound: true,
);

print('User granted permission: ${settings.authorizationStatus}');

İstekten döndürülen NotificationSettings nesnesinin authorizationStatus mülkü, kullanıcının genel kararını belirlemek için kullanılabilir:

  • authorized: Kullanıcı izin verdi.
  • denied: Kullanıcı izin vermedi.
  • notDetermined: Kullanıcı henüz izin verip vermeyeceğine karar vermemiştir.
  • provisional: Kullanıcı geçici izin verdi

NotificationSettings'teki diğer özellikler, belirli bir iznin mevcut cihazda etkin olup olmadığını, devre dışı bırakılıp bırakılmadığını veya desteklenip desteklenmediğini döndürür.

İzin verildikten ve farklı cihaz durumu türleri anlaşıldıktan sonra uygulamanız gelen FCM yüklerini işlemeye başlayabilir.

Mesajları işleme

Uygulamanızın mevcut durumuna bağlı olarak, farklı mesaj türlerine sahip gelen yüklerin işlenmesi için farklı uygulamalar gerekir:

Ön plan mesajları

Uygulamanız ön plandayken mesajları işlemek için onMessage akışını dinleyin.

FirebaseMessaging.onMessage.listen((RemoteMessage message) {
  print('Got a message whilst in the foreground!');
  print('Message data: ${message.data}');

  if (message.notification != null) {
    print('Message also contained a notification: ${message.notification}');
  }
});

Akış, yükle ilgili çeşitli bilgileri (ör. nereden geldiği, benzersiz kimlik, gönderilme zamanı, bildirim içerip içermediği vb.) içeren bir RemoteMessage içerir. Mesaj, uygulamanız ön plandayken alındığından Flutter uygulamanızın durumuna ve bağlamına doğrudan erişebilirsiniz.

Ön plan ve bildirim mesajları

Uygulama ön plandayken gelen bildirim mesajları, hem Android hem de iOS'te varsayılan olarak görünür bir bildirim göstermez. Ancak bu davranışı geçersiz kılabilirsiniz:

  • Android'de "Yüksek Öncelikli" bir bildirim kanalı oluşturmanız gerekir.
  • iOS'te, uygulamanın sunu seçeneklerini güncelleyebilirsiniz.

Arka plan mesajları

Arka plan mesajlarını işleme süreci, yerel (Android ve Apple) ve web tabanlı platformlarda farklıdır.

Apple platformları ve Android

onBackgroundMessage işleyicisi kaydederek arka plan mesajlarını işleyin. Mesaj alındığında, uygulamanız çalışmadığında bile mesajları işlemenize olanak tanıyan bir izole oluşturulur (yalnızca Android; iOS/macOS için ayrı bir izole gerekmez).

Arka plan mesaj işleyicinizle ilgili göz önünde bulundurmanız gereken birkaç nokta vardır:

  1. Anonim işlev olmamalıdır.
  2. Üst düzey bir işlev olmalıdır (ör. başlatma gerektiren bir sınıf yöntemi olmamalıdır).
  3. Flutter 3.3.0 veya sonraki bir sürümü kullanırken mesaj işleyici, işlev beyanının hemen üstüne @pragma('vm:entry-point') ile not edilmelidir (aksi takdirde, sürüm modu için ağaç sallama sırasında kaldırılabilir).
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  // If you're going to use other Firebase services in the background, such as Firestore,
  // make sure you call `initializeApp` before using other Firebase services.
  await Firebase.initializeApp();

  print("Handling a background message: ${message.messageId}");
}

void main() {
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
  runApp(MyApp());
}

İşleyici, uygulama bağlamınızın dışında kendi izole ortamında çalıştığından uygulama durumunu güncellemek veya kullanıcı arayüzünü etkileyen mantık yürütmek mümkün değildir. Ancak HTTP istekleri gibi mantık işlemleri yapabilir, G/Ç işlemleri (ör. yerel depolama alanını güncelleme) gerçekleştirebilir, diğer eklentilerle iletişim kurabilirsiniz.

Mantığınızı en kısa sürede tamamlamanız da önerilir. Uzun ve yoğun görevler çalıştırmak cihaz performansını etkiler ve işletim sisteminin işlemi sonlandırmasına neden olabilir. Görevler 30 saniyeden uzun sürerse cihaz, işlemi otomatik olarak sonlandırabilir.

Web

Web'de, arka planda çalışan bir JavaScript Hizmet Çalışanı yazın. Arka plan mesajlarını işlemek için hizmet çalışanını kullanın.

Başlamak için web dizininizde yeni bir dosya oluşturun ve dosyayı firebase-messaging-sw.js olarak adlandırın:

// Please see this file for the latest firebase-js-sdk version:
// https://github.com/firebase/flutterfire/blob/master/packages/firebase_core/firebase_core_web/lib/src/firebase_sdk_version.dart
importScripts("https://www.gstatic.com/firebasejs/10.7.0/firebase-app-compat.js");
importScripts("https://www.gstatic.com/firebasejs/10.7.0/firebase-messaging-compat.js");

firebase.initializeApp({
  apiKey: "...",
  authDomain: "...",
  databaseURL: "...",
  projectId: "...",
  storageBucket: "...",
  messagingSenderId: "...",
  appId: "...",
});

const messaging = firebase.messaging();

// Optional:
messaging.onBackgroundMessage((message) => {
  console.log("onBackgroundMessage", message);
});

Dosya hem uygulama hem de mesajlaşma SDK'larını içe aktarmalı, Firebase'i başlatmalı ve messaging değişkenini göstermelidir.

Ardından, çalışanın kaydedilmesi gerekir. index.html dosyasında, Flutter'ı başlatan <script> etiketini değiştirerek çalışanı kaydedin:

<script src="flutter_bootstrap.js" async>
  if ('serviceWorker' in navigator) {
    window.addEventListener('load', function () {
      navigator.serviceWorker.register('firebase-messaging-sw.js', {
        scope: '/firebase-cloud-messaging-push-scope',
      });
    });
  }
</script>

Hâlâ eski şablonlama sistemini kullanıyorsanız Flutter'ı önyükleme yapan <script> etiketini aşağıdaki gibi değiştirerek işçiyi kaydedebilirsiniz:

<html>
<body>
  <script>
      var serviceWorkerVersion = null;
      var scriptLoaded = false;
      function loadMainDartJs() {
        if (scriptLoaded) {
          return;
        }
        scriptLoaded = true;
        var scriptTag = document.createElement('script');
        scriptTag.src = 'main.dart.js';
        scriptTag.type = 'application/javascript';
        document.body.append(scriptTag);
      }

      if ('serviceWorker' in navigator) {
        // Service workers are supported. Use them.
        window.addEventListener('load', function () {
          // Register Firebase Messaging service worker.
          navigator.serviceWorker.register('firebase-messaging-sw.js', {
            scope: '/firebase-cloud-messaging-push-scope',
          });

          // Wait for registration to finish before dropping the <script> tag.
          // Otherwise, the browser will load the script multiple times,
          // potentially different versions.
          var serviceWorkerUrl =
            'flutter_service_worker.js?v=' + serviceWorkerVersion;

          navigator.serviceWorker.register(serviceWorkerUrl).then((reg) => {
            function waitForActivation(serviceWorker) {
              serviceWorker.addEventListener('statechange', () => {
                if (serviceWorker.state == 'activated') {
                  console.log('Installed new service worker.');
                  loadMainDartJs();
                }
              });
            }
            if (!reg.active && (reg.installing || reg.waiting)) {
              // No active web worker and we have installed or are installing
              // one for the first time. Simply wait for it to activate.
              waitForActivation(reg.installing ?? reg.waiting);
            } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
              // When the app updates the serviceWorkerVersion changes, so we
              // need to ask the service worker to update.
              console.log('New service worker available.');
              reg.update();
              waitForActivation(reg.installing);
            } else {
              // Existing service worker is still good.
              console.log('Loading app from service worker.');
              loadMainDartJs();
            }
          });

          // If service worker doesn't succeed in a reasonable amount of time,
          // fallback to plaint <script> tag.
          setTimeout(() => {
            if (!scriptLoaded) {
              console.warn(
                'Failed to load app from service worker. Falling back to plain <script> tag.'
              );
              loadMainDartJs();
            }
          }, 4000);
        });
      } else {
        // Service workers not supported. Just drop the <script> tag.
        loadMainDartJs();
      }
  </script>
</body>

Ardından Flutter uygulamanızı yeniden başlatın. İşçi kaydedilir ve arka plan mesajları bu dosya üzerinden işlenir.

Etkileşimi ele alma

Bildirimler görünür bir işaret olduğundan kullanıcıların bunlarla etkileşime geçmesi (basarak) yaygın bir durumdur. Hem Android hem de iOS'te varsayılan davranış, uygulamayı açmaktır. Uygulama sonlandırılmışsa başlatılır, arka plandaysa ön plana getirilir.

Bildirimin içeriğine bağlı olarak, uygulama açıldığında kullanıcının etkileşimini yönetmek isteyebilirsiniz. Örneğin, bir bildirim aracılığıyla yeni bir sohbet mesajı gönderilirse ve kullanıcı bildirime dokunursa uygulama açıldığında belirli bir ileti dizisini açmak isteyebilirsiniz.

firebase-messaging paketi, bu etkileşimi yönetmenin iki yolunu sunar:

  • getInitialMessage(): Uygulama, sonlandırılmış bir durumdan açılırsa RemoteMessage içeren bir Future döndürülür. Kullanıldıktan sonra RemoteMessage kaldırılır.
  • onMessageOpenedApp: Uygulama arka plan durumundayken açıldığında RemoteMessage yayınlayan bir Stream.

Kullanıcılarınıza sorunsuz bir kullanıcı deneyimi sunmak için her iki senaryoyu da ele almanız önerilir. Aşağıdaki kod örneğinde bunun nasıl yapılabileceği özetlenmiştir:

class Application extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _Application();
}

class _Application extends State<Application> {
  // It is assumed that all messages contain a data field with the key 'type'
  Future<void> setupInteractedMessage() async {
    // Get any messages which caused the application to open from
    // a terminated state.
    RemoteMessage? initialMessage =
        await FirebaseMessaging.instance.getInitialMessage();

    // If the message also contains a data property with a "type" of "chat",
    // navigate to a chat screen
    if (initialMessage != null) {
      _handleMessage(initialMessage);
    }

    // Also handle any interaction when the app is in the background via a
    // Stream listener
    FirebaseMessaging.onMessageOpenedApp.listen(_handleMessage);
  }

  void _handleMessage(RemoteMessage message) {
    if (message.data['type'] == 'chat') {
      Navigator.pushNamed(context, '/chat',
        arguments: ChatArguments(message),
      );
    }
  }

  @override
  void initState() {
    super.initState();

    // Run code required to handle interacted messages in an async function
    // as initState() must not be async
    setupInteractedMessage();
  }

  @override
  Widget build(BuildContext context) {
    return Text("...");
  }
}

Etkileşimi nasıl ele alacağınız, uygulama kurulumunuza bağlıdır. Yukarıdaki örnekte, StatefulWidget'ın kullanıldığı temel bir görsel gösterilmektedir.

Mesajlar'ı yerelleştirme

Yerelleştirilmiş dizeleri iki farklı şekilde gönderebilirsiniz:

  • Kullanıcılarınızın her birinin tercih ettiği dili sunucunuzda saklayın ve her dil için özelleştirilmiş bildirimler gönderin
  • Yerelleştirilmiş dizeleri uygulamanıza yerleştirin ve işletim sisteminin yerel ayarlarından yararlanın

İkinci yöntemi kullanmak için:

Android

  1. resources/values/strings.xml içinde varsayılan dil mesajlarınızı belirtin:

    <string name="notification_title">Hello world</string>
    <string name="notification_message">This is a message</string>
    
  2. Çevrilmiş iletileri values-language dizininde belirtin. Örneğin, Fransızca mesajları resources/values-fr/strings.xml olarak belirtin:

    <string name="notification_title">Bonjour le monde</string>
    <string name="notification_message">C'est un message</string>
    
  3. Sunucu yükü alanında, yerelleştirilmiş mesajınız için title, message ve body anahtarları yerine title_loc_key ve body_loc_key kullanın ve bunları görüntülemek istediğiniz mesajın name özelliğine ayarlayın.

    Mesaj yükü şöyle görünür:

    {
      "data": {
        "title_loc_key": "notification_title",
        "body_loc_key": "notification_message"
      }
    }
    

iOS

  1. Base.lproj/Localizable.strings içinde varsayılan dil mesajlarınızı belirtin:

    "NOTIFICATION_TITLE" = "Hello World";
    "NOTIFICATION_MESSAGE" = "This is a message";
    
  2. Çevrilmiş iletileri language.lproj dizininde belirtin. Örneğin, Fransızca mesajları fr.lproj/Localizable.strings olarak belirtin:

    "NOTIFICATION_TITLE" = "Bonjour le monde";
    "NOTIFICATION_MESSAGE" = "C'est un message";
    

    Mesaj yükü şöyle görünür:

    {
      "data": {
        "title_loc_key": "NOTIFICATION_TITLE",
        "body_loc_key": "NOTIFICATION_MESSAGE"
      }
    }
    

Mesaj yayınlama verilerini dışa aktarmayı etkinleştirme

Daha ayrıntılı analiz için mesaj verilerinizi BigQuery'ye aktarabilirsiniz. BigQuery, BigQuery SQL'i kullanarak verileri analiz etmenize, başka bir bulut sağlayıcıya aktarmanıza veya özel ML modelleriniz için kullanmanıza olanak tanır. BigQuery'ye yapılan dışa aktarma işlemi, mesaj türüne veya mesajın API aracılığıyla mı yoksa Bildirimler derleyicisi aracılığıyla mı gönderildiğine bakılmaksızın mesajlarla ilgili mevcut tüm verileri içerir.

Dışa aktarma işlemini etkinleştirmek için önce burada açıklanan adımları, ardından aşağıdaki talimatları uygulayın:

Android

Aşağıdaki kodu kullanabilirsiniz:

await FirebaseMessaging.instance.setDeliveryMetricsExportToBigQuery(true);

iOS

iOS için AppDelegate.m değerini aşağıdaki içerikle değiştirmeniz gerekir.

#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
#import <Firebase/Firebase.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GeneratedPluginRegistrant registerWithRegistry:self];
  // Override point for customization after application launch.
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

- (void)application:(UIApplication *)application
    didReceiveRemoteNotification:(NSDictionary *)userInfo
          fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
  [[FIRMessaging extensionHelper] exportDeliveryMetricsToBigQueryWithMessageInfo:userInfo];
}

@end

Web

Web için SDK'nın 9. sürümünü kullanmak üzere hizmet işleyicinizi değiştirmeniz gerekir. 9. sürümün paketlenmesi gerekir. Bu nedenle, hizmet çalışanının çalışmasını sağlamak için esbuild gibi bir paketleyici kullanmanız gerekir. Bunu nasıl yapacağınızı öğrenmek için örnek uygulamaya bakın.

v9 SDK'sına geçtikten sonra aşağıdaki kodu kullanabilirsiniz:

import {
  experimentalSetDeliveryMetricsExportedToBigQueryEnabled,
  getMessaging,
} from 'firebase/messaging/sw';
...

const messaging = getMessaging(app);
experimentalSetDeliveryMetricsExportedToBigQueryEnabled(messaging, true);

Hizmet çalışanınızın yeni sürümünü web klasörüne aktarmak için yarn build komutunu çalıştırmayı unutmayın.

iOS'te bildirimlerde resim gösterme

Apple cihazlarda, gelen FCM bildirimlerinin FCM yükünden resimleri görüntülemesi için ek bir bildirim hizmeti uzantısı eklemeniz ve uygulamanızı bu uzantıyı kullanacak şekilde yapılandırmanız gerekir.

Firebase telefon kimlik doğrulamasını kullanıyorsanız Pod dosyanıza Firebase Auth pod'unu eklemeniz gerekir.

1. Adım: Bildirim hizmeti uzantısı ekleyin

  1. Xcode'da Dosya > Yeni > Hedef...'i tıklayın.
  2. Olası hedeflerin listesi bir modal pencerede gösterilir. Notification Service Extension'ı seçmek için aşağı kaydırın veya filtreyi kullanın. Sonraki'yi tıklayın.
  3. Bir ürün adı ekleyin (bu eğitime devam etmek için "ImageNotification"ı kullanın), dili Objective-C olarak ayarlayın ve Son'u tıklayın.
  4. Etkinleştir'i tıklayarak şemayı etkinleştirin.

2. Adım: Pod dosyasına hedef ekleyin

Yeni uzantınızı Podfile'e ekleyerek Firebase/Messaging kapsülüne erişebildiğinden emin olun:

  1. Gezgin'den Podfile dosyasını açın: Pod'lar > Podfile

  2. Dosyanın en altına ilerleyip şunları ekleyin:

    target 'ImageNotification' do
      use_frameworks!
      pod 'Firebase/Auth' # Add this line if you are using FirebaseAuth phone authentication
      pod 'Firebase/Messaging'
    end
    
  3. ios veya macos dizininden pod install'ü kullanarak kapsüllerinizi yükleyin ya da güncelleyin.

3. Adım: Uzantıyı yardımcıyı kullanın

Bu noktada her şey normal şekilde çalışmaya devam ediyor olmalıdır. Son adım, uzantı yardımcısını çağırmaktır.

  1. Gezinme menüsünden ImageNotification uzantınızı seçin

  2. NotificationService.m dosyasını açın.

  3. Dosyanın en üstünde, aşağıda gösterildiği gibi NotificationService.h'un hemen ardından FirebaseMessaging.h'ü içe aktarın.

    NotificationService.m içeriğini şununla değiştirin:

    #import "NotificationService.h"
    #import "FirebaseMessaging.h"
    #import "FirebaseAuth.h" // Add this line if you are using FirebaseAuth phone authentication
    #import <UIKit/UIKit.h> // Add this line if you are using FirebaseAuth phone authentication
    
    @interface NotificationService ()
    
    @property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
    @property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
    
    @end
    
    @implementation NotificationService
    
    /* Uncomment this if you are using Firebase Auth
    - (BOOL)application:(UIApplication *)app
                openURL:(NSURL *)url
                options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
      if ([[FIRAuth auth] canHandleURL:url]) {
        return YES;
      }
      return NO;
    }
    
    - (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
      for (UIOpenURLContext *urlContext in URLContexts) {
        [FIRAuth.auth canHandleURL:urlContext.URL];
      }
    }
    */
    
    - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
        self.contentHandler = contentHandler;
        self.bestAttemptContent = [request.content mutableCopy];
    
        // Modify the notification content here...
        [[FIRMessaging extensionHelper] populateNotificationContent:self.bestAttemptContent withContentHandler:contentHandler];
    }
    
    - (void)serviceExtensionTimeWillExpire {
        // Called just before the extension will be terminated by the system.
        // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
        self.contentHandler(self.bestAttemptContent);
    }
    
    @end
    

4. Adım: Resmi yüke ekleyin

Artık bildirim yükünüze resim ekleyebilirsiniz. Gönderme isteği oluşturma ile ilgili iOS belgelerini inceleyin. Cihaz tarafından maksimum 300 KB resim boyutu uygulandığını unutmayın.