【Flutter】Cloud Functions x Firestore x FCMでpush通知を送る
ゴール
flutterアプリで通知したい内容と日付を設定すると、その日付に合わせて通知が送られてくる。
結論
通知が送られてきた。
環境
- macOS Big Sur (チップ : M1)
- Flutter : 2.2.3
- Dart : 2.13.4
全体の流れ
flutter側でFCM通知設定とFirestoreへのデータ保存機能、CloudFunctions側でFirestoreのデータ参照と通知送信機能を実装します。
【アプリで通知を受け取る準備をする】
- Flutterプロジェクト側の設定
- Dartファイルに記述するコード
- 通知が送られてくるかの確認
【実際に通知を送るきっかけ(トリガー)を作る】
- Cloud Functionsを始める
- Cloud Functions関数の記述
【アプリで通知を受け取る準備をする】
1. Flutterプロジェクト側の設定
以下の記事を参考にしました。
androidについては、最新のFlutterバージョンでは不要な設定があります。
If you are using Flutter Android Embedding V2 (Flutter Version >= 1.12) then no additional integration steps are required for Android.
2. Dartファイルに記述するコード
動作確認できたコード例をGitHubにアップロードしました。
.dartのファイル構成
- main.dart : 通知設定とUI
- message.dart : Firestoreとやりとりするデータのクラス
- addPush.dart : 通知を設定するページ(UI)
- model.dart : addPush.dartで設定した通知をFirestorenに保存するモデル
通知設定はmain.dart内で完結しています。
アプリは2ページの簡易的な構造になっています。
【実際に通知を送るきっかけ(トリガー)を作る】
1. Cloud Functionsを始める
今回初めてCloudFunctionsを利用しました。
利用開始の手順は公式ドキュメントにも記載されています。
あまりつまづくところはなかったですが、公式ドキュメントに記載がない部分で私が手間取った点↓
2. Cloud Functions関数の記述
公式ドキュメントに従って『プロジェクトの初期化』(firebase init functions)が成功したら、『functions』フォルダ内にindex.jsが生成されます。
(javascript or typescriptでtypescriptを選択したら .ts)
今回の場合、実際に編集が必要だったのはこの『index.js』ファイルだけでした。
関数はexport.以下で定義します。
実際のコード全体
動作確認できたコードは以下です。
確認しやすいように3分ごとに通知が来るようになっていますが、24時間毎とかにも設定できるようです。
const functions = require('firebase-functions')
const admin = require('firebase-admin');
admin.initializeApp()
const firestore = admin.firestore()
//push通知実行メソッド
const pushMessage = (fcmToken, text) => ({
notification: {
title: '新しいオファーを受信しました。',
body: `${text}`,
},
apns: {
headers: {
'apns-priority': '10'
},
payload: {
aps: {
badge: 9999,
sound: 'default'
}
}
},
data: {
data: 'test',
},
token: fcmToken
});
///関数
exports.mySendMessages = functions.region('asia-northeast1')
.runWith({ memory: '512MB' })
.pubsub.schedule('every 3 minutes')//関数を実行する時間間隔が設定できる
.timeZone('Asia/Tokyo')
.onRun(async (context) => {
// 秒を切り捨てた現在時刻
const now = (() => {
let s = admin.firestore.Timestamp.now().seconds
s = s - s % 60
return new admin.firestore.Timestamp(s, 0)
})()
//秒を切り捨てた昨日の時刻(動作確認のため広めに設定)
const yesterday = (() => {
let s = admin.firestore.Timestamp.now().seconds
s = s - 86400
return new admin.firestore.Timestamp(s, 0)
})()
//秒を切り捨てた明日の時刻
const tomorrow = (() => {
let s = admin.firestore.Timestamp.now().seconds
s = s + 43200
return new admin.firestore.Timestamp(s, 0)
})()
///時刻の確認
console.log('now', now.toDate())
console.log('yesterday', yesterday.toDate())
console.log('tomorrow', tomorrow.toDate())
//try(うまくデータが取れた!)
const pushDataRef = firestore.collection('pushData');
const snapshot = await pushDataRef.where('postAt', '>=', yesterday).where('postAt', '<=', tomorrow).get();
if (snapshot.empty) {
console.log('No matching documents.');
return;
}
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
console.log(doc.data()['postAt']);
//通知送信
const token = doc.data()['fcmToken']
const title = doc.data()['title']
admin.messaging().send(pushMessage(token, title))
});
})//.onRun
Discussion