🛩️

x分後にFirestoreにデータを追加したい

7 min read

1. はじめに

「x分後にFirestoreにデータを追加したい」

あなたならどうしますか?

Cloud FirestoreおよびCloud Functionsを使用している方なら、よく遭遇するシチュエーションだと思います。

Cloud Functionsを使用するにはBlazeプラン(有料)にアップグレードする必要があります。
詳しい料金表はこちら

Cloud Schedulerを使用する

まず思いつくのはCloud Schedulerを使用する方法です。
以下のように書けば、5分ごとに関数が実行されます。1時間ごと、24時間ごとと柔軟に時刻を設定できます。Cloud FunctionsでCloud Schedulerを使用する方法については、関数のスケジュール設定をご確認ください。

exports.scheduledFunction = functions
.pubsub.schedule('every 5 minutes')
.onRun((context) => {
  console.log('5分ごとに本関数を実行');
  return null;
});

設計

全体の設計は以下のようになります。
クライアント側からドキュメントをqueued_writesコレクションに書き込みます。Cloud Scheduler + Cloud Functionsでqueued_writesコレクションを毎分監視し、処理すべきドキュメントがあった場合、target_collectionにドキュメントを作成します。

Schedule Firestore Writesを使用して楽をする

Firebaseを使っているなら、Firebase ExtensionのSchedule Firestore Writesでx分後にFirestoreにデータを追加することができます。もちろんCloud Functionsのコードを書く必要はありません。

Firebase Extensions

Firebase Extensions とは、

特に定義されているイベントがアプリまたはプロジェクトで発生したときに、タスクを実行するコードです。Firebase Extensions - 仕組み

Firebase Extensionsを使うと、コードの調査、記述、デバッグを独自に行うことなく、アプリに拡張機能を追加できます。

設計

Firebase Extensionsのおかげで設計がすっきりしました。Schedule Firestore Writesの利用にコードを書く必要はなく、インストールするだけですので、運用も非常に楽になります。

Schedule Firestore Writesの問題点

Schedule Firestore Writesには以下の制限があると考えています。

  • 最短更新頻度が1分、1分未満の実行はできない
  • コレクションを監視するために不要なFunctionsがトリガーされ続け、サーバコストが嵩む

本番環境で実装する場合は、Cloud Tasksの検討をオススメします。
詳しい実装は、How to schedule a Cloud Function to run in the future with Cloud Tasks (to build a Firestore document TTL) をご覧ください。

2. 実装

今回は、リマインダー機能を搭載したTODOアプリを作成します。
投稿から1分後に、ダイアログでリマインドをします。
今回は実装しませんが、FCM通知を使っても良いですね。

https://zenn.dev/rafekun/articles/ef8a22f9fe90bd

環境

  • Flutter stable v2.2.3
  cloud_firestore: "^2.4.0"
  firebase_core: "^1.4.0"

事前準備

  • FlutterFire パッケージの導入が完了していること
    まだの方はこちらからどうぞ。
  • Firestoreプロジェクトを作成済みであること

Schedule Firestore Writes

Extensionをインストール

Install on Firebase」をタップしインストールしてください。公式リポジトリはこちらです。

Schedule Firestore Writesは、試験的なバージョンです。本番環境でのご利用は十分注意してください。(Install this experimental extension)

設定

設定は以下の通りです。TODOリストですので、queued_writesコレクションのドキュメントは削除しません。

複合インデックスをデプロイ

Schedule Firestore Writesを利用するためには、以下の複合インデックスを事前にデプロイする必要があります。今回は監視対象コレクションをqueued_writesにしました。ユースケースに合わせて変更してください。Firebase CLIもしくはFirebaseダッシュボードから手動でデプロイしてください。詳しくは、「Required Setup」をご覧ください。

{
  "indexes": [
    {
    // 監視対象コレクション名
      "collectionGroup": "queued_writes",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "state", "order": "ASCENDING" },
        { "fieldPath": "deliverTime", "order": "ASCENDING" }
      ]
    },
    {
    // 監視対象コレクション名
      "collectionGroup": "queued_writes",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "state", "order": "ASCENDING" },
        { "fieldPath": "leaseExpireTime", "order": "ASCENDING" }
      ]
    }
  ]
}

Flutter

リマインダーコレクションの監視

queued_writesコレクションでドキュメントが作成された1分後、target_collectionにドキュメントが作成されます。リマインダーを表示するためには、target_collectionのドキュメント作成を監視する必要があります。
(説明のためにコードは簡略化してあります)

void initState() {
   // target_collectionでドキュメントが作成され次第、ダイアログを表示
    _reminderStream.listen((_) {
      reminderDialog();
    });
  }

TODOリストのアイテムを表示

StreamBuilderを使用し、queued_writesコレクションに格納されているTODOリストのアイテムを表示しています。
(説明のためにコードは簡略化してあります)

Scaffold(
      appBar: AppBar(
        title: Text('home'),
      ),
      floatingActionButton: _FloatingActionButton(),
      body: StreamBuilder<QuerySnapshot>(
        stream: _taskStream,
        builder: (context, snapshot) {
          return _TaskCard();
        },
      ),
    );

1分後のリマインダーをセットしたTODOアイテムを作成

投稿から1分後にリマインダーを送信できるように、リマインダーコレクション(queued_writes)にドキュメントを作成します。
(説明のためにコードは簡略化してあります)

final now = DateTime.now();
final targetTime = now.add(Duration(minutes: 1));
final data = {
     'state': "PENDING",
     'deliverTime': targetTime,
};
await FirebaseFirestore.instance.collection("queued_writes").add(data);

これで以下のように動作するはずです。TODO投稿から1分後に、リマインダーが表示されます。

タスク作成 リマインダー

3. 考察

いかがでしたでしょうか。Schedule Firestore Writesを使えば、x分後にFirestoreへ書き込みをすることができます。リマンダーの他にも、遅延プッシュ通知や、タイマーなどのユースケースにも使えると思います。Firebase Extensionsは、機能の実装にコードを書く必要がないため、開発コストを大きく抑えることができます。今後も新しいExtensionsが追加されるようなので、楽しみにしたいですね。

今回のサンプルコードは以下に置いておきます。
使用する際は、iOSならGoogleService-Info.plistを、Androidならgoogle-services.jsonを追加してください。

https://github.com/mafreud/schedule_firestore_writes_sample

4. 参考記事

https://zenn.dev/mogmet/articles/91962b5d6b5972
https://medium.com/firebase-developers/how-to-schedule-a-cloud-function-to-run-in-the-future-in-order-to-build-a-firestore-document-ttl-754f9bf3214a