スマホアプリ開発を加速する,Firebaseを使ってみよう

第3回 データの読み出しをマスターする

この記事を読むのに必要な時間:およそ 11 分

Valueイベント

Valueイベントは,特定のURI以下にデータが新規に追加されたり,変更があった場合に呼び出されます。

今回のサンプルデータの例で言うと,https://<YOUR-FIREBASE-APP>.firebaseio.com/messages以下に新規メッセージが追加されたり,メッセージが変更された際に処理を実行したい場合,/messagesのValueイベントに対してリスナを登録しておけば良いということになります。

AndroidクライアントでValueイベントに対応するリスナは,ValueEventListenerインタフェースの実装クラスを使って作成します。

データの取得に成功すればonDataChange(DataSnapshot snapshot)が呼ばれ,何らかの理由で失敗すればonCancelled(FirebaseError error)が呼ばれます。

さっそくコードで見ていきましょう。

リスト1 Valueイベントによるデータ取得の実装例

Firebase ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/messages");

ref.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot snapshot) {
        for (DataSnapshot dataSnapshot : snapshot.getChildren()) {
            String sender = (String) dataSnapshot.child("sender").getValue();
            String body = (String) dataSnapshot.child("body").getValue();
            Log.d("Firebase", String.format("sender:%s, body:%s", sender, body));
        }
    }

    @Override
    public void onCancelled(FirebaseError error) {
    }
});

まずはこの通りコードを書いて実行してみてください。ログに以下のように出力されれば成功です。

D/Firebase: sender:John, body:Hi, there!
D/Firebase: sender:Steve, body:What's up?
D/Firebase: sender:Bill, body:hey hey hey!

では,リスト1の各行の意味を順を追って見ていきましょう。

DataSnapshot

リスト1の5行目で,データ取得成功時にonDataChangeの引数として渡されるDataSnapshotは,文字通り「あるURIのある瞬間のデータのスナップショット」です。つまり,その瞬間Firebaseに存在するデータを意味します。

DataSnapshot自体はデータの入れ物であり,データそのものを取り出すにはgetValue()メソッドを使います。FirebaseはスキーマレスなJSONオブジェクトで任意の値を保持できるので,取り出す値はもちろんJSONオブジェクトで表現できる任意の型です。したがって利用者が適宜適切な型にキャストして利用しなければなりません。その時点で指定したURIに何のデータも存在しない場合はnullが返されます。

また,DataSnapshotが配列のようなデータ構造(コラム参照)をしている場合,getChildren()メソッドで各要素をコレクションとして取り出すことができます。

今回のサンプルデータでは,/messagesから取得したDataSnapshotに対してgetChildren()すると

{
  "body" : "Hi, there!",
  "sender" : "John"
},
{
  "body" : "What's up?",
  "sender" : "Steve"
},
{
  "body" : "hey hey hey!",
  "sender" : "Bill"
}

これらが順番に取得できます。

さらに,DataSnapshotがオブジェクトの場合は,child("キー名")メソッドでキーに対応する値が取得できます。リスト1の7行目でdataSnapshot.child("sender").getValue()senderに対応する値を取り出し,(String)で文字列型に変換して送信者名を取り出しています。8行目のbodyについても同様です。最後にログ出力して完成です。

型安全なデータの読み出し

リスト1の7,8行目でchild()メソッドを使って値を取り出した部分は,対応するエンティティクラスを用意することで型安全に取り出すことも可能です。

以下のコードを,

String sender = (String) dataSnapshot.child("sender").getValue();
String body = (String) dataSnapshot.child("body").getValue();

以下のように修正してみてください。

まず,チャット本文を表現するChatMessageというエンティティクラスを新規に定義します。

public class ChatMessage {
    public String body;
    public String sender;
}

次に,リスト1の7,8行目を以下のようにdataSnapshot.getValue(ChatMessage.class)に書き換えます。

ChatMessage chatMessage = dataSnapshot.getValue(ChatMessage.class);
String sender = chatMessage.sender;
String body = chatMessage.body;

この方がコードの見通しもよく,取り回しもしやすくなったことがわかると思います。

Valueイベントの呼び出しタイミングと注意点

Valueイベントは初回アクセス時に一度呼び出され,対応するURI以下のデータをすべて取得します。それに加え,URI以下のデータが変更される度に毎回呼ばれますが,その都度毎回URI以下の全データを取得し直します。

単純な値を取り出す場合や,データを1回だけ取得できれば良い場合はValueイベントでも問題ありませんが,チャットメッセージ一覧のようなデータ構造の場合,新着メッセージがひとつ届くだけで過去のメッセージも含めてすべてのデータ取り直すことになり,大変非効率です。そういった場合には,次にご紹介するChild Addedイベントを利用すると良いでしょう。

Child Addedイベント

Child Addedイベントは,配列のようなデータ構造に要素が追加されるごとに呼び出されます。

Valueイベントが指定したURI以下の全データを毎回取り直すのに対し,Child Addedイベントは追加された要素だけをDataSnapshotとして受け取るので,チャットメッセージ一覧のようなケースにうってつけです。

Child Addedイベントは,ChildEventListenerインタフェースのonChildAdded(DataSnapshot snapshot, String previousChildKey)をオーバーライドすることで受信することができます。

onChildAddedの第一引数のDataSnapshotは,追加された要素そのものになります。

Firebase ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/messages");

ref.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot dataSnapshot, String previousKey) {
        ChatMessage chatMessage = dataSnapshot.getValue(ChatMessage.class);
        String sender = chatMessage.sender;
        String body = chatMessage.body;
        Log.d("Firebase", String.format("onChildAdded, sender:%s, body:%s", sender, body));
    }
    ...
});

このように,DataSnapshotの中身がチャットメッセージの各要素だけになるので分かりやすく,また,新規メッセージが追加されるごとに全データを取り直したりしないので効率的です。

onChildAddedの第二引数のpreviousKeyは,その要素が全体の何番目かを知るのに利用できるキー(サンプルデータの場合は,"01", "02", "03" などの通し番号)が渡されますが,これの利用方法は後の連載で予定している実践的なテクニックの中でご紹介できればと思います。

最後に,Child Addedイベントにリスナを登録した場合,初回アクセス時に要素数分だけonChildAddedが呼ばれ,以後新規に要素が追加されるたびに呼ばれます。

著者プロフィール

白山文彦(しろやまふみひこ)

サーバサイド,インフラ,Androidなど何でもやるプログラマ。