Poiboyで使用しているFirebaseの機能紹介と事例ついでにおまけ

#はじめに
はい！こんにちは〜！
[株式会社Diverse](https://diverse-inc.co.jp/)で今年から[Poiboy](https://poiboy.jp/)のiOSエンジニアとして働かせてもらっているgiiiitaです！
アーキテクチャ(PoiboyはMVP,興味本位でMVVM)であったりAutoLayoutあったり覚えることがてんこ盛りですが、日々の新しいインプットが新鮮でエンジョイしてます！

昨日は、[@yuki-oさん](https://qiita.com/yuki-o)の[FlareをFlutterだけじゃなくてJSでもFlareった話](https://qiita.com/yuki-o/items/388362990b9d25194611)でした。
試行錯誤した結果本家にprを出す行動・・・かっこいいなぁと！
Flutterを採用した大規模なプロダクトを開発されているチームの方なので、このプロダクトがリリースされた際にはぜひFlutterノウハウお聞きしたいですね！

本日は、
[Diverse Advent Calendar 2018](https://qiita.com/advent-calendar/2018/diverse) 20日目の記事として
Poiboyチームが今年から導入した技術の一つ**Firebase**について記事にしたいと思います！
※この記事では触れてない使い方もあります

# Firebaseとは
![ダウンロード.png](https://qiita-image-store.s3.amazonaws.com/0/75593/622587e8-342d-e698-d7a1-1fd57b56d022.png)

一言で言うとGoogleが提供しているmBaaS(mobile Backend as a Service)です。

サービス開発で頻繁に使われる認証とかPush通知等汎用的な機能を比較的簡単に実装が行えたりりFireStoreといったフルマネージドなDBがあるおかげでインフラ構築、サーバーの管理、スケールを行う必要もなく開発側はアプリケーション開発に集中することができ結果リリースまでの時間を短縮させてくれるサービスだと考えています。

また、
認証機能など開発中の助けとなる機能だけではなくログ収集、収益化、A/BTestなど運用フェーズの助けとなる機能も豊富なところも注目の一つかと思っております。

ある程度の制限はございますが、無料枠でFirebaseの機能を試すことができますので興味がある方はぜひTRYしてみてください！

詳しくは、
[Firebase公式ドキュメント](https://firebase.google.com/docs/?hl=ja)
[Firebaseのgithub](https://github.com/firebase)
上二つのリンクを見てみると良さそうです。

#Poiboyで使用している機能
Poiboyチームでは今年よりFirebaseが提供するいくつかの機能を実際のプロダクトで使用しているのでその例を紹介します！
##Authentication

![1_zTdZMxbTkVdXCOoZlXLnsg.png](https://qiita-image-store.s3.amazonaws.com/0/75593/d000190c-38ce-b32f-c57f-7d6f9dd6b301.png)

**主にFacebook,Twitter,SMSの3つのログイン経路を作成するために使用しております。**

**Poiboyチームで行ってる設定例**
![スクリーンショット 2018-12-19 16.09.33（2）.png](https://qiita-image-store.s3.amazonaws.com/0/75593/70d59223-0a72-83b1-ac2e-2e53e40cbceb.png)

Poiboyのログイン画面↓↓↓
<img width="361" alt="スクリーンショット 2018-12-19 16.35.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/75593/c407a978-f9ef-5448-9a73-c36c4bad8e39.png">


LINEログインなどはAuthenticationがカバーするログイン方法として提供されてはいないですがカスタム認証トークンを用いて[LINEでログイン](https://developers-jp.googleblog.com/2016/11/authenticate-your-firebase-users-with-line-login.html)が実現できるようです。

[Authenticationを使用してTwitterログインを行う方法についてはこちら](https://qiita.com/giiiita/items/d7c367deb10650ebd14e)
[Authenticationのガイドはこちら](https://firebase.google.com/docs/auth/?hl=ja)

##Cloud Functions
![1_xRxNEQkLv2g-2SLZlmr8dg.png](https://qiita-image-store.s3.amazonaws.com/0/75593/940624e6-6120-8b05-af8f-e9ffe786217f.png)

**エラー検知の仕組みとして使用しております。**

Cloud FunctionではHTTPであったりDBにデータが入った時などいくつかのイベントをトリガーに設定し行いたい処理を書くことができます。
Poiチームでは、Analyticsのイベントをコードに仕込みそのイベントがAnalytics/Eventsに通知された際にSlackへ通知を飛ばす仕組みをとっています。


**Analytics/Eventをトリガーとするfunctionを用意**

```sample.ts
export const faceBookLoginFailedNotificationToSlack = functions.analytics.event('FaceBookLoginFailed').onLog(async (event) => {
    //analyticsEventに"FaceBookLoginFailed"が通知された時に実行したい処理内容を書く
});
```

**Analytics/Eventを通知させたい箇所に以下コードを仕込む**

```sample.swift
Analytics.logEvent("FacebookLoginFailed", parameters: nil)
```

**こんな具合でslackに通知が届く**
<img width="1038" alt="スクリーンショット 2018-12-19 16.14.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/75593/00faa17b-7aff-e157-41fe-50582d689ac9.png">

今回は通知のためにCloud Functionsを使用しましたが、
他にもFireStoreでは出来ないこと(ドキュメントに含まれる2つのフィールドに対して囲検索が出来ない等)をCloud FunctionsとAlgoliaを使用して出来ないことをカバーする方法があります。
ドキュメントが追加(onCreate),更新(onUpdate)されることをトリガーにFireStoreのデータをAlgoliaに送りアプリから検索を行いたい際にはAlogilaからデータを引くイメージです。
公式でも紹介されているので気になった方は[こちら](https://firebase.google.com/docs/firestore/solutions/search?hl=ja)

[functionsをDeployするときのコマンド集はこちら](https://qiita.com/giiiita/items/aa07a5cf5ea09ec6f33b)
[Cloud Functionsのガイドはこちら](https://firebase.google.com/docs/functions/?hl=ja)
[functions-sample](https://github.com/firebase/functions-samples)
##RemoteConfig
![images.png](https://qiita-image-store.s3.amazonaws.com/0/75593/cf6291a6-207f-b01e-e03d-b3e8bb08cba6.png)

**ABTest,特定の値を即時切り替えたい箇所で使用しております。**

**Poiboyチームで行ってる設定例**
![スクリーンショット 2018-12-19 16.07.55（2）.png](https://qiita-image-store.s3.amazonaws.com/0/75593/ff382e7a-0e94-6e4e-866c-0d8d3dbbc98e.png)

具体的に、
・ABTestでは、ABそれぞれ異なるパターンのユーザ体験を用意し数パーセントのユーザにBパターンを反映されるように仕組みます。
その結果、意図した結果であれば徐々にBパターンが反映されるユーザの範囲を広げたりしております。

特定の値をすぐに切り替えたい箇所では、
とある画面の更新が終わるまでに30秒間隔を開けたい際にこの30をRemoteConfigで設定します。
設定例では30と設定されていますが、30ではなく20にしようとなった時もアプリの更新を行う必要がなく設定画面でパラメータを20にしてあげれば即時反映されます。

RemoteConfigの良さとしてリアルタイム性がありもし仮に不具合、不都合があった場合には管理画面で値を切り替えてしまえば良いのでリスク回避も柔軟に行えます。
既存コンテンツであれば、RemoteConfigの導入箇所は考えればいくつかあるかなと思っておりまして例えば、以下のような感じです。

・緊急メンテを行いたい場合管理画面で値を切り替え即時メンテ用の画面に切り替える
・ソシャゲのイベントでイベント完走率が見込んだ数字より低ければ難易度を下げる
etc...(考えればチームにフィットした使い方があるはず！！！)

アプリの更新をせずに専用のコードさえ仕込んでおけば管理画面からポチッと切り替えられるのは強い！！！

[FirebaseでABTestを行う方法についてはこちら](https://qiita.com/giiiita/items/676e12be109d2361d23a#firebase%E3%81%AEremoteconfig--%E5%A4%96%E9%83%A8%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9repro%E7%AD%89)
[RemoteConfigのガイドはこちら](https://firebase.google.com/docs/remote-config/?hl=ja)

# Firebaseの機能を導入してみて
チームで決めた施策やテストを行う際にそれが反映、実現されるまでのスピード感があるなと感じてます。
ABTestを行うにしろ基盤作成から工数を見積もるといつテストが始まるのだろう？となりがちなところをFirebaseの機能にのっかってしまえば後はテストしたい箇所に数行コードを書くだけでテストが行えたりするので便利だと感じております。

認証とかはアプリではほぼ必須な機能ですが、Authenticationを使用することで少ない工数で実装が可能なのでアプリケーション開発に時間を回せるのもエンジニア的には嬉しいのではないでしょうか？


#おまけ

## ユーザ登録からユーザ画面までを実装してみる

### リポジトリ用意しました
[サンプルリポジトリ](https://github.com/yotsu12/FirebaseLogin)
だいたい1時間もかからないくらいでこの実装が行えたのでかなりコスパ良いかなと思ってます！
###イメージ
Login画面に表示されているボタンをタップすることでLogin処理を行います。
Login処理が完了後にプロフィール用の画像、名前を入力してDBに保存します。
プロフィール用の画像、名前が無事DBに保存されればユーザ画面を表示します。
<img width="660" alt="スクリーンショット 2018-12-18 17.38.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/75593/1275b761-39a8-672a-7cf3-bec893854898.png">

### 使用するFirebaseのサービス

**Authentication** <- 認証に匿名ログインを使用
**FireStore** <- UserDataのread/write
**CloudStorage** <- Userのプロフィール画像の置き場所


### 実装箇所でポイントだと思う箇所をピックアップ
サンプルリポジトリに含まれるコードでFirebaseを使って機能実装している箇所でポイントとなる箇所のみ抽出してみました！

#### ログイン済みかどうかで最初に表示するViewControllerを選択
```AppDelegate.swift
if let userId: String = Auth.auth().currentUser?.uid {
    Firebase.User.get(userId) { (user, error) in
        if let error = error { return }
        guard let user = user else { return }
        let vc: UserViewController = UserViewController.instantiate(with: .init(user: user))
        self.window?.rootViewController = vc
        self.window?.makeKeyAndVisible()

    }
} else {
    let vc: LoginViewController = LoginViewController.instantiate()
    self.window?.rootViewController = vc
    self.window?.makeKeyAndVisible()
}
```
#### UserModelの定義
Pringを使用することで、FireStoreのDocumentをオブジェクトのように扱うことが可能です。
ここでは、ユーザ登録情報として必須のname,thumbnail,必須項目が登録済みであるかのisActivatedを含むオブジェクトを作成しています。

```Firebase+User.swift
import Foundation
import Firebase
import FirebaseFirestore
import Pring

extension Firebase {
    @objcMembers
    class User: Object {
        
        dynamic var name: String?
        dynamic var thumbnail: File?
        
        // nameとthumbnailが登録されていれば利用可能
        dynamic var isActivated: Bool = false
        
    }
}
```

#### 匿名ログイン

```LoginViewController.swift
    private func registrationAnonymus() {
        Auth.auth().signInAnonymously(completion: { (authDataResult, error) in
            if let error = error { return }
            guard let authDataResult = authDataResult else { return }
            let id: String = authDataResult.user.uid
            let user: Firebase.User = Firebase.User(id: id)
            user.save { (ref, error) in
                if let error = error { return }
                print("Registration Success")
                let vc: UserRegistrationViewController = UserRegistrationViewController.instantiate(with: .init(user: user))
                
                let nvc: UINavigationController = UINavigationController(rootViewController: vc)
                
                self.present(nvc, animated: true)
                
            }
        })
    }
```

signInAnonymouslyWithCompletion: メソッドがエラーの場合は早期return,エラーがない場合は匿名ユーザーのアカウントデータをFIRAuthDataResultオブジェクトから取得しています。
`user.save { (ref, error) in }`でエラーがなければ新しくUserコレクション内に新規のドキュメントが生成されます。

```swift
Auth.auth().signInAnonymously(completion: { (authDataResult, error) in
    if let error = error { return }
    guard let authDataResult = authDataResult else { return }
    let id: String = authDataResult.user.uid
    let user: Firebase.User = Firebase.User(id: id)
    user.save { (ref, error) in
        if let error = error { return }
    }
})
```

####UserDocumentの更新

Userオブジェクトのnameに値を入れて
`self.user.update`を行うとFireStoreのUserDocumentのFieldが更新されます。

```UserRegistrationViewController.swift
        self.user.name = name
        self.user.update { [weak self] (error) in
            if let error = error { return }
        }
```
#### Firebase管理画面のスクショ
**Autentication周り**

匿名ログインを有効にして、ユーザ作成が行われるとこのように表示されます。
<img width="947" alt="auth1.png" src="https://qiita-image-store.s3.amazonaws.com/0/75593/42d4d1b1-2582-4af0-d481-a20bd7f6ae7c.png">
<img width="956" alt="auth2.png" src="https://qiita-image-store.s3.amazonaws.com/0/75593/4bda9c37-9d3c-16c6-7893-478f48cb8592.png">

**Firestore**
Firestore内のデータは以下スクショのように確認出来ます。
UserCollectionの中にそれぞれのUserDocumentが作成されています。
管理画面上で検索も可能です。
<img width="1138" alt="cloudFireStore.png" src="https://qiita-image-store.s3.amazonaws.com/0/75593/b5a6a9fb-d2c8-62a1-57ff-624e4fe5da20.png">

<img width="608" alt="スクリーンショット 2018-12-20 14.42.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/75593/98629fbf-e825-9230-d70b-eb0b8e6f7fd6.png">

**Cloud Storage**

ユーザのthumbnailがCloud Storageに保存されております。
FirestoreのUserDocumentにはCloudStorageに保存されているthumbnailのDownLoadURLが格納されています。
<img width="953" alt="storage.png" src="https://qiita-image-store.s3.amazonaws.com/0/75593/8e507e1f-9c27-223f-aab7-7ffa2627a7a1.png">

#明日のアドカレ内容について

[Diverse Advent Calendar 2018](https://qiita.com/advent-calendar/2018/diverse)　21日目は[@mina_mさん](https://qiita.com/mina_m)で**デザイナーが考える！心理学を取り入れて少しでもマッチングの確率を上げる方法**についてです！

マッチングの確率を上げる方法として心理な観点からどのようなアプローチがあるのか楽しみですね！