📝

Firebase FunctionsのPythonコードをFlutterから呼び出すためのメモ

2024/04/19に公開

Firebase FunctionsをPythonで書いて、Flutterアプリから呼び出す方法についてまとめました。

Functionsの作成

1. Firebaseプロジェクトを初期化

firebase init 

2. 言語の選択肢

使用する言語を聞かれるので、Pythonを選択します。

? What language would you like to use to write Cloud Functions? 
  JavaScript 
  TypeScript 
❯ Python

Pythonを選ぶと、Firebaseが自動的にPython用のファイルを生成してくれます。

3. main.pyを編集

まずはデフォルトで生成されるmain.pyのコメントを外して、以下のように編集します。

from firebase_functions import https_fn
from firebase_admin import initialize_app

initialize_app()

@https_fn.on_request()
def on_request_example(req: https_fn.Request) -> https_fn.Response:
    return https_fn.Response("Hello world!")

4. Functionsのデプロイ

firebase deploy --only functions

onCallを使用したFunctionsの作成

クライアントからFirebase Authenticationで認証されたユーザーのみがCloud Functionsを呼び出せるようにしたいため、onCallを使います。

from firebase_admin import initialize_app
from firebase_functions import https_fn
from typing import Any

initialize_app()

@https_fn.on_call()
def on_call_example(req: https_fn.Request) -> Any:
    # on_callを使用する場合はJSON形式で返す必要がある
    return {"response":"hello"}

特定の関数のみをデプロイしたい場合は、以下のようにします。

firebase deploy --only functions:on_request_example

FlutterからFunctionsを呼び出す

FlutterからFunctionsを呼び出すために以下のパッケージを使います。

  • firebase_auth
  • cloud_functions

https://pub.dev/packages/firebase_auth

https://pub.dev/packages/cloud_functions

2. FlutterからFunctionsを呼び出す

import 'package:cloud_functions/cloud_functions.dart';
import 'package:firebase_auth/firebase_auth.dart';

Future<void> callTestFunction() async {
    // 匿名認証
    await FirebaseAuth.instance.signInAnonymously();
    try {
      // Firebase Functionsを呼び出す
      final result = await FirebaseFunctions.instance.httpsCallable('on_call_example').call();
      print('result ${result.data}');
    } on FirebaseFunctionsException catch (e) {
      print('Function error: ${e.code}, ${e.details}');
    }
}

リージョンを指定したFunctionsの作成とデプロイ

デフォルトでは、Functionsはus-central1にデプロイされますが、リージョンを指定することで、他のリージョンにデプロイすることができます。リージョンを変更する場合はFunctions側とFlutter側の両方でリージョンを指定する必要があります。

1. Functions側でリージョンを指定

from firebase_admin import initialize_app
from firebase_functions import https_fn, options
from typing import Any

initialize_app()
# リージョンにasia-northeast1を指定
options.set_global_options(region=options.SupportedRegion.ASIA_NORTHEAST1)

@https_fn.on_call()
def on_call_example(req: https_fn.Request) -> Any:
    return {"response":"hello"}

2. Functionsをデプロイ

先ほどと同様にデプロイすると、リージョンが指定されたFunctionsがデプロイされます。
firebaseのコンソールから確認すると、リージョンがasia-northeast1に変更されていることが確認できます。
image

3. Flutter側でリージョンを指定してFunctionsを呼び出す

リージョンを指定した場合、Flutterから呼び出す際にもリージョンを指定する必要があります。
instanceForのregion引数に先ほど指定したasia-northeast1を指定して呼び出します。

Future<void> callTestFunction() async {
    await FirebaseAuth.instance.signInAnonymously();
    try {
      // Firebase Functionsをリージョン指定して呼び出す
      final result = await FirebaseFunctions.instanceFor(region: 'asia-northeast1').httpsCallable('on_call_example').call();
      print('result ${result.data}');
    } on FirebaseFunctionsException catch (e) {
      print('Function error: ${e.code}, ${e.details}');
    }
}

Functionsエミュレーターのセットアップ

詳しい記事はこちら
https://zenn.dev/shima999ba/articles/b83c4e26f2be18

1. エミュレーターを初期化

firebase init emulators

今回は既存のプロジェクトを使用

=== Project Setup

? Please select an option: 
❯  Use an existing project 
  Create a new project 
  Add Firebase to an existing Google Cloud Platform project 
  Don't set up a default project 

2. Functions Emulatorを選択

どのFirebase emulators機能を使用するか聞かれるので、Functions Emulatorを選択した状態でエンターキーを押します

=== Emulators Setup
? Which Firebase emulators do you want to set up? Press Space to select 
emulators, then Enter to confirm your choices. (Press <space> to select, <a> to 
toggle all, <i> to invert selection, and <enter> to proceed)
 ◯ Eventarc Emulator
 ◯ Authentication Emulator
❯◉ Functions Emulator
 ◯ Firestore Emulator
 ◯ Database Emulator
 ◯ Hosting Emulator
 ◯ Pub/Sub Emulator
(Move up and down to reveal more choices)

あとの設定はデフォルトで問題ないのでエンターキーを押します

=== Emulators Setup
? Which Firebase emulators do you want to set up? Press Space to select emulators, then Enter to confirm your choices. Functions Emulator
? Which port do you want to use for the functions emulator? 5001
? Would you like to enable the Emulator UI? Yes
? Which port do you want to use for the Emulator UI (leave empty to use any available port)? 
? Would you like to download the emulators now? Yes
i  ui: downloading ui-v1.11.7.zip...

i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...

✔  Firebase initialization complete!

3. エミュレーターを起動

$ firebase emulators:start

実行すると、以下のようなメッセージが表示されます。

┌─────────────────────────────────────────────────────────────┐
│ ✔  All emulators ready! It is now safe to connect your app. │
│ i  View Emulator UI at http://127.0.0.1:4000/               │
└─────────────────────────────────────────────────────────────┘

┌───────────┬────────────────┬─────────────────────────────────┐
│ Emulator  │ Host:Port      │ View in Emulator UI             │
├───────────┼────────────────┼─────────────────────────────────┤
│ Functions │ 127.0.0.1:5001 │ http://127.0.0.1:4000/functions │
└───────────┴────────────────┴─────────────────────────────────┘
  Emulator Hub running at 127.0.0.1:4400
  Other reserved ports: 4500

Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files.

4. Pythonコードの編集

エミュレーターの確認用に、以下のようなコードを追加します。

@https_fn.on_call()
def on_call_emulator(req: https_fn.Request) -> Any:
    return {"response":"hello from emulator"}

iOS版Flutterアプリからエミュレーターを呼び出す

useFunctionsEmulatorを使用してエミュレーターを指定します。
第一引数にlocalhost、第二引数にエミュレーター起動時に表示されたポート番号を指定します。

final _functions = FirebaseFunctions.instanceFor(region: 'asia-northeast1');
Future<void> callTestFunction() async {
    _functions.useFunctionsEmulator('localhost',5001);
    await FirebaseAuth.instance.signInAnonymously();
    try {
        final result = await _functions.httpsCallable('on_call_emulator').call();
        print('result ${result.data}');
    } on FirebaseFunctionsException catch (e) {
        print('Function error: ${e.code}, ${e.details}');
    }
}

Android版Flutterアプリからエミュレーターを呼び出す

先ほどエミュレーターの実行をiOSで確認していましたが、Androidの場合は実行結果がUNAVAILABLEとなり通信がブロックされてしまいます。
これはエミュレータがHTTPSではなくHTTPで通信していて、Android版のFlutterアプリではデフォルトでHTTP通信がブロックされているためのようです。
その他めAndroid版では、以下の手順が必要です。

1. AndroidManifest.xmlでクリアテキストトラフィックを許可

<application
       android:usesCleartextTraffic="true"
       android:networkSecurityConfig="@xml/network_security_config">

2. ネットワークセキュリティ設定ファイルを作成

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain>10.0.2.2</domain>
    </domain-config>
</network-security-config>

3. キャッシュをクリア

flutter clean

以上の手順により、Android版でもエミュレーターを使用してローカル環境のFunctionsを呼び出すことができます。

Discussion