Firebase + Vue.jsで認証付き簡易チャットアプリ作成

github
https://github.com/ebatetsu/firebase_vue_chat

## Firebaseプロジェクト作成
https://console.firebase.google.com/?hl=ja
ここからプロジェクトを作成


## Vue.jsの開発環境構築
Vue CLIを使用するのでインストール
`$ npm install -g vue-cli`

任意のディレクトリでプロジェクトフォルダ作成
`$ vue init webpack [project_name]`

とりあえず全部EnterでOKです。

ディレクトリ移動
`$ cd [project_name]`

パッケージをインストールする
`$ npm install`

sassで書きたかったら
`$ npm install sass-loader node-sass --save-dev`

ローカルサーバー起動
`$ npm run dev`

http://localhost:8080
にアクセスして
![sample_app.png](https://qiita-image-store.s3.amazonaws.com/0/287157/511d9fb6-e6cf-2a9a-b21a-4caf8b179f79.png)
これが表示されればOKです。


## Firebaseプロジェクトとの紐付け
Firebase CLIのインストール＆ログイン
`$ npm install -g firebase-tools`
`$ firebase login`

初期化コマンド
`$ firebase init`
![スクリーンショット 2018-10-11 12.58.38.png](https://qiita-image-store.s3.amazonaws.com/0/287157/6ef8185d-0d91-2da3-f4bb-d5a53921c76a.png)
こんな画面になり、今回はDatabase、Hostingだけ使用するのでSpaceで選択してEnter

Firebaseプロジェクト選択では先ほど作成したプロジェクトを選択

基本Enterで進んでいくが、
「What do you want to use as your public directory?」
publicディレクトリを聞かれた際は、最終的にVue CLIで開発したものをビルドして`./dist`ディレクトリに吐き出すので`dist`と入力


## 一旦Firebaseにデプロイしてみる
Vueをビルドする
`$ npm run build`

Firebaseにデプロイ
`$ firebase deploy`

Firebase管理面の「Hosting」から確認できます。


## Firebaseをアプリに追加
左上の歯車マークから設定画面に遷移し、「ウェブアプリに Firebase を追加」をクリック
![sample app – 設定 – Firebase console.png](https://qiita-image-store.s3.amazonaws.com/0/287157/d8205170-d671-ab7b-60e2-997efd93a087.png)
表示されたスニペットをコピーし
`./index.html`
の`</body>`直前に貼り付ける


## Google認証を有効化
管理画面 → Authentication → ログイン方法 → googleからステータスを有効にしてメールアドレスを選択して保存
![sample app – Authentication – Firebase console.png](https://qiita-image-store.s3.amazonaws.com/0/287157/8559f18d-c753-e70b-2a8d-14db2f078a07.png)


## データベース作成
管理画面 → Database
からRealtime Databaseを選択
![sample app – Database – Firebase console.png](https://qiita-image-store.s3.amazonaws.com/0/287157/c5e3cb88-f117-2ebe-107d-1f046dd94da2.png)

セキュリティルールは設定しなおすのでどちらでも大丈夫です。

`./database.rules.json`
に以下のように記述
https://github.com/ebatetsu/firebase_vue_chat/blob/master/database.rules.json

- 読み込みは制限なし
- 書き込みは認証が必要
- 認証時のnameデータと書き込み時のnameデータは一致していないといけない
- textデータは300文字以下でないといけない

みたいな設定をしています。

詳しくは公式の[Firebase Realtime Database ルールについて](https://firebase.google.com/docs/database/security/)を見てください。


データベース設定だけデプロイ
`$ firebase deploy --only database`

管理画面から反映を確認
![sample app – Database – Firebase console (2).png](https://qiita-image-store.s3.amazonaws.com/0/287157/7e666de6-414d-6cca-b04c-0d36a6025232.png)


# 実装（ようやく。。）

## 認証部分実装
雛形となる部分を実装していきます。

`./src/App.vue`
に以下のように記述
https://github.com/ebatetsu/firebase_vue_chat/blob/master/src/App.vue

サインイン・サインアウト機能は非常に簡単に実装できます。

```js
signIn () {
  const provider = new firebase.auth.GoogleAuthProvider()
  firebase.auth().signInWithPopup(provider)
},
signOut () {
  firebase.auth().signOut()
},
```

ユーザーがログインしているかどうかでボタンのテキストやfunctionを切り替えています。

```js
onAuthStateChanged () {
  firebase.auth().onAuthStateChanged( user => {
    this.userName = user ?
      this.getUserName() : null

    this.userPic = user ?
      this.getProfilePicUrl() : null

    this.authButtonText = user ?
      'Sign-out' : 'Sign-in with Google'

    this.authFunction = user ?
      this.signOut : this.signIn

    this.isSignedIn = user ?
      true : false
  })
},
```

```vue

<button class="header__auth-button" @click="authFunction">
  {{ authButtonText }}
</button>
```

下記部分は子コンポーネントに値を渡しています。

```vue
<router-view
  :isSignedIn="isSignedIn"
  :userName="userName"
  :userPic="userPic"
>
</router-view>
```


## ルート設定
`./src/components/HelloWorld.vue`
を任意の名前に変更する
`./src/components/Chat.vue`

`./src/router/index.js`
を以下のように記述
https://github.com/ebatetsu/firebase_vue_chat/blob/master/src/router/index.js

これでTOPにアクセスすると`<router-view></router-view>`に`./src/components/Chat.vue`の内容が表示される


## チャット部分実装
`./src/App.vue`
`<router-view></router-view>`に入る部分を実装していきます。

以下のように記述
https://github.com/ebatetsu/firebase_vue_chat/blob/master/src/components/Chat.vue

データベース設定でもバリデーションを入れていましたがjs側でもログイン状態を確認しつつ値が空ではないこともチェックし、問題なければ'/messages/'配列に追加する

```js
postMessage () {
  let that = this
  if (!that.isSignedIn || !that.message) return
  firebase.database().ref('/messages/').push({
    name: that.userName,
    text: that.message,
    profilePicUrl: that.userPic
  })
  .then( data => {
    that.errorMessage = null
    that.message = null
  })
  .catch( error => {
    that.errorMessage = '正しく入力してください'
  })
},
```

データベースから値を取ってきて

```js
loadMessages () {
  firebase.database().ref('/messages/').on('value', (snapshot) => {
    if (snapshot) {
      let rootList = snapshot.val()
      let messageList = []
      Object.keys(rootList).forEach((val, key) => {
        rootList[val].id = val
        messageList.push(rootList[val])
      })
      this.messageList = messageList
    }
  })
}
```

`messageList`にプッシュ

```js
data () {
  return {
    messageList: []
  }
},
```

v-forで表示する

```vue
<li v-for="list in messageList">
  <div class="conversation__user-image"><img :src="list.profilePicUrl" alt=""></div>
  <div class="conversation__col">
    <p class="conversation__user-name">{{list.name}}</p>
    <p class="conversation__user-text">{{list.text}}</p>
  </div>
</li>
```

ちなみに
`<style scoped lang="scss">`
の`scoped`はコンポーネントごとにcssを切り分けてくれる属性です。


## 再度ビルド＆デプロイ
`$ npm run build`
`$ firebase deploy`



完成！
![スクリーンショット 2018-10-12 19.09.26.png](https://qiita-image-store.s3.amazonaws.com/0/287157/1795f6c4-c246-9c59-4c56-b62d9c9680bc.png)