Fire baseの認証とデータベース機能を使ったアプリケーション制作のメモ
- Fire baseとは
- セットアップ
- Vueで使う場合
- Firebaseの認証機能(Authentication)の設定
- Vue内でログイン機能を実装
- ログインユーザーの取得機能
- ログアウト機能を実装
- ログイン状態による表示の切り替え
- ログイン状態による遷移の制御
- Cloud Firestoreを利用したデータベースを作成する
- アプリケーションからCloud Firestoreへのデータの保存
- アプリケーション側からCloudFirestoreからデータを取り出す
- 保存したデータの編集
- 連絡先編集フォームに値をセットする
- 連絡先情報の更新をfirebaseに反映する
- 連絡の削除機能追加
Fire baseとは
Google が提供しているモバイルおよび Web アプリケーションのバックエンドサービス
Baas(Backend as a Service)の一種。
自分でサーバーを構築する必要がなく、Fire baseのサービスとして認証機能や、
データベースの機能を利用できる。
無料で認証やデータベースの機能を簡単に実装できる
セットアップ
※私は既にGoogleアナリティクスを利用しているのでフローが違うかも
まずはコンソール画面へアクセスする
プロジェクト作成を押下
適当なプロジェクト名を入力して、利用規約にチェックを入れて続行を押下
続行を押下
使用するアカウントを設定してプロジェクト作成を押下
続行を押下
画像の</>
をクリック
プロジェクト名をつけてアプリを登録を押下
下記スクリプトをhtmlに貼り付けると使用できるようになる
(Vueならpublic/index.htmlのbodyの閉じタグの手前に貼り付ける)
Vueで使う場合
ライブラリがあるので下記コマンドを実行しインストールする
npm install firebase
public/index.htmlのbodyの閉じタグの手前に貼り付けたスクリプトを、
切り取り、src/main.js
のVueインスタンス生成前に貼り付ける
このとき下記のスクリプトタグは削除する。
<script src="https://www.gstatic.com/firebasejs/7.14.2/firebase-app.js"></script> <script src="https://www.gstatic.com/firebasejs/7.14.2/firebase-analytics.js"></script>
また先程インストールしたライブラリをimportする
import firebase from 'firebase'
完成形は下記
import Vue from 'vue' import './plugins/vuetify' import App from './App.vue' import router from './router' import store from './store' import firebase from 'firebase' Vue.config.productionTip = false // Your web app's Firebase configuration var firebaseConfig = { apiKey: "AIzaSyCGTqL7NG3JMurGIkzQosYdtsD75n2gmNk", authDomain: "my-address-pj-c683f.firebaseapp.com", databaseURL: "https://my-address-pj-c683f.firebaseio.com", projectId: "my-address-pj-c683f", storageBucket: "my-address-pj-c683f.appspot.com", messagingSenderId: "852390100399", appId: "1:852390100399:web:2b5faa892d84b52d50835a", measurementId: "G-GVNJ2D1NLW" }; // Initialize Firebase firebase.initializeApp(firebaseConfig); firebase.analytics(); new Vue({ router, store, render: h => h(App) }).$mount('#app')
Firebaseの認証機能(Authentication)の設定
ここでの認証は Googleアカウントでログインすること
まずは、Firebaseのコンソールへ遷移
サイドメニューの「Authentication」を押下
ログイン方法を設定を押下
今回はGoogleアカウントで認証を行うのでGoogleを押下
右上の有効にするを押下し、
プロジェクトの公開名を適当に決める
プロジェクトのサポートメールは自分のメールアドレスにしておく
その後保存を押下
Googleの認証が有効になっていることを確認
承認済みドメインにlocalhostが存在していることが確認できたら設定は完了
Vue内でログイン機能を実装
ここではログインボタン押下後Googleの認証画面へ遷移し、
再度アプリケーションへ戻ってくる機能を実装する
まず、storeにログイン機能を実装する
src/store/index.js
を開く
firebaseの機能を使えるようにimportしておく。
import firebase from 'firebase'
actionsに下記メソッドを追加
GoogleAuthProviderのインスタンスを作成し、signInWithRedirectの引数にGoogleAuthProvider
のインスタンスを渡すとGoogleの認証画面にリダイレクトする。
login(){ const google_auth_provider = new firebase.auth.GoogleAuthProvider() firebase.auth().signInWithRedirect(google_auth_provider) },
認証後、再度このアプリケーションに戻ってくるので、
そのときにログインユーザーの情報を受け取ることができる
src/views/Home.vue
を下記のように変更する
ここではGoogleアカウントでログインを押下するとGoogleの認証画面へ遷移し、
認証後再度アプリケーションに戻ってくる。
<template> <v-container text-xs-center justify-center> <v-layout row wrap> <v-flex xs12> <h1>マイアドレス帳</h1> <p>マイアドレス帳をご利用の方は、Googleアカウントでログインしてください。</p> </v-flex> <v-flex xs12 mt-5> <v-btn color='info' @click="login">Googleアカウントでログイン</v-btn> </v-flex> </v-layout> </v-container> </template> <script> // @ is an alias to /src import {mapActions} from 'vuex' export default { methods:{ ...mapActions(['login']) } } </script>
ログインユーザーの取得機能
ここでは、Firebaseからユーザー情報を取得して、storeに格納する機能を実装する
まずはsrc/store/index.js
を編集
stateにlogin_user:null,
というプロパティを作成。
これにユーザー情報を格納する。
そして、mutationsにユーザー情報の格納、
actionsに発火用の記載を追記
mutations: { setLoginUser(state, user){ state.login_user = user }, ---------------------- actions: { setLoginUser({commit},user){ commit('setLoginUser', user) },
これで遷移時setLoginUserを呼び出せばログイン情報を格納できる
次にFirebaseの機能でログイン情報を取得できる機能を追加する
どこのページにいてもログイン情報を取得できるようにする場合は、
src/App.vue
にユーザー取得の処理を追加する。
App.vueは全ページ共通のコンポーネントを記載する場所なので、
created
メソッドにログイン情報の取得処理を記載する。
ログイン状態の確認はonAuthStateChanged
というメソッドを使って確認できる。
このメソッドは、引数に状態が変わったときに呼び出されるコールバック関数を指定できる。
状態が変わったときとは、ログイン時、ログアウト時のこと。
ログイン時はユーザー情報を。ログアウト時はnullが渡ってくる。
created(){ firebase.auth().onAuthStateChanged(user => { if(user){ //ログイン状態 this.setLoginUser(user) } })
完成形
<template> <v-app> <v-toolbar app> <v-toolbar-side-icon @click="toggleSideMenu"></v-toolbar-side-icon> <v-toolbar-title class="headline text-uppercase"> <span>マイアドレス帳</span> </v-toolbar-title> <v-spacer></v-spacer> </v-toolbar> <SideNav/>> <v-content> <router-view/> </v-content> </v-app> </template> <script> import firebase from 'firebase' import SideNav from './components/SideNav' import { mapActions } from 'vuex' export default { name: 'App', components:{ SideNav }, created(){ firebase.auth().onAuthStateChanged(user => { if(user){ //ログイン状態 this.setLoginUser(user) } }) }, data () { return { // } }, methods:{ ...mapActions(['toggleSideMenu', 'setLoginUser']) } } </script>
ログアウト機能を実装
現状ログアウトできないのでログアウト機能を実装する。
まず、src/store/index.js
から編集する
mutations
にメソッドを追加
deleteLoginUser(state){ state.login_user = null },
actions
にメソッドを追加
firebase.auth().signOut()
でログアウトできる。
deleteLoginUser({commit}){ commit('deleteLoginUser') }, logout(){ firebase.auth().signOut() },
次に追加した機能を使えるように'src/App.vue`を編集する
新たにログアウト用のボタンを作成し、押下されたときにログアウトするようにする
<v-tllobar-items> <v-btn @click="logout">ログアウト</v-btn> </v-tllobar-items>
ログアウト時、状態が変わった事により下記コールバック関数が呼ばれるため、
このときにuser情報の削除を行う。
created(){ firebase.auth().onAuthStateChanged(user => { if(user){ //ログイン状態 this.setLoginUser(user) } else{ //ログアウト状態 this.deleteLoginUser() } }) },
ログイン状態による表示の切り替え
src/App.vue
を編集する
ログアウトのボタンは下記条件式で消す
<v-tllobar-items v-if="$store.state.login_user">
次にsrc/store/index.js
を編集
ここではログイン時、ユーザー名と画像を取得できるようにするgettersを定義する。
getters
が呼ばれた場合、stateが引数に入ってくる。
戻り値には加工した値を戻している。
getters:{ userName: state => state.login_user ? state.login_user.displayName : '', photoURL: state => state.login_user ? state.login_user.photoURL:'' },
次にsrc/components/SideNav.vue
を編集
mapGetters
はmapActions
のGetter版
Actionsとは違いcomputed
に記載する点に注意
import { mapGetters } from 'vuex' export default { data () { return { items: [ { title: 'ホーム', icon:'home', link:{name:'home'}}, { title: '連絡先一覧', icon: 'list', link:{name:'addresses'}} ] } }, computed:{ ...mapGetters(['userName','photoURL']) } }
ログイン状態による遷移の制御
ここではログイン状態に応じたログイン制御を行う。
ユーザーログイン時は、連絡先一覧の画面へ、
ログアウト時は、Homeのログインボタンのあるページへ自動遷移するようにする
src/App.vue
を編集する
createdを下記のように修正
created(){ firebase.auth().onAuthStateChanged(user => { if(user){ //ログイン状態 this.setLoginUser(user) //現在homeにいる場合はアドレス帳一覧へ遷移 if (this.$router.currentRoute.name === 'home') this.$router.push({name:'addresses'}) } else{ //ログアウト状態 this.deleteLoginUser() //ログアウトしたらホームへ遷移 this.$router.push({name:'home'}) } }) },
次にログイン時サイドメニューからホームボタンを非表示にする。
<v-toolbar-side-icon v-show="$store.state.login_user" @click="toggleSideMenu"></v-toolbar-side-icon>
次にログインしている場合のみサイドメニューを表示させる処理を入れる。
src/components/SideNav.vue
を編集
(ホームの配列を削除)
data () { return { items: [ { title: '連絡先一覧', icon: 'list', link:{name:'addresses'}} ] } },
Cloud Firestoreを利用したデータベースを作成する
まず、Firebaseのコンソールを表示し作成したプロジェクトを押下 次にDatabaseを押下
CloudFirestoreのデータベースの作成を押下
テストモードで開始にチェックを入れ有効にするを押下
ロケーションは下記を選択
asia-northeast1
→東京
ルールタブをクリック
この画面で読み書き可能なルールを設定できる。
Cloud Firestoreではパスの形式でデータの保存ができる
/users/{userId}/addresses/{addressId}
みたいなパスのデータを保持できる
保持データのイメージは下記の感じ
user1:情報1 user1:情報2...etc
今回のルールで定義すると下記のようになる
service cloud.firestore { match /databases/{database}/documents { match /users/{userId}/addresses/{addressId} { allow read, update, delete: if request.auth.uid == userId; allow create: if request.auth.uid != null; } } }
データベースを使う場合下記は固定っぽい
service cloud.firestore { match /databases/{database}/documents {
3行目のmatchの部分がルールの設定対象になるデータベースのパスを表している
userId,addressIdの部分にはデータベースに保持されているuserIdとaddressIdが入る
match /users/{userId}/addresses/{addressId} {
read, update, deleteの権限を与えている文
条件としてユーザーIDが一致したときのみデータベースの更新ができる
request.auth.uid
とはリクエストしてきた認証済みのユーザーのuidのこと
allow read, update, delete: if request.auth.uid == userId;
これは認証済みのuidであれば誰でもデータを挿入できるという文
allow create: if request.auth.uid != null;
公開を押下
アプリケーションからCloud Firestoreへのデータの保存
src/store/index.js
保存先は下記の様にアドレス形式で保存先を指定する
/users/{userId}/addresses/
まずGetterでグーグルのログインIDを取得できるようにする
uid: state => state.login_user ? state.login_user.uid : null
次にActionsのアドレス追加メソッドにfirebase側へ格納処理を追加する
これはfirebaseのcollectionメソッドで、格納先のユーザーidを紐付けて、addメソッドでアドレスを追加している
また引数のcontextオブジェクトにはgettersもあるので指定している。
addAddress({ getters, commit }, address){ if(getters.uid) firebase.firestore().collection(`users/${getters.uid}/addresses`).add(address) commit('addAddress', address) }
その後アプリケーション側で次のようにテストデータを入れてみる
データベースに格納されたかどうかの確認をするため
FirebaseコンソールのDatabaseのデータタブをクリック
すると1レコード格納されていることがわかる
これをクリックしてたどっていくと最終的に先程フォームから送信したデータが入っていることがわかる
アプリケーション側からCloudFirestoreからデータを取り出す
現状データベースからデータの取得を行っていないので、リロードしたときに
アドレス一覧から入力した値が消えてしまう。
これを防ぐために画面表示時データベースから保存したデータを取り出す処理を入れる
src/store/index.js
を開く
firestoreからデータを取ってくるアクションを追加する
fetchAddresses({getters, commit}){ firebase.firestore().collection(`users/${getters.uid}/address`).get().then( snapshot => { snapshot.forEach( doc => commit('addAddress', doc.data())) } ) },
これはfirebase.firestore().collection(
users/${getters.uid}/address)
でどのパラメーターからデータを取得するかを設定している
その後get
メソッドで非同期にデータの取得が行われ、then
メソッドで結果のデータを受け取っている。
snapshot
にDBに保存されているユーザーIDが一致しているデータが入っている
そしてcommitでアプリケーション側のアドレスの配列に追加していっている。
src/App.vue
を編集
this.fetchAddresses()
をcreatedに追加する。
すると、リロード時にも保存したデータが表示される
保存したデータの編集
保存したデータを編集するためにはデータのユニークなIDが必要になるので取得する処理を書いていく
src/store/index.js
を開く
addAddress({ getters, commit }, address){ if(getters.uid) firebase.firestore().collection(`users/${getters.uid}/addresses`).add(address) commit('addAddress', address) }
この部分を下記のように修正
addAddress({ getters, commit }, address){ if(getters.uid){ firebase.firestore().collection(`users/${getters.uid}/addresses`).add(address).then( doc => { //doc.idがユニークID commit('addAddress',{id: doc.id, address}) } ) } }
.add
メソッドの戻り値にユニークのIDが含まれているオブジェクトが入っている。
これに伴いmutations
のaddAddress
も変更する
変更前
addAddress(state, address){ state.addresses.push(address) }
変更後
addAddress(state, {id, address}){ address.id = id state.addresses.push(address) }
これでaddressesの配列にもユニークなIDも含める事ができるようになった
次にactionsのfetchAddresses
でもidを取得できる用に変更する
変更前
fetchAddresses({getters, commit}){ firebase.firestore().collection(`users/${getters.uid}/addresses`).get().then( snapshot => { snapshot.forEach( doc => commit('addAddress', doc.data())) } ) },
変更後
fetchAddresses({getters, commit}){ firebase.firestore().collection(`users/${getters.uid}/addresses`).get().then( snapshot => { snapshot.forEach( doc => commit('addAddress', {id:doc.id, address:doc.data() })) } ) },
get()
で呼び出した場合は、idプロパティに格納されている
次に編集画面へ遷移できるようにリンクを追加する
src/views/Addresses.vue
操作の列には各連絡先を編集するためのボタンを配置するので、ソート機能を塞いでいる
headers: [ { text: '名前', value: 'name' }, { text: '電話番号', value: 'tel' }, { text: 'メールアドレス', value: 'email' }, { text: '住所', value: 'address' }, { text: '操作', sortable: false} ],
HTML側も変更する
<v-data-table :headers='headers' :items='addresses'> <template v-slot:items="props"> <td class="text-xs-left">{{ props.item.name }}</td> <td class="text-xs-left">{{ props.item.tel }}</td> <td class="text-xs-left">{{ props.item.email }}</td> <td class="text-xs-left">{{ props.item.address }}</td> <td class="text-xs-left"> <span> <router-link :to="{name :'address_edit', params:{address_id: props.item.id}}"> <v-icon small class="mr-2">edit</v-icon> </router-link> </span> </td> </template> </v-data-table> ------------------------------------------ </script> <style scoped lang="scss"> a{ text-decoration:none; } </style>
連絡先編集フォームに値をセットする
現状編集すると中身が入っていない状態で表示されるので、
編集の場合はデータがセットされている状態で表示するようにする。
src/store/index.js
を編集する
対象のIDからデータを取得できるようにgetterメソッドを追加
getAddressById: state => id => state.addresses.find( address => address.id === id )
これはidにマッチしたアドレスデータを戻り値とする関数を定義している。
次にsrc/views/AddressForm.vue
を編集する
今回は連絡先情報を非同期にfirestoreから取得しているので、
直接このアドレスに遷移した場合は、データの取得ができていない場合がある。
そのため取得できた場合のみデータを反映、それ以外は連絡先一覧を表示する作りにする
created(){ if(!this.$route.params.address_id) return const address = this.$store.getters.getAddressById(this.$route.params.address_id) if(address){ this.address = address } else{ this.$router.push({ name:'addresses'}) } },
if(!this.$route.params.address_id) return
ここではrouteのパラメーターにaddress_idが含まれているかをチェックしている。
連絡先情報の更新をfirebaseに反映する
src/store/index.js
を編集する
まずactionsメソッドを更新する。
updateAddress ({ getters, commit}, {id, address}){ if(getters.uid){ firebase.firestore().collection(`users/${getters.uid}/addresses`).doc(id).update(address).then( () => { commit('updateAddress', { id, address }) } ) } }
doc(id).update(address)
の部分は、idを取得してからアドレスを更新という流れで更新している。
次にmutations
を更新する
updateAddress(state, {id, address}){ const index = state.addresses.findIndex(address => address.id === id) state.addresses[index] = address },
ここでは同じIdの配列番号を取得し、その配列に新しい文字列を追加する処理を書いている。
src/views/AddressForm.vue
を編集
methods:{ submit(){ if(this.$route.params.address_id){ this.updateAddress({id: this.$route.params.address_id, address:this.address}) } else{ this.addAddress(this.address) } //addressesへ画面遷移 this.$router.push({name:'addresses'}), this.address = {} }, ...mapActions(['addAddress', 'updateAddress']) }
ここではIdが入っていればアップデート処理、
入っていなければ新規作成処理の出し分けを行っている
連絡の削除機能追加
src/store/index.js
actionにdeleteAddressを追加する(addAddressに似ているので比較する)
deleteAddress({ getters, commit }, {id}){ if(getters.uid){ firebase.firestore().collection(`users/${getters.uid}/addresses`).doc(id).delete().then( () => { //doc.idがユニークID commit('deleteAddress',{id}) } ) } },
deleteの場合はaddressはいらないので削除対象のIDだけ引数で受け取る。
そしてそのIDを使って.doc(id).delete()
で削除
最後にcommit('deleteAddress',{id})
でstateから削除している。
次にmutations
を更新
deleteAddress(state, { id }){ const index = state.addresses.findIndex(address => address.id === id) state.addresses.splice(index, 1) }
これは同じアドレスのインデックスを算出しspliceメソッドでデータを抜き出している。
src/views/Addresses.vue
を編集する
methodsを作成
methods:{ deleteConfirm(id){ if( confirm('削除してよろしいですか?')){ this.deketeAddress({ id }) } }, ...mapActions(['deleteAddress']) }
confirm('削除してよろしいですか?')
ここでOKを押すと削除の処理が走る
deleteと書いたv-icon
を使用するとゴミ箱のアイコンが生成されるようになる
<span> <v-icon small class="mr-2" @click="deleteConfirm(props.item.id)">delete</v-icon> </span>