Flutter & Firebase プロジェクト作成からエミュレータ環境構築まで

Flutter & Firebase プロジェクト作成からエミュレータ環境構築まで

March 11, 2022

1. Flutter プロジェクトの作成 #

flutter create -i swift -a kotlin my_flutter_app

その後、Android エミュレータと iOS シミュレータを立ち上げてカウンターアプリが表示されることを確認する。

flutter run

2. Firebase プロジェクトの作成 #

Firebase のコンソールから新規プロジェクトを作成する。(詳細省略)

3. Android 関連の設定 #

以下の記事および記事冒頭に貼られている Youtube 動画を参考に実施していく。

3-1. アプリを Firebase に登録する #

記事中の「ステップ 2: アプリを Firebase に登録する」を実施する。

補足:動画で説明されていた追加事項 #

android/app/build.gradleminSdkVersion21 に更新する。

// 更新前
minSdkVersion flutter.minSdkVersion

// 更新後
minSdkVersion 21

3-2. Firebase 構成ファイルを追加する #

記事中の「ステップ 3: Firebase 構成ファイルを追加する」を実施する。

補足:android/app/google-services.json について #

Firebase 構成ファイルまたは Firebase 構成オブジェクトの内容は公開情報とみなされます。

そのため公開されても情報ではないものの、

オープンソース プロジェクトの場合、一般的に考えて、ソース管理にアプリの Firebase 構成ファイルや Firebase 構成オブジェクトを含めることはおすすめできません。ほとんどの場合、ユーザーは独自の Firebase プロジェクトを作成し、(独自の Firebase 構成ファイルまたは Firebase 構成オブジェクトを使用して)アプリが独自の Firebase リソースを参照するように設定すると想定されるからです。

との説明あり。

また動画内でも下記の説明があった。

Now, none of this is really secret. But you should really only share this file with your team. If you’re making an open source demo app to be shared publicly, you’ll probably want anybody who runs their project to generate their own version of this file to hook up to their own Firebase project. Now, in my case, I’m not sharing this out publicly. This is just for me and my team. So I could check it into my personal Git repository, if I had one.

補足:firebase-bom について #

Firebase コンソールのガイドで android/app/build.gradle に追加するよう指示される以下の dependencies 部分は対応しなくて良い。

これは Android のネイティブプロジェクトでのみ実施が必要なもので、Flutter プロジェクトの場合は追加不要のため。

dependencies {
  // Import the Firebase BoM
  implementation platform('com.google.firebase:firebase-bom:29.0.4')

  // Add the dependencies for the desired Firebase products
  // https://firebase.google.com/docs/android/setup#available-libraries
}

4. iOS 関連の設定 #

以下の記事および記事冒頭に貼られている Youtube 動画を参考に実施していく。

4-1. アプリを Firebase に登録する #

記事中の「ステップ 2: アプリを Firebase に登録する」を実施する。

補足:動画で触れられていたこと #

Android の applicationId と iOS の Bundle Identifier は同じではないことに注意。

Android はスネークケースを好むため ID が com.example.my_flutter_app のようになっていることが多いが、iOS はキャメルケースを好むため ID が com.example.myFlutterApp のようになっていることが多い。

4-2. Firebase 構成ファイルを追加する #

記事中の「ステップ 3: Firebase 構成ファイルを追加する」を実施する。

GoogleService-Info.plist は必ず Xcode から追加すること。

注: Finder や別のエディタでファイルを移動するのではなく、Xcode を使用してプロジェクトに GoogleService-Info.plist ファイルを追加してください。これにより、そのファイルは Xcode プロジェクト内でも接続されます。

GoogleService-Info.plist ファイル追加後も、Firebase コンソールでは Firebase SDK の追加や初期化コードの追加などの作業が指示されるが、こちらはすべて飛ばして良い。

5. FlutterFire プラグインの追加 #

これまで参照していた記事中の「ステップ 4: FlutterFire プラグインを追加する」およびこれまで同様の Youtube 動画を参考に実施していく。

Flutter が提供する Firebase プラグインのセットは、FlutterFire と総称されています。

FlutterFire のウェブサイトが以下。

上記のうち、Core(firebase_core)は必須でインストールし、あとは各自の必要性に応じて必要なプラグインを追加する。

Youtube 動画では Realtime Database を例として説明しているため、この記事でも Realtime Database を利用するものとする。firebase_corefirebase_database を追加する。(以下は追加例)

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^1.0.0
  firebase_database: ^9.0.0

6. iOS のバージョン指定をあげる #

(これは説明ガイドや動画にはなかった内容)

いまはプラグインをインストールしただけの状態。一度ここで flutter run してみると iOS シミュレータの方で以下のエラーが発生した。(Android の方は問題なし)

Launching lib/main.dart on iPhone 13 in debug mode...

(省略)

Specs satisfying the `firebase_database (from `.symlinks/plugins/firebase_database/ios`)` dependency were found, but they required a higher minimum deployment target.

(省略)

[!] Automatically assigning platform `iOS` with version `9.0` on target `Runner` because no platform was specified. Please specify a platform for this target in your Podfile.

上記を解消するため ios/Podfile を編集する。

[更新前]
# platform :ios, '9.0'

[更新後]
platform :ios, '10.0'

その後再び flutter run すると無事にカウンターアプリが開いた。

7. コードを編集 #

これまでの Youtube 動画を参考に作業する。(なお動画では Future Builder を用いて初期化処理を改善する工程が途中に挟まれているがここでは割愛)

まずは初期化処理を記載する。lib/main.dartmain() を以下のように更新する。

更新前

void main() {
  runApp(const MyApp());
}

更新後

import 'package:firebase_core/firebase_core.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(const MyApp());
}

次に Realtime Database にアクセスする処理を書く。lib/main.dart_MyHomePageState_incrementCounter() を更新する。

更新前

void _incrementCounter() {
  setState(() {
    _counter++;
  });
}

更新後

import 'package:firebase_database/firebase_database.dart';

void _incrementCounter() {
  DatabaseReference _ref = FirebaseDatabase.instance.ref().child('test');
  _ref.set('Hello, world.');

  setState(() {
    _counter++;
  });
}

そして、Firebase コンソールから Realtime database の有効化も忘れずに実施する。

補足 #

以下のように各プラグインごとにサンプルプログラムが公開されているので、こちらも参考にする。(以下は firebase_database のサンプルプログラム)

8. Android / iOS からアクセス #

Android エミュレータと iOS シミュレータ上で _incrementCounter() を実行してみる。すると Realtime database に以下のレコードが作成されていることが確認できる。

test: "Hello, world."

補足:Android エミュレータだけがうまくいかない場合 #

私が引っかかったケース。そもそも Android エミュレータからインターネットへの接続ができていなかったのが原因。

試しに Android エミュレータ上で Google Chrome を開き、適当なサイトにアクセスできるか試してみると、アクセスに失敗する。

まずは以下の記事の「DNS 設定」の方を実施する。

その後、エミュレータ上で Google Chrome を開き再度確認する。

これでダメだったら原因はエミュレータの起動の仕方。エミュレータを起動するときに -dns-server 8.8.8.8 を指定する。

emulator.exe -avd Pixel_3_API_30 -netdelay none -netspeed full -dns-server 8.8.8.8

ここまでで Flutter と Firebase プロジェクトの作成と接続は完了。

以降では、ローカルで開発するための Firebase エミュレータ環境を作成する。

ここからの手順に関しては全体的に以下の記事が参考になる。

ただし、記事の作成日が 2020/5/22 のため、最新の状況と異なる部分もある点に留意しつつ進める。


9. Firebase CLI (firebase-tools) のインストール #

(詳細省略)

10. Firebase プロジェクト作成・エミュレータ起動 #

firebase init

その後 firebase.json を以下のように更新する

{
  "database": {
    "rules": "database.rules.json"
  },
  "emulators": {
    "database": {
      "port": 9000
    },
    "ui": {
      "enabled": true,
      "port": 4000
    }
  }
}

Realtime Database 以外も使用する際は以下を参照しつつ適宜更新。

その後以下を実行してエミュレータを起動する。

firebase emulators:start

そして http://localhost:4000/ をブラウザで開くと Firebase Emulator UI のページが表示される(はず)。

補足:Firebase Emulator の起動がタイムアウトする対応 #

私の環境の場合 firebase emulators:start すると以下のメッセージがでて起動が失敗してしまった。

Error: TIMEOUT: Port 9000 on localhost was not active within 60000ms

と言われても特に 9000 番ポートを使用しているサービスもない様子。firebase.json でポートを 9000 番以外にしてもやはりダメ。

以下の ISSUE に準じて対応する。

対応は node のバージョンを 16 にする(確かに私の node は 17 だったので恐らくこれが原因)か、firebase.json を編集する方法のどちらか。

今回は firebase.json を編集する方法をとる。以下のように更新する。

{
  "database": {
    "rules": "database.rules.json"
  },
  "emulators": {
    "database": {
      "port": 9000,
      "host": "127.0.0.1"
    },
    "ui": {
      "enabled": true,
      "port": 4000,
      "host": "127.0.0.1"
    }
  }
}

改めて firebase emulators:start すると無事に起動した。

11. コードを編集:iOS からエミュレータの Realtime Database にアクセス #

lib/main.dart_incrementCounter() を以下の通り更新する。

void _incrementCounter() {
  FirebaseDatabase.instance.useDatabaseEmulator('localhost', 9000); // 追加

  DatabaseReference _ref = FirebaseDatabase.instance.ref().child('test');
  _ref.set('Hello, world.');

  setState(() {
    _counter++;
  });
}

上記変更後、iOS シミュレータから _incrementCounter() を実行すると エミュレータ上の Realtime database にレコードが作成されている。

補足 #

うまくいかない場合、セキュリティルール database.rules.json に引っかかっている可能性があるためチェック。いまはとりあえずすべて true としておけば良い。

database.rules.json

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

12. コードを編集:Android からエミュレータの Realtime Database にアクセス #

上記のまま今度は Android エミュレータで実行すると、エミュレータにはレコードが作成されない。

理由は以下ドキュメントのコード中にもひっそりと書いてある。

https://firebase.google.com/docs/emulator-suite/connect_and_prototype?database=RTDB#connect_your_app_to_the_emulators

// 10.0.2.2 is the special IP address to connect to the 'localhost' of
// the host computer from an Android emulator.
FirebaseDatabase database = FirebaseDatabase.getInstance();
database.useEmulator("10.0.2.2", 9000);

つまり、Android エミュレータから localhost にアクセスするには 10.0.2.2 を使用する。

そのため lib/main.dart_incrementCounter() のエミュレータのホスト指定部分を以下の通り変更する。

void _incrementCounter() {
  FirebaseDatabase.instance.useDatabaseEmulator('10.0.2.2', 9000); // 'localhost' から '10.0.2.2' に変更

  DatabaseReference _ref = FirebaseDatabase.instance.ref().child('test');
  _ref.set('Hello, world.');

  setState(() {
    _counter++;
  });
}

上記変更後、Android エミュレータから _incrementCounter() を実行すると エミュレータ上の Realtime database にレコードが作成されている。

13. コードを編集:Android / iOS 両方に対応 #

ホストの指定を手動で切り替えなくて良いようにこうする。

import 'dart:io';

void _incrementCounter() {
  final host = Platform.isAndroid ? '10.0.2.2' : 'localhost';
  FirebaseDatabase.instance.useDatabaseEmulator(host, 9000);

  DatabaseReference _ref = FirebaseDatabase.instance.ref().child('test');
  _ref.set('Hello, world.');

  setState(() {
    _counter++;
  });
}

Android および iOS のどちらからもエミュレータの Realtime Database にアクセスできることを確認する。

以上 #

これで一通りの初期構築および起動確認は完了した。