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

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

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

データのクエリ

これまでの例では,データを追加した順に取得するのみでしたが,実際には

  • データを特定の条件にしたがって並び替える
  • データを特定の条件のものだけ取り出す

といった,いわゆる「クエリ」を利用することが複雑なアプリケーション開発には欠かせません。

ここではFirebaseでさまざまなクエリの発行方法をご紹介したいと思います。

Firebaseのクエリで利用できるメソッド一覧

Firebaseのクエリでは,大きく分けて「並び替え」用のメソッドと「条件付き取得」用のメソッドが用意されています。

並び替え

メソッド概要
orderByChild()子要素のキーで並び替え
orderByKey()要素のキーで並び替え
orderByValue()要素の値で並び替え
orderByPriority()要素の優先度で並び替え

条件付き取得

メソッド概要
limitToFirst()先頭からn件取得
limitToLast()後方からn件取得
startAt()条件にマッチする値以降を取得
endAt()条件にマッチする値以前を取得
equalTo()条件にマッチする値だけを取得

それぞれについて詳しく確認していきたいと思います。

サンプルデータ

さまざまなクエリを試すにあたり,サンプルデータを以下のように変更してください。

{
  "messages" : {
    "01" : {
      "body" : "Hi, there!",
      "sender" : "John",
      "timestamp" : 20160123
    },
    "02" : {
      "body" : "What's up?",
      "sender" : "Steve",
      "timestamp" : 20151004
    },
    "03" : {
      "body" : "hey hey hey!",
      "sender" : "Bill",
      "timestamp" : 20151217
    },
    "04" : {
      "body" : "ho ho ho:)",
      "sender" : "Mike",
      "timestamp" : 20160403
    },
    "05" : {
      "body" : "howdy",
      "sender" : "Clint",
      "timestamp" : 20140411
    }
  }
}

また,これに合わせてChatMessageクラスも以下のように変更してください。

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

さらに,各ユーザが受信したメールの数を管理するデータも追加します。

ルートノードに以下のようなデータを追加してください。

{
  "counts" : {
    "john" : 16,
    "Steve" : 2,
    "Bill" : 7,
    "Mike" : 25,
    "Clint" : 9
  }
}

並び替え

orderByChild()

子要素を特定のキー名でソートする場合は,orderByChild("キー名")を利用します。まずは以下のコードを参照してください。

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

Query query = ref.orderByChild("timestamp");
query.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;
        long timestamp = chatMessage.timestamp;
        Log.d("Firebase", String.format("onChildAdded, sender:%s, body:%s, timestamp:%d", sender, body, timestamp));
    }

これまでと同じように,/messagesの参照refを作りますが,そこから更にorderByChild("キー名")でソートしたいキー名を指定します。上記の例ではtimestampを指定しているのでタイムスタンプ順にソートされるはずです。

D/Firebase: onChildAdded, sender:Clint, body:howdy, timestamp:20140411
D/Firebase: onChildAdded, sender:Steve, body:What's up?, timestamp:20151004
D/Firebase: onChildAdded, sender:Bill, body:hey hey hey!, timestamp:20151217
D/Firebase: onChildAdded, sender:John, body:Hi, there!, timestamp:20160123
D/Firebase: onChildAdded, sender:Mike, body:ho ho ho:), timestamp:20160403

見事,タイムスタンプでソートされているようです。他にも手元でref.orderByChild("sender")に変えたりしていろいろ試してみてください。

orderByKey()

子要素を要素自身のキー名でソートする場合は,orderByKey()を利用します。

サンプルデータの場合は,/messages以下の "01", "02, "03", "04", "05" がそれぞれの要素のキーになります。

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

Query query = ref.orderByKey();
query.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;
        long timestamp = chatMessage.timestamp;
        Log.d("Firebase", String.format("onChildAdded, sender:%s, body:%s, timestamp:%d", sender, body, timestamp));
    }

結果は,最初に用意した通りの順番となるはずです。この挙動はオーダリングを指定しなかった場合のデフォルトの挙動です。

orderByValue()

子要素のキーではなく,要素の値でソートしたい場合は,orderByValue()を利用します。今度は,/countsに対してリスナを登録してみましょう。以下のようにします。

Firebase ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/counts");
Query query = ref.orderByValue();
query.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot dataSnapshot, String previousKey) {
        String user = dataSnapshot.getKey();
        long count = (long) dataSnapshot.getValue();
        Log.d("Firebase", String.format("user: %s's count=%d", user, count));
    }

今回は/countsに対してChild Addedイベントリスナを登録したので,各子要素は{"Bill" : 7}といったものが取得できるはずです。

そこでString user = dataSnapshot.getKey()してユーザ名を取得し,long count = (long) dataSnapshot.getValue();でメッセージ数を取得しています。

D/Firebase: user: Steve's count=2
D/Firebase: user: Bill's count=7
D/Firebase: user: Clint's count=9
D/Firebase: user: john's count=16
D/Firebase: user: Mike's count=25

以上のように,countで昇順にログ出力できたら成功です。

orderByPriority()

もうひとつ,priority(優先度)で並び替えるためのオプションがあります。これは,各データにユーザが任意の優先度を付けて並び替えをカスタマイズできるように用意されたものです。

実はFirebaseにはデータを降順に並べる手段が標準では用意されていません。したがって降順にするためのワークアラウンドにこのオプションを利用したりします。この辺りは後の連載の実践テクニックでぜひご紹介したいと思います。

条件付き取得

並び替えと組み合わせて,取得数の上限や,どこからどこまで取得するといった範囲を指定することができます。

limitToFirst(), limitToLast()

データを最初から数えていくつまで,最後から数えていくつまで,といった指定をするには,それぞれlimitToFirst(),limitToLast()を利用します。

Firebase ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/counts");
Query query = ref.orderByValue().limitToFirst(2);
// or
Query query = ref.orderByValue().limitToLast(2);
query.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot dataSnapshot, String previousKey) {
        String user = dataSnapshot.getKey();
        long count = (long) dataSnapshot.getValue();
        Log.d("Firebase", String.format("user: %s's count=%d", user, count));
    }

両方試して,それぞれ以下のように最初の2件,最後の2件が取得できることを確認してみてください。

D/Firebase: user: Steve's count=2
D/Firebase: user: Bill's count=7
// or
D/Firebase: user: john's count=16
D/Firebase: user: Mike's count=25
startAt(), endAt()

データの値がどこから始まって,どこまでを含む,といった範囲を指定するには,それぞれstartAt(), endAt()を利用します。 単独で使うことも,コンビネーションで使うこともできます。

Firebase ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/counts");
Query query = ref.orderByValue().startAt(3).endAt(17);
query.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot dataSnapshot, String previousKey) {
        String user = dataSnapshot.getKey();
        long count = (long) dataSnapshot.getValue();
        Log.d("Firebase", String.format("user: %s's count=%d", user, count));
    }

上記の例ですと,3~17の範囲の要素が取得できるので,以下のようにログに出力されれば成功です。

D/Firebase: user: Bill's count=7
D/Firebase: user: Clint's count=9
D/Firebase: user: john's count=16

ここにさらにref.orderByValue().startAt(3).endAt(17).limitToFirst(1)のようにして範囲内の最初の1件に絞るといったことも容易です。

equalTo()

最後に,指定した値とぴったり同じ値でマッチングするにはequalTo()を利用します。今度は/messagesからsenderMikeのメッセージを取得してみましょう。

Firebase ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/messages");
Query query = ref.orderByChild("sender").equalTo("Mike");
query.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;
        long timestamp = chatMessage.timestamp;
        Log.d("Firebase", String.format("onChildAdded, sender:%s, body:%s, timestamp:%d", sender, body, timestamp));
    }
以下のように"Mike"のメッセージだけが取得できていれば成功です。
D/Firebase: onChildAdded, sender:Mike, body:ho ho ho:), timestamp:20160403

まとめ

いかがだったでしょうか。

今回の連載では,同じようにデータを取得する場合でも,一気に全データを取得したり,特定のケースだけに特化した効率的なやり方があることを見ていきました。また,クエリを利用すればデータを並び替えたり取得条件を指定して柔軟にデータを取り出すことができることも確認しました。

次回の連載では,このチャットアプリケーションに新規にデータを追加する方法を解説し,ケースにあったさまざまなデータの保存方法をご紹介したいと思います。

どうぞお楽しみに。

著者プロフィール

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

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