假設手機A想要發送推播給B,如果企業有自己的DB和APN Server,自然可以透過呼叫後端API來達成想要的功能。但是畢竟自己架設後台也是一個負擔,所以如果可以透過使用Firebase的服務可以省下不少* (Firebase 並不是完全免費的,要看使用量)
工作的流程大概如圖所示:
- 手機A將資料寫入Database。
- 透過Functions部署的Node.js監控Database變化,如果有資料異動,則依造寫入的資料讀取token,將token丟給Messaging去發送推播。
- 推播完成,刪除暫存資料。
所以可以發現,最重要的部分是Node.js的部署,由Node.js寫的function去監控和觸發事件。
萬事起頭難,讓我們一步步來建立這樣的環境吧!
前置作業:
- XCode & CocoaPods
- Apple 開發者帳號
- iOS App使用 Swift
環境建立Building environment - Firebase & Node.js
*如果尚未安裝Node JS和NPM,請先安裝後再繼續。
- 建立一個新的 Firebase project
- 登記Web 和 iOS app
- 在Local 建立一個folder 作為要部署之用,建立完成之後,在該資料夾下進行Node 初始化:
npm init - 接下來安裝Firebase CLI:
npm install -g firebase-tools
- 接下來登入並且初始化Project:
firebase login firebase init functions
- 結束後應該可以看到functions的資料夾已經建立好了。
- 測試看看是否可以使用。打開index.js 把 exports.helloWorld 這段unmark。存檔後發布:
firebase deploy --only functions
- 打開瀏覽器並輸入Function URL你可以看到“Hello from Firebase!”
環境建立 Xcode & Firebase messaging
在這篇我們要建立Client - App部分,也就是設定App可以接收Notification。我們需要Firebase messaging SDK來取得token,並將token寫到Firebase database。詳細的步驟可以參考這裏。
步驟:
- 建立新的Project (或利用舊有的)。並且開啟Push Notification (App > Capabilities) 。並且記得要把Firebase的GoogleServices-Info.plist 加到專案內。
- 用Cocoapods 安裝Firebase messaging. 除了Firebase SDK ,我另外加了Toast-Swift作為在App開啟的狀態下顯示推播用。target 'testApp' do# Comment the next line if you don't want to use dynamic frameworksuse_frameworks!pod 'Firebase/Messaging'pod 'Firebase/Auth'pod 'Firebase/Database'pod 'Toast-Swift' //給App at Foreground# Pods for testAppend
- 安裝pods後開啟專案(.xcworkspace)
- 開啟AppDelegate.swift ,加入Notification 需要的methodsThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
// // AppDelegate.swift // CryptoCurrencyPayment // // Created by Nick on 4/16/20. // Copyright © 2020 Nick. All rights reserved. // import UIKit import Firebase @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? let gcmMessageIDKey = "gcm.message_id" func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. self.window = UIWindow(frame: UIScreen.main.bounds) self.window?.makeKeyAndVisible() FirebaseApp.configure() // [START set_messaging_delegate] Messaging.messaging().delegate = self // [END set_messaging_delegate] // [START register_for_notifications] //Register for remote notifications if #available(iOS 10.0, *) { // For iOS 10 display notification (sent via APNS) UNUserNotificationCenter.current().delegate = self let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] UNUserNotificationCenter.current().requestAuthorization( options: authOptions, completionHandler: {_, _ in }) } else { let settings: UIUserNotificationSettings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil) application.registerUserNotificationSettings(settings) } application.registerForRemoteNotifications() // [END register_for_notifications] return true } // MARK: UISceneSession Lifecycle func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { // Called when a new scene session is being created. // Use this method to select a configuration to create the new scene with. return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) { // Called when the user discards a scene session. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } // [START receive_message] func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) { // If you are receiving a notification message while your app is in the background, // this callback will not be fired till the user taps on the notification launching the application. // TODO: Handle data of notification // With swizzling disabled you must let Messaging know about the message, for Analytics Messaging.messaging().appDidReceiveMessage(userInfo) // Print message ID. if let messageID = userInfo[gcmMessageIDKey] { print("Message ID: \(messageID)") } NotificationCenter.default.post(name: .didReceiveMoney, object: nil, userInfo: userInfo) // Print full message. print(userInfo) } func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { // If you are receiving a notification message while your app is in the background, // this callback will not be fired till the user taps on the notification launching the application. // TODO: Handle data of notification // With swizzling disabled you must let Messaging know about the message, for Analytics Messaging.messaging().appDidReceiveMessage(userInfo) // Print message ID. if let messageID = userInfo[gcmMessageIDKey] { print("Message ID: \(messageID)") } NotificationCenter.default.post(name: .didReceiveMoney, object: nil, userInfo: userInfo) // Print full message. print(userInfo) completionHandler(UIBackgroundFetchResult.newData) } // [END receive_message] func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { print("Unable to register for remote notifications: \(error.localizedDescription)") } // This function is added here only for debugging purposes, and can be removed if swizzling is enabled. // If swizzling is disabled then this function must be implemented so that the APNs token can be paired to // the FCM registration token. func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { print("APNs token retrieved: \(deviceToken)") // With swizzling disabled you must set the APNs token here. // Messaging.messaging().apnsToken = deviceToken } } // [START ios_10_message_handling] @available(iOS 10, *) extension AppDelegate : UNUserNotificationCenterDelegate { // Receive displayed notifications for iOS 10 devices. func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { let userInfo = notification.request.content.userInfo // With swizzling disabled you must let Messaging know about the message, for Analytics // Messaging.messaging().appDidReceiveMessage(userInfo) // Print message ID. if let messageID = userInfo[gcmMessageIDKey] { print("Message ID: \(messageID)") } NotificationCenter.default.post(name: .didReceiveMoney, object: nil, userInfo: userInfo) // Print full message. print(userInfo) // Change this to your preferred presentation option completionHandler([]) } func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { let userInfo = response.notification.request.content.userInfo // Print message ID. if let messageID = userInfo[gcmMessageIDKey] { print("Message ID: \(messageID)") } NotificationCenter.default.post(name: .didReceiveMoney, object: nil, userInfo: userInfo) // Print full message. print(userInfo) completionHandler() } } // [END ios_10_message_handling] extension AppDelegate : MessagingDelegate { // [START refresh_token] func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) { print("Firebase registration token: \(fcmToken)") // let dataDict:[String: String] = ["token": fcmToken] // NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: dataDict) // TODO: If necessary send token to application server. // Note: This callback is fired at each app startup and whenever a new token is generated. let defaults = UserDefaults.standard defaults.set(fcmToken, forKey: "fcmToken") } // [END refresh_token] // [START ios_10_data_message] // Receive data messages on iOS 10+ directly from FCM (bypassing APNs) when the app is in the foreground. // To enable direct data messages, you can set Messaging.messaging().shouldEstablishDirectChannel to true. func messaging(_ messaging: Messaging, didReceive remoteMessage: MessagingRemoteMessage) { print("Received data message: \(remoteMessage.appData)") } // [END ios_10_data_message] } - XCode設定算是告一段落,接下來開啟Firebase project的設定(Settings > Cloud Messaging),並且上傳APNs Authentication Key。參考如何取得APN Key。
- 安裝App 到實機上,試著將xcode console端取得的token丟到Firebase messaging 看看能不能測試發送推播。
- 接下要寫一些Code,將收到token 存到database。直接下載Demo專案。App中名單列表紅色表示為自己機器,主要程式碼都會在ViewController.swift 。每次按下"Add this device to DB"按鈕,就會把push token存到Firebase database。Database的結構如下:
<Host>
| - msgPool
| - userList
| - <Token>
|- userName : <UserName>
資料結構中,msgPool是用來存放推播消息,基本上都是空的,只有在推播送出資料後會有資料,然後監控的node js發現有資料後會將資料讀出並發送推播,推播發送後即刪除。userList 則是存使用者的token,每按一次"Add this device to DB"就會寫入/更新最新的名稱。

留言
張貼留言