【Flutter】Cloud Functions for Firebaseの基本

## 1. はじめに
FlutterとFirebaseは、モバイルアプリやウェブアプリの開発において強力なツールです。その中で**Cloud Functions for Firebase**は、バックエンドの処理を簡単にサーバーレスで実行できるサービスです。本記事では、Cloud Functions for Firebaseの基本的な使い方と、Flutterとの連携について解説していきます。

## 2. Cloud Functionsとは

**Cloud Functions for Firebase**は、Google Cloudが提供するサーバーレスコンピューティングサービスです。サーバーレスとは、開発者がサーバーの管理・運用を行う必要がなく、ソースコードの記述のみでバックエンドの機能を実現できる仕組みです。Cloud Functionsでは、イベントがトリガーされた際に、バックエンドの処理を自動で実行することができます。

具体的に、以下のような用途で活用できます：
- Firebase AuthenticationやCloud Firestoreのデータベース変更に応じて処理を実行
- HTTPリクエストに応じて処理を実行
- 定期的なスケジュールに従って自動で処理を実行

## 3. 料金プラン

Firebaseには、SparkプランとBlazeプランがあります。Cloud Functions for Firebaseを利用するには、Blazeプランを使用する必要があります。Blazeプランはクレジットカード登録が必要ですが、Blazeプランにしただけで料金が発生することはないので安心してください。無料枠を超えた利用をした場合にのみ、料金がかかってきます。Firebaseは無料枠が広いので、普通に使っている分には料金がかかることは少ないです。

以下は、Cloud Functions for Firebaseの無料枠に関する表です。

| 無料枠項目                     | 無料枠内容                                     |
|-------------------------------|----------------------------------------------|
| **リクエスト数**            | 200万リクエスト/月                           |
| **CPU時間**                 | 400,000 GHz-秒/月                            |
| **インバウンドデータ転送**   | 5GB/月                                      |
| **アウトバウンドデータ転送** | 5GB/月                                      |
| **並行実行**              | 最大1,000並行リクエストまで無料               |
| **スケジューリング**     | 無制限                                      |

#### 補足:
- リクエスト数やCPU時間、データ転送量に関しては、月単位で無料枠があります
- 並行実行は、並行で1,000リクエストまで無料で処理が可能です
- スケジュール実行に関しては、月内の回数に制限なく無料で実行できます。

多くの小規模なアプリケーションや開発段階のプロジェクトでは、無料枠の範囲内で運用できることが多いです。

## 4. Cloud Functionのバージョン

Cloud Functionsには、**第1世代**と**第2世代**の2つのバージョンがあります。それぞれに特徴があり、用途に応じて使い分ける必要があります。

以下は、Cloud Functions for Firebaseの第1世代と第2世代の比較表です。

| 比較項目                       | 第1世代                                           | 第2世代                                           |
|--------------------------------|------------------------------------------------|------------------------------------------------|
| **最大メモリ割当て**           | 2GB                                             | 16GB                                             |
| **最大CPU**                    | 1 vCPU                                          | 8 vCPU                                           |
| **並行処理**           | シングルリクエスト                               | 並行処理が可能（最大1000リクエストまで）           |
| **関数の起動時間（Cold Start）** | 遅い                                             | 高速                                             |
| **料金**                 | 低性能のため安価で済む可能性が高い                       | 高性能で並行処理が可能なため、高額になる可能性がある |
| **スケール上限**                | 1000 関数インスタンス                            | 100,000 関数インスタンス                           |
| **機能**         | 一通りの機能を利用可能                                        | 一通りの機能を利用可能           
| **リージョン**           | 特定リージョン                         | 多くのリージョンに対応                         |

第2世代のCloud Functionsは、高性能、並行処理、スケーリングなどが大幅に向上しており、基本的には第2世代を利用しておけば問題ないと思います。本記事では、第2世代を利用して解説していきます。

## 5. Cloud Functionsのセットアップ

Cloud Functions for Firebaseを使用するためには、Firebaseプロジェクトを作成し、Cloud Functionsのセットアップを行う必要があります。本セクションでは、セットアップの基本的な流れについて説明します。

### 1. Firebaseプロジェクトを作成
Firebase Console (https://console.firebase.google.com) にアクセスし、**「プロジェクトを作成」** を選択して、プロジェクトを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674933/3c9946d9-ec46-b631-3a60-c78837e2b29b.png)

### 2. FirebaseとFlutterのプロジェクトを連携
以下のFlutterアイコンを選択すると、FirebaseとFlutterのプロジェクトを連携するためのガイドが表示されますので、ガイドに沿って設定をしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674933/e1a55b4e-0c92-96fb-b9df-feb6022cfd5c.png)

### 3. CloudFunctionsのセットアップ
Cloud Functionsのページに移動します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674933/d5789043-9f4d-79db-7b8e-e1adbf7be6b2.png)

料金プランを、Blazeプランに設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674933/85730bd5-24db-8055-49fa-6af569c66d6f.png)

料金プラン変更後、「はじめる」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674933/6faa491f-b971-8592-d2a3-d5eee0be90c6.png)

以下のポップアップが表示されますので、Firebaseツールをインストールしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674933/d147fbbb-ff35-22e5-f3e4-d0bcdcdb48ca.png)

任意のディレクトリで以下のコマンドを実行し、次のページに進みます。

```bash
npm install -g firebase-tools
```
以下のコマンドのうち、`firebase init`のみ実行し「終了」を選択します。

![スクリーンショット 2024-10-17 21.21.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674933/97cacf89-5448-eb05-b331-c63d7c70df2a.png)

Flutterプロジェクトのルートで、以下のコマンドを実行します。

```bash
firebase init
```
上記のコマンドを実行すると、いくつか質問されるので回答していきます。

```bash
# 質問1
? Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter to confirm your choices. Functions: 
# 回答
Configure a Cloud Functions directory and its files

# 質問2
? Please select an option:
# 回答
Use an existing project

# 質問3
? Select a default Firebase project for this directory:
# 回答
your-firebase-project

# 質問4
? What language would you like to use to write Cloud Functions?
# 回答
JavaScript

# 質問5
? Do you want to use ESLint to catch probable bugs and enforce style?
# 回答
No

# 質問6
? Do you want to install dependencies with npm now?
# 回答
Yes
```
上記のコマンド実行後、`functions` フォルダが生成されます。

### 4. Hello World
関数を作成（ソースコードを記述）し、デプロイ（Firebaseにアップロード）してみます。`functions/index.js`に以下のコードを記述してみましょう。

```javascript
// Cloud Functions 第2世代（公式で推奨）
const functions = require('firebase-functions/v2');

// 外部からアクセスできる関数を定義
exports.helloWorld = functions.https.onRequest((request, response) => {
  // Firebaseのログに出力
  functions.logger.info("Hello logs!", {structuredData: true});
  // レスポンスとして値を返す
  response.send("Hello from Firebase!");
});
```

Firebaseツールで、関数をFirebaseにアップロードします。

```bash
firebase deploy --only functions
```

これで、`helloWorld` 関数がデプロイされ、指定されたURLで呼び出すことができます。

### 5. 動作確認
関数のデプロイが完了すると、Cloud Functionsのページで関数が表示されます。また、ここに表示されたURLにアクセスし、「Hello from Firebase!」というメッセージが表示されるか確認しておきましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674933/22bd4152-1d0f-cf24-0dfa-af49e3183d6d.png)

## 6. 実装

本セクションでは、Cloud Functionsを具体的にFlutterアプリと連携させる際、よく使う実装パターンについて解説します。

### 1. HTTP リクエスト経由で関数を呼び出す

HTTPリクエストを使用して、Flutterアプリや外部のサービスからCloud Functionsを呼び出すことができます。

#### Firebaseの実装：
`functions/index.js`に、HTTPトリガーを利用した関数を作成します。
`functions.https.onRequest`で関数を定義します。

```javascript
const functions = require("firebase-functions/v2");

exports.helloWorld = functions.https.onRequest((request, response) => {
  functions.logger.info("Hello logs!", {structuredData: true});
  const result = {
    title: "Hello",
    data: "World"
  };
  
  // JSON形式で応答を返す
  response.status(200).json(result);
});
```

#### Flutterから呼び出す際の実装：
FlutterからCloud Functionsを呼び出す場合、`http`パッケージを使って、HTTPリクエストを送信します。

```dart
import 'package:http/http.dart' as http;
import 'dart:convert';

Future<void> fetchData() async {
  final response = await http.get(Uri.parse('<Cloud Functions URL>'));

  if (response.statusCode == 200) {
    var data = json.decode(response.body);
    print(data); // 取得したデータを表示
  } else {
    print('Failed to load data');
  }
}
```

### 2. スケジュールに従って関数を呼び出す

定期的に実行する必要があるタスク（例：毎日バックアップを作成するなど）をCloud Functionsで自動化することができます。

#### Firebaseの実装：


##### 毎日0時に関数を実行する：
`functions/index.js`に以下の通り実装します。

```javascript
const functions = require("firebase-functions/v2");
const {onSchedule} = require("firebase-functions/v2/scheduler");

exports.time1 = onSchedule({schedule: 'every day 00:00', timeZone: 'Asia/Tokyo'}, async (event) => {
  functions.logger.info("every day 00:00", {structuredData: true});
});
```

- **3分おきに関数を実行する**：

```javascript
const functions = require("firebase-functions/v2");
const {onSchedule} = require("firebase-functions/v2/scheduler");

exports.time2 = onSchedule({schedule: 'every 3 minutes'}, async (event) => {
  functions.logger.info("every 3 minutes", {structuredData: true});
});
```

### 3. Firebase Authentication トリガー

ユーザーの認証状態（新規登録、ログイン、削除など）に応じて特定の処理を実行することができます。

##### Firebaseの実装：
ユーザが新規登録したときに、Cloud Firestoreにuid, Emailを登録する関数を実装します。

```javascript
// v1 APIを使用
const functions = require("firebase-functions/v1");
// firebase-adminはバックエンドのサーバ環境で、Firebaseの各サービス（Authentication、Firestore、Realtime Database、Cloud Messagingなど）を直接操作可能
const admin = require("firebase-admin");
admin.initializeApp();

exports.onUserCreate = functions.auth.user().onCreate((userRecord, _context) => {
  const user_id = userRecord.uid;
  const email = userRecord.email;
  console.log(user_id);
  console.log(email);

  // 必要な処理をここに記述します
  // 例: Cloud Firestoreにユーザーデータを保存するなど
  return admin.firestore().collection("users").doc(user_id).set({
    email: email,
    createdAt: admin.firestore.FieldValue.serverTimestamp(),
  });
});
```

### 4. Cloud Firestore トリガー

Firestoreのデータ変更に応じて自動的に処理を実行することができます。例えば、ドキュメントの作成、更新、削除時に動作する関数を作成可能です。

##### Firebaseの実装：
Firestoreのドキュメント作成時に動作する関数を作成します。

```javascript
const {onRequest} = require("firebase-functions/v2/https");
const {onDocumentCreated} = require("firebase-functions/v2/firestore");

exports.onCreateTrigger = onDocumentCreated("users/{docId}", (event) => {
  const snapshot = event.data;
  const data = snapshot.data();
  console.log(data);
  console.log(event.params.docId);
});
```

## 参考
Cloud FunctionsのCLIコマンドは、開発やデプロイの際に頻繁に利用します。

##### 関数のリストを表示

```
firebase functions:list

┌────────────┬─────────┬─────────┬─────────────┬────────┬──────────┐
│ Function   │ Version │ Trigger │ Location    │ Memory │ Runtime  │
├────────────┼─────────┼─────────┼─────────────┼────────┼──────────┤
│ helloWorld │ v1      │ https   │ us-central1 │ 256    │ nodejs18 │
└────────────┴─────────┴─────────┴─────────────┴────────┴──────────┘
```

##### 全ての関数をデプロイ
```
firebase deploy # or
firebase deploy --only functions
```

##### 特定の関数のみデプロイ
```
firebase deploy --only functions:func1
```

##### 関数の削除
```
firebase functions:delete func1 func2
```

## おわりに
Cloud Functions for Firebaseを活用することで、Flutterアプリケーションとバックエンド処理をスムーズに連携させることが可能です。サーバーレスのメリットを活かし、サーバーの管理や運用を気にせず、アプリケーションの開発に集中できる点が非常に魅力的です。加えて、トリガーベースの関数や定期実行タスクを簡単に実装できるため、リアルタイムな応答や自動化された処理を手軽に取り入れることができます。本記事を通じて、Cloud FunctionsとFlutterの連携方法や、基本的な実装パターンについて理解を深めていただけたなら幸いです。これからさらにアプリを進化させるための一助となれば嬉しいです。