Flutter & Firestore で気軽に永続化してオラオラする方法

![スクリーンショット 2021-01-10 17.39.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/3f28bb30-81b6-5911-d143-11c80168cd43.png)


# 🧑‍💻 まえがき

## ■やること
- Flutter & Firebase Firestore にて、必要最低限の実装内容で動くものを作成

## ■作業概要
- STEP0：事前準備、参考資料
- STEP1：firebaseの設定
- STEP2：設定ファイルの配置
- STEP3：パッケージの追加
- STEP4：接続テスト
- STEP5：firestoreの準備
- STEP6：コードの実装

## ■下記の症状に効能があります
- flutter & firestore の日本語資料がない
- 公式教材やってみたけど、バージョンが古くて動かない
- もっと気軽に普段からオラオラできたら便利
- てめぇは俺を怒らせた

## ■環境
- editer：VScode
- flutter：1.22.5
- Dart：2.10.4
- xcode：12


# 📗STEP0：事前準備、参考資料
- Flutter環境構築
    - https://flutter.dev/docs/get-started/install/macos
- サンプルappの作成
    - https://flutter.dev/docs/get-started/test-drive?tab=vscode
- firebaseの繋ぎ込み
    - https://firebase.google.com/docs/flutter/setup?hl=ja
- firestore用の教材
    - https://codelabs.developers.google.com/codelabs/flutter-firebase/index.html?hl=ja#9

# 🔥STEP1：firebaseの設定
## ■appプロジェクト作る
- 先にappのプロジェクトを作成すること
- プロジェクト作成
    - `flutter create sampleApp`
- シュミレーター起動
    - `open -a Simulator`
- projectの起動

```
cd flutter_sample
flutter run
```


## ■firebaseの設定
- コンソール画面で「プロジェクトを追加」
- アナリティクスの設定は適当でok!

![スクリーンショット 2021-01-10 11.29.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/4d1c5bba-403c-24e7-6e37-86fa0d35be2b.png)



# 📃STEP2：設定ファイルの配置(iOS)
- 下記からiosとandroidの設定をそれぞれ追加

![スクリーンショット 2021-01-10 11.31.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/87363820-f184-0042-c60a-5bf909fea164.png)
- バンドルIDをxcodeで確認して入力

![スクリーンショット 2021-01-10 11.34.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/fbff3319-874c-ab07-6023-d8979b7dcd4c.png)
![スクリーンショット 2021-01-10 11.59.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/e7fb2904-f216-5bf9-eb32-f6c25b37e698.png)


- GoogleService-Info.plistをダウンロード
- plistを**"xcodeから"**pjの"Runner/Runnerディレクトリ"に追加する
- その時、表示されるダイアログでcopyにチェックが入ってることを必ずチェック（私はこれではまった）

![スクリーンショット 2021-01-10 11.39.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/4910af70-b7b6-de38-d678-2b91217bc792.png)

![スクリーンショット 2021-01-10 11.39.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/1891aa4f-1fe2-c708-8b21-39bf40cbf6af.png)

- ファイルの追加を行ったら、firebaseのios設定画面STEP3と4はスキップでok
![スクリーンショット 2021-01-10 12.00.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/d0e94e48-90fe-cadf-3934-12afcbdf8520.png)
＊もともとxcodeでの追加設定前提の内容なので、ファイル追加以降の操作は不要

# 📚STEP3：パッケージの追加

- /pubspec.yamlのdependenciesに下記を追加
- ＊最新版のバージョンはそれぞれ下記から確認
    - https://pub.dev/packages/firebase_core/install
    - https://pub.dev/packages/firebase_analytics/install
    - https://pub.dev/packages/cloud_firestore/install

```
dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^0.5.3 ←追加
  firebase_analytics: ^6.3.0 ←追加
  cloud_firestore: ^0.14.4 ←追加
```
![スクリーンショット 2021-01-10 12.36.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/b76d95e7-8e03-d828-fa95-88718f32c8c2.png)


# ✅STEP４：接続テスト

- デフォルトのmain.dartに下記のように追記

```main.dart
import 'package:flutter/material.dart';
import 'package:firebase_analytics/firebase_analytics.dart'; //←追加
import 'package:firebase_analytics/observer.dart'; //←追加

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

class MyApp extends StatelessWidget {
  static FirebaseAnalytics analytics = FirebaseAnalytics(); //←追加
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
      //↓追加
      navigatorObservers: [
        FirebaseAnalyticsObserver(analytics: analytics),
      ],
    );
  }
}
```

- `flutter run`を実行（＊runするとpluginが自動でinstallされる&podfileも更新される）
- firebase→dashboardを確認
- 下記のように「過去30分間のユーザー」に反応があれば接続成功

![スクリーンショット 2021-01-10 14.04.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/1db8788b-2738-3903-9d1f-31ba3ce5e7f9.png)

# 🗄STEP5：firestoreの準備

## ■DB作成
- コンソールからClound Firestore > データベースの作成を選択
![スクリーンショット 2021-01-10 16.39.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/13c94749-7ec1-030a-fe61-fd295c965419.png)

- テストモードを選択
![スクリーンショット 2021-01-10 17.22.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/529f63a6-ed8f-89bd-c6a8-66f18496c2be.png)

- 地域はasia-northeast1(東京)を選択
![スクリーンショット 2021-01-10 17.24.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/06e1191e-9905-cfbe-0f20-5158cc9126f5.png)

## ■サンプルデータを作成
- コレクション（sqlで言うところのテーブル名）を設定
![スクリーンショット 2021-01-10 17.28.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/37a93824-7038-4945-81e2-09cc92979adb.png)
- ドキュメント（sqlで言うところのレコード）を作成
- （IDは自動IDを選択すると自動で振ってくれます）
![スクリーンショット 2021-01-10 17.28.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/bb357087-bce3-5918-2b6e-1b1c63a40e7d.png)
- 作成後はこんな感じ
![スクリーンショット 2021-01-10 17.28.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/629f4bef-9e15-878c-3df1-0c4e6a58fda7.png)


# 🧑‍💻STEP6：コードの実装
- main.dartを下記に変更

```main.dart

import 'package:flutter/material.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_analytics/observer.dart';
import 'package:cloud_firestore/cloud_firestore.dart'; //←追加
import 'package:firebase_core/firebase_core.dart'; //←追加

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  static FirebaseAnalytics analytics = FirebaseAnalytics();
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(title: 'やれやれだぜ'),
      navigatorObservers: [
        FirebaseAnalyticsObserver(analytics: analytics),
      ],
    );
  }
}

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

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: _buildBody(),
    );
  }

  Widget _buildBody() {
    final Future<FirebaseApp> _initialization = Firebase.initializeApp();
    return FutureBuilder(
      future: _initialization,
      builder: (BuildContext context, AsyncSnapshot snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          return _streamBuilder();
        }
        return LinearProgressIndicator();
      },
    );
  }

  Widget _streamBuilder() {
    return StreamBuilder<QuerySnapshot>(
      stream: FirebaseFirestore.instance.collection('rushes').snapshots(),
      builder: (BuildContext context, AsyncSnapshot snapshot) {
        if (!snapshot.hasData) {
          return LinearProgressIndicator();
        }
        return _buildList(snapshot.data.docs);
      },
    );
  }

  Widget _buildList(List<DocumentSnapshot> snapshot) {
    return ListView(
      padding: const EdgeInsets.only(top: 20.0),
      children: snapshot.map((data) {
        return _buildListItem(data);
      }).toList(),
    );
  }

  Widget _buildListItem(DocumentSnapshot data) {
    final rushes = Rushes(data);

    return Padding(
      padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
      child: Container(
        decoration: BoxDecoration(
          border: Border.all(color: Colors.grey),
          borderRadius: BorderRadius.circular(6.0),
        ),
        child: ListTile(
            title: Text(rushes.makeOraOra()),
            trailing: FlatButton(
              onPressed: () {
                rushes.reference.update({'times': 0});
              },
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Icon(
                    Icons.delete,
                  ),
                  Text(rushes.times.toString()),
                ],
              ),
            ),
            onTap: () {
              rushes.reference.update({'times': FieldValue.increment(1)});
            }),
      ),
    );
  }
}

class Rushes {
  String word;
  int times;
  DocumentReference reference;

  Rushes(DocumentSnapshot snapshot) {
    var map = snapshot.data();
    this.word = map['word'];
    this.times = map['times'];
    this.reference = snapshot.reference;
  }

  String makeOraOra() {
    var ret = "";
    for (var i = 0; i < times; i++) {
      ret += word;
    }
    return ret;
  }
}

```


# 🎊🤸‍🎉Congratulations!🎉🤸‍🎊
完成したら、いいね！&コメントに「ワッフルワッフル」と叫んでください😆✨
![スクリーンショット 2021-01-10 17.39.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/3f28bb30-81b6-5911-d143-11c80168cd43.png)

# 📱サンプルはこちら📱
💾 https://github.com/TD3P/flutter_rushMaker

# 🌟ご指摘大歓迎🌟
ここちゃうで！！っていうとこあったら、教えてもらえると嬉しいです！！

# シリーズ情報
📱Flutterで基本のTODOアプリを作ってみよう
https://qiita.com/pe-ta/items/b3b7458059c1fd7efcf0

📱Flutterでページ遷移するTODOアプリを作ってみよう
https://qiita.com/pe-ta/items/e547c4cf460319f5093c

📱Flutter x Firestore で気軽に永続化してオラオラする方法
https://qiita.com/pe-ta/items/ccd49fc396b063a821af