【Flutter】Firebase を使って SNS っぽいアプリを作るために学んだこと

趣味の魚捌きの延長で、魚を捌くのが好きな人のための SNS アプリ、 "Sengyo" を作ってリリースしました。

__iOS__
https://apps.apple.com/us/app/id1523325680

__Android__
https://play.google.com/store/apps/details?id=com.tsuyoshichujo.sengyoproduct

Firebase を始めとした今まで雰囲気で使っていた技術の勉強や OS の新機能などの実験台として何かひとつ具体的なアプリがあると良いな、というモチベーションで作ったこのアプリですが、ひとまずアプリとして最低限形になったので、まずはこのアプリを作るために Firebase の使い方で学んだことを振り返ってみたいと思います。

なお、この記事では __どのようなページを見てどのような情報を得ながらアプリを作ったか__ という内容が中心で、「こうプログラムを書けばこう動く」というような具体的なところまでは書いていません。

「同じようなアプリを作ってみたいけど、何をどう調べてどう作り始めたら良いかイメージできない」という方の役に立てればと思って書いた記事です。

# 学んだこと

このアプリはアプリ本体を Flutter で、サーバーサイドを Firebase で作っています。 Firebase はさらに細かく見ると、

* Firestore
* Cloud Storage
* Firebase Authentication

という3つのプロダクトを利用しています。

この記事では、 Sengyo アプリでこれらの Firebase の各プロダクトを使うために何を見てどのような情報を得たのかをそれぞれまとめていきます。 

なお、Sengyo アプリのコードは GitHub に上げてあります。必要に応じて参照してみてください。（ただし「良いコード」ではない点に注意です！）

[chooyan/Sengyo | GitHub](https://github.com/chooyan-eng/Sengyo)

## Firestore

[Firestore](https://firebase.google.com/docs/firestore) は文字列や数値、日時など様々な型のデータを保存することができるデータベースです。 

Sengyo アプリでは、主にユーザーの入力した投稿データを保存するデータベースとしてこの Firestore を利用しています。

### SQL 脳から脱却する

そんな Firestore を使う上でまず知っておく必要があるのが、 Firestore は MySQL や Oracle のような RDBMS とは違い、 JSON 形式でデータを管理する __NoSQL データベース__ であるということです。

NoSQL データベースを使ってすでに何かプロダクトを作ったことがある方にとっては当たり前のことかもしれませんが、表形式の RDB と JSON 形式の NoSQL ではデータ構造の設計方法や考え方が全く異なります。自分は NoSQL でプロダクトを作る知見も経験もほぼなかったため、まずはそこから調べる必要がありました。

ということでまず見つけたのが↓の動画です。

[The Firebase Database For SQL Developers | Youtube](https://www.youtube.com/playlist?list=PLl-K7zZEsYLlP-k-RKFa7RyNPa9_wCH2s)

この動画では SQL を知っている開発者向けに NoSQL を説明しています。Firestore に限らず「NoSQL とは」を SQL と比較する形で説明してくれているので、アプリの SQLite やバックエンドの MySQL などを触ったことのある自分にとってはちょうど良い内容でした。

詳しくは動画を観ていただければと思いますが、ここでは

* RDB ではサービスの機能に依らずデータそのものに着目して正規化する一方で、 __NoSQL ではサービスが一番必要とする形式のまま__データを保存すること
* 同じデータが複数箇所に保存されることを怖がる必要はないこと
* SQL を NoSQL に置き換える例

など、 SQL 脳を脱却するための考え方を得ることができました。

動画も1つ数分で終わるライトなものなので、それほど身構えずに観られるのも良い点です。

### Firestore という製品を知る

NoSQL データベースというジャンルについての理解ができたら、次は [Firestore](https://firebase.google.com/docs/firestore) というプロダクトについて知る必要があります。

これも Firebase が公式で動画を出しているので、順番に観ていきます。

[Get to Know Cloud Firestore | Youtube](https://www.youtube.com/playlist?list=PLl-K7zZEsYLluG5MCVEzXAQ7ACZBCuZgZ)

Firestore は NoSQL の考え方をベースに、様々な機能や設計を取り入れています。例えば

* 「ドキュメント / コレクション」ベースのデータ管理
* 配列型とコレクションの使い分け
* リアルタイム アップデート
* オフライン時のデータアクセス
* セキュリティルール

などです。
上記の動画はこれらの機能をざっと把握するのにとても役に立ちました。

若干スピーカーの英語が早口なのと、所々でちょっとしたジョークが入って置いていかれることがあったものの、この内容は Firebase のドキュメントに文章としてもまとまっているため、分からない部分はそちらで補完していけばだいたい大丈夫でした。

[Cloud Firestore | Firebase](https://firebase.google.com/docs/firestore/)

### Flutter で Firestore を扱う

NoSQL の考え方と Firestore という製品がある程度できたら、あとは Flutter アプリから使ってみるだけです。

Flutter で Firestore を扱うための `cloud_firestore` プラグインが公開されているため、これを使います。

[cloud_firestore | pub.dev](https://pub.dev/packages/cloud_firestore)

単純なデータの出し入れの方法はパッケージのサンプルコードを見ればだいたい使い方は分かるようになっています。まずは自分の Google アカウントで Firebase プロジェクトを作って README の通りに動かしてみると良いでしょう。Firebase はちょっと触ってみる分には無料で使えるようになっています。

「参照型のフィールドを `setData()` したい場合には `DocumentReference` 型のインスタンスを value として渡す」など、細々としたところで README には書かれていない部分もありましたが、実際に動かしながら確認すればそれほど難しいことはなかった記憶です。

なお、Sengyo アプリでは Firestore のデータを出し入れするコードは以下のあたりです。

https://github.com/chooyan-eng/Sengyo/blob/master/lib/repository/article_repository.dart

何かの参考になれば。

## Cloud Storage

[Cloud Storage](https://firebase.google.com/docs/storage) は静的なファイルの保存場所として使える製品です。Sengyo では画像ファイルの保存に利用しています。

Cloud Storage も Flutter 向けにパッケージが公開されていますので、 Firestore と同じようにサンプルコードを見ながら動きを確認すればそれほど大きな問題はないと思います。

[firebase_storage | pub.dev](https://pub.dev/packages/firebase_storage)

Cloud Storage はフォルダを作ってファイルを保存するシンプルなもので、基本的には Firebase のドキュメントとパッケージの README を読めばある程度使えるようになっています。

Sengyo アプリでは以下のあたりのソースコードが参考になるかと思います。

https://github.com/chooyan-eng/Sengyo/blob/master/lib/repository/image_file_repository.dart

## Firebase Authentication (メールリンク認証）

[Firebase Authentication](https://firebase.google.com/docs/auth) はユーザーの認証をするための仕組みを提供してくれる製品です。これを使うことでログイン機能が簡単に、安全に実現できます。

一言に「認証」と言っても、ドキュメントを見ると分かる通りその方法は様々で、シンプルな ID/パスワード で認証する方法から電話番号、SNS認証、また匿名認証というものも用意されています。

そのため、まずはそれぞれの認証方法に必要なユーザー情報やメリット・デメリットを理解した上で、サービスの要件（想定ユーザーの使いやすさ、セキュリティ要件など）に応じて利用する方法を取捨選択していく必要があります。

Sengyo アプリでは、管理コストの問題からなるべく少ない情報のやりとりでログインを実現したいことや、まずは分かりやすく1通りだけユーザーにログイン方法を示したかったため、__メールリンク認証__ を採用しています。

この記事でもメールリンク認証について書いていきます。

### メールリンク認証とは

メールリンク認証は、ユーザーがアプリから入力した __メールアドレスに対してログイン用リンクを送信__ し、メーラーで __リンクをタップすればアプリが開いてログインが完了する__ 、という仕組みです。

ユーザーが入力されたメールアドレスの持ち主であることを根拠に本人を認証する方法で、ユーザーとしてはパスワードを入力する必要もなく手軽に安心してアカウントを作成できる、というわけです。

このあたりは、 Firebase のドキュメントを読むことである程度イメージができるかと思います。

[iOS でメールリンクを使用して Firebase 認証を行う | Firebase](https://firebase.google.com/docs/auth/ios/email-link-auth?hl=ja)
[Android でメールリンクを使用して Firebase 認証を行う | Firebase](https://firebase.google.com/docs/auth/android/email-link-auth?hl=ja)

一方で実装面では若干手順が多くなるため、ひとつひとつ何が起きているのかを整理して理解する必要があります。

### メールリンク認証の流れ

まずは、ユーザーがメールアドレスを入力したあと、裏でどのようなことが起こっているのかを理解します。

同じことは Firebase のドキュメントでも説明されていますが、実際の処理の流れに加えて Firebase コンソール上の設定方法なども織り交ぜて説明されているため、ここでは事前準備などは省いて実際のサービスで何が起きているか、に着目して流れをまとめてみます。

1. ユーザーのメールアドレスにログイン用のリンクが含まれたメールを送信する
2. ユーザーがメール内のリンクをタップして開く。
3. アプリが開かれたリンクを拾ってアプリを起動する
4. リンクに含まれるリクエストパラメータとメールアドレスをセットにして Firebase へ送信する
5. Firebase がリクエストパラメータとメールアドレスのセットが正しいことを検証する
6. 検証の結果がアプリに通知される

ここで、3の「開かれたリンクを拾ってアプリを起動する」を実現するために、もう1つ別の Firebase 製品である [Firebase Dynamic Links](https://firebase.google.com/docs/dynamic-links) を利用します。そのため、まずは Firebase Dynamic Links について学ぶ必要があります。

### Firebase Dynamic Links とは

アプリには、ユーザーが特定の URL を開こうとした時にブラウザではなくアプリを起動する仕組みがあります。これを、 Android では [ディープリンク](https://developer.android.com/training/app-links/deep-linking)、iOS では [Universal Link](https://developer.apple.com/ios/universal-links/) と呼んでいます。

この仕組みをアプリに取り入れるためには、アプリ側だけでなくサーバー側にも「その URL の保有者とアプリの開発者が確かに同一であること」を証明するための設定が必要であったり、アプリがインストールされていなかった場合の挙動を定める必要があったり、またそれらの設定を Android / iOS それぞれのプラットフォームの仕様に従って行わなければならないなど、様々な労力が発生します。

__Firebase Dynamic Links__ は、そのあたりの面倒な設定を Firebase コンソール上で一括で行ってくれるサービスです。

Firebase コンソール上の "Dynamic Links" メニューから上記のような設定項目（iOS / Android それぞれについての、リンクで起動するアプリやインストールされていない場合の挙動など）、トラッキングの有無などを設定して Dynamic Link を作成するだけなのでとても便利です。

ただしこれをメールリンク認証で使う場合の設定方法についてはメールリンク認証のドキュメント内に概要だけ書かれていたものの、具体的にどの項目をどう設定すれば良いかの記述は見つからなかったため、今回は割と雰囲気と試行錯誤で設定しました。

このあたりは追って別の記事で詳しくまとめてみたいと思います。

### アプリ の実装

メール認証と Dynamic Links の設定が Firebase コンソール上でできたら、次にアプリの方もいろいろ実装する必要があります。

このあたりも先ほどの Firebase のドキュメントを見ながら、 iOS は XCode から `Associated Domain` の設定などを追加、 Android は `AndroidManifest.xml` ファイルに `Intent-Filter` を追加して、指定したドメインのリンクが開かれたときに、ブラウザではなく自分のアプリが開かれるようにしていきます。

次に、「メールリンク認証の流れ」で書いた通り、アプリのプログラムとして

* メールを送信する処理
* Dynamic Link からアプリが開かれたことを判定する処理
* Firebase Authentication へリンクとメールアドレスを送信する処理

を実装します。

これも `firebase_auth` という Firebase Authentication 全般を Flutter で扱うプラグインが公開されているため、これを使います。

[firebase_auth | pub.dev](https://pub.dev/packages/firebase_auth)

ただし、メールリンク認証についてはサンプルコードや README では触れられていないため、 API ドキュメントやコードを読んで適切なメソッドを見つける必要があります。

[FirebaseAuth class | firebase_auth]( library]https://pub.dev/documentation/firebase_auth/latest/firebase_auth/FirebaseAuth-class.html)

結論から言うと、アドレスを指定してログイン用のメールを送るためには `sendSignInWithEmailLink()` を、 Dynamic Link でアプリが開かれたあと FirebaseAuth にリンクとメールアドレスを送って認証するためには `signInWithEmailAndLink()` メソッドを利用します。

さらに、 Dynamic Link からアプリが開かれた際の処理については、 `firebase_dynamic_links` というプラグインが別に用意されているため、こちらを使います。

[firebase_dynamic_links | pub.dev](https://pub.dev/packages/firebase_dynamic_links)

基本的な使い方や考え方は README に書いてある通りです。

注意点としては、 Dynamic Link からアプリが開かれたとき、すでに裏でアプリが実行中で初期化処理などが実行されないパターンも考慮する必要がある、ということです。この点についてはサンプルコードでも `FirebaseDynamicLinks.instance.onLink` で Dynamic Link から起動された時に呼び出されるコールバックを初回起動時に登録しておくコードが書かれていますので、参考にすると良いでしょう。

また、このサンプルコードはメールリンク認証で使うことを考慮したものではないため、うまく `signInWithEmailAndLink()` メソッドとつなげてあげることや、ログイン後の処理に自分のアプリの要件に応じてつなげてあげる必要があります。

このあたりはサンプルコードも少なく試行錯誤した上で実装しましたが、一応 Sengyo アプリとしては以下の `LoginBloc` クラスにまとめたような実装になっていますので、何かの参考になれれば嬉しいです。

https://github.com/chooyan-eng/Sengyo/blob/master/lib/bloc/login_bloc.dart

# まとめ

以上、 Sengyo アプリを作るために利用した Firestore, Cloud Storage, メールリンク認証 をそれぞれ使うために参照したものや学んだことをまとめてみました。

細かなソースコードレベルの説明や具体的な設定の手順などは最初に書いた通り省いていますので、そのうちそれぞれについては別の記事として詳しく書いてみたいと思います。特にメールリンク認証についてはそのまま参考にできる記事などが全然見つからなかったため、優先的に書きたいところです。

Firebase と Flutter は、ちょっとした個人アプリをささっと作ろうと思ったときにとても役に立つ組み合わせだと思います。

初めて触る人にとっては馴染みのないものもあるかもしれませんが（もしかしたら最近は Firebase から入るような人も多いかもしれませんが）、この記事でまとめたように参考にできる記事や動画はたくさん公開されていますので、それらをひとつずつ見ながら実際に触ってみることでとりあえずひとつのアプリとして形にできる環境が整っています。

「それっぽい SNS アプリをとりあえず作ってみる」ために、何をどれくらい知る必要があるのか、この記事を読んでイメージが伝えられれば嬉しいです。