🚀Firebase + Flutterにおけるサーバーレス環境設定手順
背景
Flutter + functions + Firestore + FirebaseAuth + GCPの組み合わせでプロジェクトを推進したときの初期環境構築手順が意外と多く、そして今後も実施することがありそうなので自分の覚書き兼あわよくば皆さんの役に立てれば幸いと思い、以下に手順を記載します。
flavor等でエンドポイントを分離したいと思っている方は各flacorごとに下記の操作を行う必要があります。
途中少し手順端折っちゃてるところあります。すみません...
前提
Flutter : ver.3.35.1
Firebase : Blazeプラン
functions : v1

手順
1. Firebaseコンソールで新規プロジェクトを作成する

2. GCPにてBilling Accountに当該プロジェクトを紐付ける
これに関しては支払い方法に依存すると思いますが、私はGCPで請求代行会社を使用していた+FirebaseはBlazeプランでしたので、この操作が必要でした。

3. Firestoreを有効にする(始めるを押下)
4. 手順3にて本番環境を選択した場合、セキュリティルールを下記に変更する
rules_version = ‘[任意の数字]’;
service cloud.firestore {
match /databases/{database}/documents {
// authログインしていないユーザーでも読めるようにしておきたいドキュメントがある場合は以下のように設定する
//(例えばAppのバージョン情報など)
match /[collection名]/[document名]{
allow read: if true; // 公開読み取りOK
allow write: if request.auth != null; // 書き込みは認証済みのみ
}
// ログイン必須にする場合は以下のように設定
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
5. 必要なコレクションやドキュメントを作成する

6. 複合インデックスとTTLを設定する

firestoreでは二つ以上のインデックスを使ったクエリは事前に作成しておく必要があります。
例えば、usersコレクション内のuid='abc123'の人が投稿したpostsコレクション直近100件を取得するときなど。この場合は下記のように二つのインデックスを使用しているため事前に作成していないとエラーになります。
~.collection('posts')
.where('createdBy', isEqualTo 'abc123') // <-- 1つ目のインデックス
.orderBy('createdAt', descending: true) // <-- 2つ目のインデックス
.limit(100).get()
1つだけのインデックスの場合は内部的に自動で作成してくれるみたいです。
TTLを適用するドキュメントには'expiredAt'のような削除する時間をしてするためのTimestampフィールドが必要です。
7. Authenticationを有効にする
8. APNsの認証キーをアップロード
PUSH通知をFCMを活用して実装する場合はプロジェクトの設定>CloudMessagingより、
APNs認証キーをアップロードしておく。

APNs認証キーはApple Developer Consoleより取得する。
Apple Developer Consoleの操作は以下のように辿っていくとキーの追加ができる(雑ですみません)


9. GCPにてその他必要なAPIを有効にする
SecretManagerAPIやCloudTasksAPIを使っている場合はGCPにて有効化する

Cloud Tasks でreminder-task-queueという名前でキューを作成する
この名前はfunctionsで何らかのタスクをスケジューリングさせたいときにパラメータとして以下のように使用します。
import {CloudTasksClient, protos} from "@google-cloud/tasks";
export async function setTaskScheduler({
projectId = functions.config().[プロジェクト名].project_id,
location = functions.config().[プロジェクト名].location,
queue = "reminder-task-queue", <---キューの名前を指定
topic = "thread-reminder-topic", <---トピックの名前を指定
scheduleTime, // Date 型`
payload,
}: {
projectId?: string;
location?: string;
queue?: string;
topic?: string;
scheduleTime: Date;
payload: any;
}) {
const parent = cloudTaskClient.queuePath(projectId, location, queue);
const task: protos.google.cloud.tasks.v2.ITask = {
httpRequest: {
httpMethod: "POST",
url: `https://${location}-${projectId}.cloudfunctions.net/[エンドポイント名]`,
body: Buffer.from(JSON.stringify(payload)),
headers: {"Content-Type": "application/json"},
oidcToken: {
serviceAccountEmail:
`firebase-adminsdk-fbsvc@${projectId}.iam.gserviceaccount.com`,
audience: `https://${location}-${projectId}.cloudfunctions.net/[エンドポイント名]`,
},
},
scheduleTime: {
seconds: Math.floor(scheduleTime.getTime() / 1000),
},
};
const request: protos.google.cloud.tasks.v2.ICreateTaskRequest = {
parent,
task,
};
const [response] = await cloudTaskClient.createTask(request as any);
console.log(
`Task scheduled for ${scheduleTime.toISOString()}:`,
response.name);
}
10. CLIにて以下のコマンドでデプロイ先のプロジェクトを追加し、必要であればaliasを設定
firebase use --add
11. 以下でCLIのデフォルトのプロジェクトを変更
firebase use [alias名]
12. firebaseのconfigに値を設定
firebase functions:config:set \
"[project-name].api_key"="[firebaseのWebAPIキー]" \
"[project-name].project_id"="[firebaseのプロジェクトID]" \
"[project-name].location"="[functionsのロケーション]" \
"smtp.pass"="~" \
"smtp.port"="465" \
"smtp.host"="~" \
"smtp.user"="~"
13. App Engine default service accountを作成
まずはsecretManagerAPIを使っていない関数(どれでも良い)でデプロイする
手順14のようにGCPで権限付与する前にsecretManagerAPIを使用している関数をデプロイしようとすると権限エラーとなったため、初回のみ面倒ですが二段階でデプロイします。
(何か良い方法があるはずなので模索中です。。。ご存知の方はぜひ教えてください)
firebase deploy --only functions:[functionsの関数名] --project [projectId]
※--project [projectId]を入力しなくてもデフォルトの環境でデプロイ可能ですが、
誤って本番環境へのデプロイを実行しないように、私は明示的に書くようにしています。
14. GCPでの権限付与
GCPにて〜appspot.gserviceaccount.comのアカウントに
Service Account Token Creatorの権限を付与する
15. 全てデプロイする
firebase deploy --only functions --project [projectId]
以下のように気持ちよくデプロイ完了すれば完了です。

P.S.
これが記念すべき初めての投稿になります。
最近ではAIにコードを書かせてしまえばほとんど悩むことなくやりたいことを実現できてしまうとは思いますが、特にビルド周りやパッケージ・技術選定などのAIが苦手そうな部分(個人的な主観ですが)やAI活用してもなお解決せずに沼った経験談などを中心に投稿していこうと思います。
次はApple Watch専用App(watch only App)のビルド周りについて書こうと思います。
Discussion