Flutter で Firebase Local Emulator Suite 試してみた

# はじめに

最近、Flutter × Firebase で個人開発をやっていて、ふとこんなものを見つけたのでお試しでやってみようかなと思います :sunglasses: 

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165251/1139d4ae-3831-5735-6de8-b46ada10af7f.png" width="360"/>
参考：[Firebase Local Emulator Suite の概要](https://firebase.google.com/docs/emulator-suite?hl=ja)

## 環境

- macOS Catalina 10.15.7
- docker desktop 3.0.2
- Git 2.29.2
- Flutter 1.24.0-10.2.pre • channel beta
- Dart 2.12.0
- Android Studio 4.0
- Xcode 12.2

## 前提条件

- terminal で flutter コマンドを実行できる
- 各エディタの Flutter の設定は完了している

# 基盤を作成

## Flutter プロジェクトを作成

```zsh
# プロジェクトを作成
cd {プロジェクトを作成したい場所}
flutter create --org {パッケージ名} {プロジェクト名}

# プロジェクトをバージョン管理しておく
cd {プロジェクト名}
git init --initial-branch main

# beta 版の gitignore をローカルプロジェクトにも取り込む
echo "$(curl -fsSL https://raw.githubusercontent.com/flutter/flutter/beta/.gitignore)" >> .gitignore

# GoogleService-Info.plist をバージョン管理しないようにする
echo "**/ios/**/GoogleService-Info.plist" >> .gitignore

# 変更点を全てコミット
git add .
git commit -m "initial commit"
```

## Firebase プロジェクトを作成

Firebase Local Emulator Suite を使用する前に、Firebase プロジェクトを作成して、必要な設定をする必要があります。
個人的には、この設定も不要になれば良いのになぁ、なんて願ってます笑

### [Firebase Console](https://console.firebase.google.com/) でプロジェクトを追加

適当な名前でプロジェクトを追加します。

| 1 | 2 | 3 | 4 |
|---|---|---|---|
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165251/8ecf38ee-6fe6-f4bf-b324-bd90652097a1.png" width="360"/> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165251/c0658558-fd59-4ad6-73ae-2fa41edacf2d.png" width="360"/> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165251/3fd7de5c-bfd5-93fb-75f1-d7e264ab4a66.png" width="360"/> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165251/dc811bd1-132d-8162-693e-d2d874dc73b2.png" width="360"/> |

### 作成したプロジェクトにアプリを追加

今回は iOS シミュレータのみ動かしていくので、Android の設定は行いません。
参考：[Flutter アプリに Firebase を追加する](https://firebase.google.com/docs/flutter/setup?hl=ja&platform=iOS)

#### GoogleService-Info.plist を設定まで

| 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|
| Xcode でバンドル ID を確認して入力 | GoogleService-Info.plist をダウンロードして、Runner/Runner ディレクトリに移動 | 特に何もせず、次へをクリック | 特に何もせず、次へをクリック | コンソールに進むをクリック |
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165251/15e5c989-e612-2ce4-a324-743ab427ec1b.png" width="360"/> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165251/c639fcb5-c076-404a-08b7-d16c6e701f0c.png" width="360"/> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165251/d4dcc9ac-4fbe-be9d-d38d-fdb587a89276.png" width="360"/> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165251/0c87c27b-bf31-6fb5-a8df-343e4c977d4d.png" width="360"/> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165251/665a4afc-d907-fc21-1bf8-d4f005fff0ac.png" width="360"/> |

※ 注）手順２で Xcode で以下のような画面から移動を行わないと、正常に設定を完了できません。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165251/3913815c-94c4-c82e-498b-d9e7b1f37104.png" width="360"/>

#### FlutterFire プラグインを追加する

```yaml:pubspec.yaml
...
dependencies:
  flutter:
    sdk: flutter

+ # For Firebase
+ firebase_core: ^0.5.3
+ firebase_database: ^4.4.0
...
```

```zsh
# ライブラリを更新
cd {プロジェクトを作成した場所}
flutter pub get

# 変更点を全てコミット
git add .
git commit -m "add FlutterFire"
```

## Docker の設定

できるだけ環境依存のものを減らすために Docker を使います :whale: 

### Dockerfile を作成

```zsh
cd {プロジェクトを作成した場所}
mkdir -p docker/firebase
touch ./docker/firebase/Dockerfile
```

```dockerfile:Dockerfile
FROM ubuntu:20.04

RUN apt-get update -y

RUN apt-get install -y curl openjdk-11-jre-headless

RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - \
    && apt-get install -y nodejs

RUN npm install -g firebase-tools
```

```zsh
# 変更点を全てコミット
cd {プロジェクトを作成した場所}
git add .
git commit -m "add Dockerfile"
```

### docker-compose.yaml を作成

```zsh
cd {プロジェクトを作成した場所}/docker
touch docker-compose.yaml
```

```yaml:docker-compose.yaml
version: "3"

services:
    firebase:
        build:
            context: ./firebase
        volumes:
            - ./firebase/emulators/:/opt/workspace:cached
            - ./firebase/bin/:/root/.cache:cached
            - ./firebase/config/:/root/.config:cached
        ports:
            - 4000:4000 # Emulator Suite UI
            - 9000:9000 # Realtime Database
        working_dir: /opt/workspace
        command: bash
        tty: true
```
```zsh
# 変更点を全てコミット
cd {プロジェクトを作成した場所}
git add .
git commit -m "add docker-compose.yaml"
```

## Firebase CLI の設定

```zsh
# コンテナを起動
cd {プロジェクトを作成した場所}/docker
docker-compose up -d

# コンテナ内に入る
docker-compose exec firebase bash
```

```bash
# コマンドを叩いた後、ブラウザでどのユーザーでログインするか選択する
# ログイン後、キーを terminal にキーを貼り付ける
firebase login --no-localhost

# コマンドを叩いた後、いろいろと聞かれるので、１つ１つ確認しながら進めていく
firebase init
```

## Local Emulator Suite を起動

`{プロジェクトを作成した場所}/docker/firebase/emulators/firebase.json` を以下のように編集します。

```json:firebase.json
{
  "rulesFile": "database.rules.json",
  "emulators": {
    "database": {
      "host": "0.0.0.0",
      "port": 9000
    },
    "ui": {
      "enabled": true,
      "host": "0.0.0.0",
      "port": 4000
    }
  }
}
```

```zsh
# 変更点を全てコミット
cd {プロジェクトを作成した場所}
git add .
git commit -m "complete firebase init"
```

そして、コンテナの中から Local Emulator Suite を起動！

```bash
firebase emulators:start
```

特にポート番号をカスタマイズしていなければ、以下のように表示できるかと思います :thumbsup: 

| http://localhost:4000 | http://localhost:4000/database |
|---|---|
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165251/4a5742c8-ca88-cc1c-0403-f3a9628436bd.png" width="360"/> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165251/af97545d-6771-5310-f081-7acc499de169.png" width="360"/> |

# Flutter アプリからデータへアクセス

## データベースのアクセスルールの設定

デフォルトでは誰もデータベースの変更ができないようになっているため、`{プロジェクトを作成した場所}/docker/firebase/emulators/database.rules.json` を以下のように編集します。

```json:database.rules.json
{
  "rules": {
    ".read": true,
    ".write": true
  }
}
```

```zsh
# 変更点を全てコミット
cd {プロジェクトを作成した場所}
git add .
git commit -m "edit database.rules.json"
```

## Flutter アプリからデータを作成・取得

`{プロジェクトを作成した場所}/lib/main.dart` を以下のように編集します。

```dart:main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:firebase_database/ui/firebase_animated_list.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Local Emulator Suite Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Local Emulator Suite Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // TODO: databaseURL は自分のローカルのURLを指定する
  final _databaseReference = FirebaseDatabase(
    app: Firebase.app(),
    databaseURL: 'http://127.0.0.1:9000/?ns=fir-emulator-demo-48f2b',
  ).reference();

  void _addItem() {
    _databaseReference.push().set('add item');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: FirebaseAnimatedList(
        query: _databaseReference.orderByKey(),
        padding: const EdgeInsets.all(8.0),
        reverse: false,
        itemBuilder: (_, snapshot, animation, x) {
          return ListTile(
            title: Text(snapshot.value.toString()),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addItem,
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
```

コミットはお忘れなく

```zsh
# 変更点を全てコミット
cd {プロジェクトを作成した場所}
git add .
git commit -m "edit main.dart"
```

※ 注）`databaseURL` の値は、自分のローカルで立ち上がっているデータベースのURLを設定しないといけない。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165251/ddbfebac-7c42-4dcf-d411-17c90cbb96a5.png" width="360"/>

## Flutter アプリでお試し

めっちゃ便利！
![test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165251/a3d4ca8d-e0ea-b1fd-c317-38c75d108ae9.gif)

# おわりに

今回、Docker まで使っちゃったのでけっこう設定項目多くなっちゃいました笑
ただ、このようにしておくと、テストを本番と環境分けて行うことができるので、個人開発では CI/CD の設定もガッツリ入れていこうかなと考えてます :sunglasses: 

深夜に一気に書いたので、何か変なこと書いていたらコメントとかで教えていただけると助かります :bow: 笑

最終的なコードは [こちら](https://github.com/blendthink/firebase_emulator_demo) に置いておきます。

みなさんもぜひお試しください！