2021/05/19

Firebase Cloud Messagingを使ってiOSアプリにPush通知を送る

swiftswiftuifirebase

Firebaseの公式ドキュメントをみて対応しました。

対応手順

  1. Apple Developerアカウントにログインして、APNsのキーを作成してp8ファイルをダウンロードします
  2. Apple Developerアカウントにて、開発用及び本番環境用のAPNs証明書を作成します。
  3. 上記をFirebaseコンソール上で プロジェクトを設定 > Cloud Messaging のメニューから先程ダウンロードしたAPNs 認証キー及びAPNs証明書をアップロードします。
  4. firebase-ios-sdkをインストールしてMessagingを使えるようにします。
  5. XCode上で、TARGETS > YOURAPP > Signing & Capabilitiesで Push NotificationsのCapabilityを追加します。
  6. AppDelegateに以下の記述を追加します。
import SwiftUI
import Firebase

@main
struct Application: App {
    
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: NSObject, UIApplicationDelegate, MessagingDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {        
        FirebaseApp.configure()
        Messaging.messaging().delegate = self
        if #available(iOS 10.0, *) {
            UNUserNotificationCenter.current().delegate = self
            let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
            UNUserNotificationCenter.current().requestAuthorization(
                options: authOptions,
                completionHandler: { granted, _ in
                    if granted {
                      // TODO subscribe to fcm topic, etc...
                    }
                }
            )
        } else {
            let settings: UIUserNotificationSettings
                = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
            application.registerUserNotificationSettings(settings)
        }
        application.registerForRemoteNotifications()
        
        return true
    }
    
    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("Oh no! Failed to register for remote notifications with error \(error)")
    }
    
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        var readableToken: String = ""
        for i in 0..<deviceToken.count {
            readableToken += String(format: "%02.2hhx", deviceToken[i] as CVarArg)
        }
        print("Received an APNs device token: \(readableToken)")
    }
}

extension AppDelegate: MessagingDelegate {
    @objc func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
      print("Firebase token: \(String(describing: fcmToken))")
      // let dataDict:[String: String] = ["token": fcmToken ?? ""]
      // NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: dataDict)
    }
}

extension AppDelegate : UNUserNotificationCenterDelegate {
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification,
        withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
    ) {
        if #available(iOS 14, *) {
            completionHandler([[.banner, .list, .sound]])
        } else {
            completionHandler([[.alert, .sound]])
        }
    }
    
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse,
        withCompletionHandler completionHandler: @escaping () -> Void
    ) {
        let userInfo = response.notification.request.content.userInfo
        // 以下の用にしてnotification centerを使って通知し、
        // view側では以下のメソッドを使って、処理を続行します。
        // onReceive(NotificationCenter.default.publisher(for: Notification.Name("didReceiveRemoteNotification")))
        NotificationCenter.default.post(
            name: Notification.Name("didReceiveRemoteNotification"),
            object: nil,
            userInfo: userInfo
        )
        completionHandler()
    }
}

FCMをDebugする

Push通知が届かない時の確認手順です。

  1. Simulator上で通知の表示を確認する
  2. 直接APNsを呼び出してみて確認する
  3. Firebaseコンソールからテストメッセージを送信して確認する

Simulator上で、.apns ファイルをドラッグ&ドロップして通知が来た場合にちゃんと動作するか確認する

{
    "Simulator Target Bundle": "your-app-bundle-id-here",
    "aps":{
        "alert":"Test",
        "sound":"default",
        "badge":1
    }
}

APNs証明書を使って、直接 APNsを呼び出してみて確認する

Google Developers Japan: iOS で Firebase Cloud Messaging をデバッグするの記事が参考になります。

openssl pkcs12 -in aps_development.p12 -out aps_development.pem -nodes -clcerts
curl --http2 --cert ./aps_development.pem \
  -H "apns-topic: com.kumano-te.portfolio-ios" \
  -d '{"aps":{"alert":"Hello from APNs!","sound":"default"}}' \
  https://api.development.push.apple.com/3/device/your-APNs-device-token-here

Firebaseコンソールからテストメッセージを送信して届くかどうか確認する

こちらの記事を参考にして送信します。

PythonからFCMにメッセージを送信する(Topic編)

設定

  1. サービスアカウントを作成
  2. 鍵を作成してダウンロード
  3. 鍵のフルパスを GOOGLE_APPLICATION_CREDENTIALS に設定
  4. firebase_admin packageをインストール
# or you can instal via pip by `pip install firebase_admin`
% poetry add firebase_admin

Pythonでメッセージを送信

import firebase_admin
from firebase_admin import messaging

default_app = firebase_admin.initialize_app()
topic = 'articles'
title = "This is test..."
body = "New article has been published!"
ntf_data = {"key": "value"}

alert = messaging.ApsAlert(
    title=title,
    body=body,
)
aps = messaging.Aps(alert=alert, sound="default", badge=1)
payload = messaging.APNSPayload(aps)

message = messaging.Message(
    notification=messaging.Notification(
        title=title,
        body=body,
    ),
    data=ntf_data,
    topic=topic,
    apns=messaging.APNSConfig(payload=payload),
)

response = messaging.send(message)
print('Successfully sent message:', response)

以上になります。