MonacaとFirebaseの連携方法をご紹介するブログの第4回目です。今回はリアルタイムデータベースを使ってみます。

先に完成品を紹介します。Firebaseの管理画面(コンソール)とアプリ側でデータの同期を行っています。アプリ側でボタンを押すと、現在時刻でデータが登録されます。そして管理画面側でデータを削除すると、それがアプリ側に反映されます。

第1回目でストレージ機能を紹介した際は、ユーザ認証をせずにデータの登録を行いましたが、あまり好ましい実装とは言えません。Firebaseでは権限をつけてデータを保存することを推奨していますので、認証を踏まえた上で実装していきたいと思います。

そこで今回は第2回で作成した認証機能のプロジェクトにコードを追記する形で作成していきます。

HTMLの変更

データの取得に際してFirebaseに接続しますので、セキュリティポリシーを変更します。具体的には connect-src *; を追加します。

<meta http-equiv="Content-Security-Policy" content="default-src * data:; connect-src *; style-src * 'unsafe-inline'; script-src * 'unsafe-inline' 'unsafe-eval'">

Vueテンプレートの変更

ログイン後のページに、[データ追加]ボタンとデータを表示するリストを追加します。

<section style="margin: 10px;">
    <button @click="add">データ追加</button>
    <button @click="logout">ログアウト</button>
</section>
<!-- データを表示するリストを追加 -->
<section style="margin: 10px;">
    <div>リスト</div>
    <ul v-for="item in times">
        <li>{{ item.time }}</li>
    </ul>
</section>

JavaScript側の実装

変数の初期化

JavaScript側ではまず最初に times という変数(配列)を初期化します。

// 初期データの設定
data: {
    user: {
        isLoggedIn: false,
        mailAddress: "",
        password: ""
    },
    times: []  // 追加
},

データの追加処理

Firebaseにデータを追加する処理はとても簡単です。
firebase.database().ref('times/') という指定でデータを保存する名称(今回はtimes)を指定します。データの保存はpushで行います。

// イベント処理
methods: {
    …省略…

    // データ追加処理
    add: function() {
        // 登録するメッセージを作成
        var d = new Date;
        var message = `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()} ${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}`

        firebase.database().ref('times/').push({
            time: message
        })
        .catch(function(error) {
            alert(error.message)
        })
    },
}

データの取得

Firebaseリアルタイムデータベースには、データの取得を行うためのvalueイベントがあります。このイベントは、初期データを取得するタイミングと、データに変更があった場合に実行されます。
(コンソールでデータを削除した場合にも呼ばれます)

ユーザの認証状態に応じて、イベントを有効にするonと無効にするoffを設定します。

この処理はVueの構築が終わったタイミング(created内)で行います。

// デプロイ完了時のイベント
created: function() {
    // ユーザのステータスが変わったら通知
    var me = this;
    firebase.auth().onAuthStateChanged(function(user) {
        me.user.isLoggedIn = (user !== null);

        var data = firebase.database().ref('times/');
        // ログイン状態であればデータ取得イベントを有効化
        if (user !== null) {
            // データの取得
            data.on('value', function(times) {
                me.times = [];
                times.forEach(function(time) {
                    me.times.push({
                        key: time.key,
                        time: time.val().time
                    })
                });
            });
        } else {
            // ログアウト状態ではイベントを無効化
            data.off('value');
        }
    });
},

全データが配列で返却されますので、変数timesを初期化して一件ずつ入れ直します。データの値はval()メソッドで取得できます。

全体のコードは以下のようになります。

var onDeviceReady = function() {
   // Firebaseの初期化
   var config = {
        apiKey: "API_KEY",
        authDomain: "AUTH_DOMAIN",
        databaseURL: "DATABASE_URL",
        projectId: "PROJECT_ID",
        storageBucket: "STORAGE_BUCKET",
        messagingSenderId: "MESSAGING_SENDER_ID"
    };
    firebase.initializeApp(config);

    // Vueの処理
    var vm = new Vue({
        el: '#app',  // マウントするDOM
        // 初期データの設定
        data: {
            user: {
                isLoggedIn: false,
                mailAddress: "",
                password: ""
            },
            times: []  // 追加
        },

        // デプロイ完了時のイベント
        created: function() {
            // ユーザのステータスが変わったら通知
            var me = this;
            firebase.auth().onAuthStateChanged(function(user) {
                me.user.isLoggedIn = (user !== null);

                var data = firebase.database().ref('times/');
                // ログイン状態であればデータ取得イベントを有効化
                if (user !== null) {
                    // データの取得
                    data.on('value', function(times) {
                        me.times = [];
                        times.forEach(function(time) {
                            me.times.push({
                                key: time.key,
                                time: time.val().time
                            })
                        });
                    });
                } else {
                    // ログアウト状態ではイベントを無効化
                    data.off('value');
                }
            });
        },

        // テンプレート
        template: `
            <div>
                <div class="center"> Firebase認証 </div>
                <section style="margin: 10px;" v-if="user.isLoggedIn">
                    <p>{{ user.mailAddress }}</p>
                    <section style="margin: 10px;">
                        <button @click="add">データ追加</button>
                        <button @click="logout">ログアウト</button>
                    </section>
                    <!-- データを表示するリストを追加 -->
                    <section style="margin: 10px;">
                        <div>リスト</div>
                        <ul v-for="item in times">
                            <li>{{ item.time }}</li>
                        </ul>
                    </section>
                </section>
                <section v-else style="margin: 10px;">
                    <p>メールアドレス</p>
                    <p>
                        <input v-model="user.mailAddress" placeholder="メールアドレス" />
                    </p>
                    <p>パスワード</p>
                    <p>
                        <input v-model="user.password" placeholder="パスワード" type="password" />
                    </p>
                    <button @click="register">新規登録</button>
                    <button @click="login">ログイン</button>
                </section>
            </div>`,

        // イベント処理
        methods: {
            // 登録処理
            register: function() {
                firebase.auth().createUserWithEmailAndPassword(this.user.mailAddress, this.user.password)
                .catch(function(error) {
                    alert(error.message);
                });
            },
            // ログイン処理
            login: function() {
                firebase.auth().signInWithEmailAndPassword(this.user.mailAddress, this.user.password)
                .catch(function(error) {
                    alert(error.message);
                });
            },
            // ログアウト処理
            logout: function() {
                firebase.auth().signOut();
            },
            // データ追加処理
            add: function() {
                // 登録するメッセージを作成
                var d = new Date;
                var message = `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()} ${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}`

                firebase.database().ref('times/').push({
                    time: message
                })
                .catch(function(error) {
                    alert(error.message)
                })
            },
        }
    });
};
document.addEventListener(window.cordova ?"deviceready" : "DOMContentLoaded", onDeviceReady, false);

Firebaseのリアルタイムデータベースではデータの追加処理と、そのイベント通知が分かれているので実装がシンプルになります。チャットや複数端末でのデータ同期などに使うのが良さそうです。

なお、通信にはWebSocketが使われています。特に気にすることはありませんが、何らかの理由によりWebSocket接続が切れてしまっているとデータが反映されないので注意してください。

今回のコードはmoongift/monaca_firebase_realtimedbにアップロードしてあります。実装時の参考にしてください。

Firebase