flutterでfirebaseを使いチャットアプリを作る（firestore編）

この記事は GameWith Advent Calendar 2018 の24日目です
[GameWith Advent Calendar 2018 12日目](https://qiita.com/tanuki4/items/8bd01dddeb7b97f4fd72)の続きになります。
今回はfirestore編です。

## 必要なパッケージ
- [cloud_firestore](https://pub.dartlang.org/packages/cloud_firestore)

こちらを`pubspec.yaml`に追記します。

```pubspec.yaml
cloud_firestore: ^0.8.2+3
```

## firestoreとの連携と実装
[cloud_firestore](https://pub.dartlang.org/packages/cloud_firestore)のページを参考に実装していきます。

まずpackageを使用するためにimportします

```dart
import 'package:cloud_firestore/cloud_firestore.dart';
```

今回はチャットということでfiresotreの`snapshot`を使用します。
参考ページにサンプルが載っているのでそれを今回は使用します。

### 送信

```dart
Future<void> addMessage(String text) {
  return Firestore.instance.collection("chat").add({
    "message": text
  });
}
```

`Firestore.instance.collection` で使用するコレクションを指定し`add`でデータを追加します。

### 受け取り

```dart
StreamBuilder<QuerySnapshot>(
  stream: Firestore.instance.collection('chat').snapshots(),
  builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
    if (snapshot.hasError)
      return Text('Error: ${snapshot.error}');
    switch (snapshot.connectionState) {
      case ConnectionState.waiting: return new Text('Loading...');
      default:
        return ListView(
          padding: EdgeInsets.only(bottom: 80.0),
          children: snapshot.data.documents.map((DocumentSnapshot document) {
            return Text(document["message"]);
          }).toList(),
        );
    }
  }
)
```

snapshotを使用しているのでfirestoreのコレクションに値が追加されるとリアルタイムで反映されるようになりました。

snapshotについては[こちら](https://firebase.google.com/docs/firestore/query-data/listen?hl=ja)を参考に
普通にデータを取りたい場合は`get`使用します。`orderBy `や`where`もあります。

## 完成品のコード
```dart
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

void main() => runApp(MyApp());

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

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

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {

  FirebaseUser _user;

  void _setUser(FirebaseUser user) {
    setState(() {
      _user = user;
    });
  }

  final GoogleSignIn _googleSignIn = GoogleSignIn();
  final FirebaseAuth _auth = FirebaseAuth.instance;

  Future<FirebaseUser> _handleSignIn() async {
    GoogleSignInAccount googleUser = await _googleSignIn.signIn();
    GoogleSignInAuthentication googleAuth = await googleUser.authentication;
    FirebaseUser user = await _auth.signInWithGoogle(
      accessToken: googleAuth.accessToken,
      idToken: googleAuth.idToken,
    );
    print("signed in " + user.displayName);
    return user;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: _user == null ? googleAuthBtn() : chat()
    );
  }

  Widget googleAuthBtn() {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          FlatButton(
            padding: EdgeInsets.all(20.0),
            color: Colors.blue,
            onPressed: () {
              _handleSignIn()
                .then((FirebaseUser user) => _setUser(user))
                .catchError((e) => print(e));
            },
            child: Text('google認証'),
          ),
        ],
      ),
    );
  }

  Widget chat() {
    final textController = new TextEditingController();

    Future<void> addMessage(String text) {
      return Firestore.instance.collection("chat").add({
        "message": text
      });
    }

    return Stack(
      children: <Widget>[
        StreamBuilder<QuerySnapshot>(
          stream: Firestore.instance.collection('chat').snapshots(),
          builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
            if (snapshot.hasError)
              return Text('Error: ${snapshot.error}');
            switch (snapshot.connectionState) {
              case ConnectionState.waiting: return new Text('Loading...');
              default:
                return ListView(
                  padding: EdgeInsets.only(bottom: 80.0),
                  children: snapshot.data.documents.map((DocumentSnapshot document) {
                    return Text(document["message"]);
                  }).toList(),
                );
            }
          },
        ),
        Positioned(
          height: 80.0,
          left: 0.0,
          right: 0.0,
          bottom: .0,
          child: Container(
            decoration: BoxDecoration(
              color: Colors.white
            ),
            padding: EdgeInsets.only(top: 10.0, bottom: 20.0, left: 20.0, right: 20.0),
            child: Row(
              children: <Widget>[
                Expanded(
                  child: TextFormField(
                    controller: textController,
                  ),
                ),
                Expanded(
                  child: FlatButton(
                    padding: EdgeInsets.all(10.0),
                    color: Colors.blue,
                    onPressed: () {
                      addMessage(textController.text);
                      textController.text = '';
                    },
                    child: Text('送信'),
                  ),
                ),
              ],
            )
          ),
        ),
      ]
    );
  }
}
```

## 動作確認
シュミレーターを２つ起動して動かすとリアルタイムで反映されると思います。

<img width="300" alt="スクリーンショット 2018-12-23 23.53.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/318632/31c23ad4-a97d-096c-33bd-7b2486c41e79.png">

firestoreにもきちんと値が反映されています。

<img width="600" alt="スクリーンショット 2018-12-23 23.54.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/318632/1ebeac93-1876-1940-149a-59f433268f51.png">

flutterは公式のドキュメントも充実しており特に迷うことなく実装することができました。
[widget](https://flutter.io/docs/development/ui/widgets)も豊富にあり、組み合わせていくだけでそれなりのものができそうです。
簡単なアプリなど個人制作などでflutterをお試しで今後も使っていこうと思います。