何をするのか
iOSにて、FCM(Firebase Cloud Messaging)を使って以下のような画像付き通知を受信します。
落とし穴
いくつか落とし穴があります。気をつけてください。
-
Firebase(GUI)から通知を送ると、受信するまでに1〜2分ほどラグが発生します
- PythonやGoなどでアプリサーバーをたて、Firebaseのプッシュ通知サーバー(
https://fcm.googleapis.com/v1/projects/プロジェクト名//messages:send
)へリクエストを送りプッシュ通知を送信してください。これでラグ発生することなく通知を受信できます。
- PythonやGoなどでアプリサーバーをたて、Firebaseのプッシュ通知サーバー(
-
画像付き通知を受信するには、iOSアプリ側でNotification Service app extension を使用してリモート通知のペイロードを編集する必要があります。
- 対処は2つ後のセクション(iOSアプリ側でNotification Service app extension を使用してリモート通知のペイロードを編集する)に書いておきます
アプリサーバー経由でFCM通知サーバーへアクセスする
今回は、Python(FastAPI)×Render でアプリサーバーを構築し、通知サーバーへアクセスしています。
以下、Pythonのコードです。プロジェクトに依存している箇所は変更していますので、適宜自身のプロジェクトと照らし合わせて変更してください mm
実装を見ると分かりますが、アプリサーバー経由で通知サーバーへアクセスする際、アクセストークンが必要だったり、リクエストパラメータを間違えないよう設定する必要があります。(パラメータに関するリファレンスはコードの下に示しておきます)
# API
from fastapi import FastAPI # pip install fastapi
from pydantic import BaseModel # リクエストbodyを定義するために必要
import uvicorn # pip install uvicorn
# FCMを用いたPush通知
import requests # pip install requests
from oauth2client.service_account import ServiceAccountCredentials # pip install oauth2client
import json
# パラメータ
PROJECT_ID = '' # 任意
JSON_PATH = '〜.json' # 任意
BASE_URL = 'https://fcm.googleapis.com'
FCM_ENDPOINT = 'v1/projects/' + PROJECT_ID + '/messages:send'
FCM_URL = BASE_URL + '/' + FCM_ENDPOINT
print(FCM_URL)
SCOPES = ['https://www.googleapis.com/auth/firebase.messaging']
def _get_access_token():
"""Retrieve a valid access token that can be used to authorize requests.
:return: Access token.
"""
credentials = ServiceAccountCredentials.from_json_keyfile_name(
JSON_PATH , SCOPES)
access_token_info = credentials.get_access_token()
return access_token_info.access_token
def _send_fcm_message(fcm_message):
"""Send HTTP request to FCM with given message.
Args:
fcm_message: JSON object that will make up the body of the request.
"""
# [START use_access_token]
headers = {
'Authorization': 'Bearer ' + _get_access_token(),
'Content-Type': 'application/json; UTF-8',
}
# [END use_access_token]
resp = requests.post(FCM_URL, data=json.dumps(fcm_message), headers=headers)
if resp.status_code == 200:
print('Message sent to Firebase for delivery, response:')
print(resp.text)
return True
else:
print('Unable to send message to Firebase')
print(resp.text)
return False
# main
app = FastAPI()
# POSTでiOSアプリから受け取るデータ
class ToUser(BaseModel):
user_fcm_token: str
title: str
body: str
imageURL: str
# POST通信
@app.post("/pushNotification/")
def create_pushNotification(toUser: ToUser):
common_message = {
"message":{
"token": toUser.user_fcm_token,
"notification":{
"body": toUser.body,
"title": toUser.title,
"image": toUser.imageURL
},
"apns":{
"payload":{
"aps":{
"mutable-content":1
}
},
"fcm_options": {
"image": toUser.imageURL
}
}
}
} # POSTでデータを送るためにJSON形式に変換
print(json.dumps(common_message, indent=2))
# アクセストークンの取得も行なっている
isPushNotification = _send_fcm_message(common_message)
if isPushNotification:
return {"id": 0, "result": "success"}
else:
return {"id": 0, "result": "failure"}
FCM通知サーバーへのリクエストパラメータに関するリファレンス
iOSアプリ側でNotification Service app extension を使用してリモート通知のペイロードを編集する
画像付き通知の 画像 を受信しバナーへ表示するためには、Notification Service app extension を用いて画像ダウンロードするプログラムを実装する必要があります。
以下2つのサイトを見ながら(漏れなく)実装追加してみてください。
1. プロジェクトに Notification Service app extension を追加する
2. NotificationService.swift を編集し画像ダウンロードを実装
// 1 にて自動生成されるファイル 手動で追加するものではないので注意
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
guard let content = (request.content.mutableCopy() as? UNMutableNotificationContent) else {
contentHandler(request.content)
return
}
let userInfo = request.content.userInfo as NSDictionary
let fcmOptions = userInfo["fcm_options"] as? [String: String]
guard let fcmOptions = fcmOptions,
let url = URL(string: fcmOptions["image"] ?? "")
else {
contentHandler(content)
return
}
print(url)
let task = URLSession.shared.dataTask(
with: url,
completionHandler: { data, _, error in
let fileName = url.lastPathComponent
let writePath = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName)
do {
try data?.write(to: writePath)
let attachment = try UNNotificationAttachment(identifier: fileName, url: writePath, options: nil)
content.attachments = [attachment]
contentHandler(content)
} catch {
print("error: \(error)")
contentHandler(content)
}
})
task.resume()
}
override func 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.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}
アプリサーバーの構築
と Notification Service app extension を用いた実装
を済ませれば、本記事冒頭に示したように画像付き通知が受信可能なはずです。
それでは、良いFCMライフを!