全新推出 Cloud Firestore(测试版):试用 Firebase 和 Google Cloud Platform 全新推出的规模可扩展且灵活的数据库。详细了解 Cloud Firestore

在 Android 上处理数据列表

本文将介绍如何在 Firebase 中处理数据列表。要了解有关 Firebase 数据读写的基础知识,请参阅在 Android 上读取和写入数据

获取 DatabaseReference

要从数据库读取数据以及将数据写入到数据库中,您需要一个 DatabaseReference 实例:

private DatabaseReference mDatabase;
// ...
mDatabase = FirebaseDatabase.getInstance().getReference();

读取和写入列表

向数据列表附加数据

使用 push() 方法可将数据附加到多用户应用内的列表中。每次将新的子项目添加到指定的 Firebase 引用时,push() 方法均会生成一个唯一键。如果为列表中的每个新元素使用这些自动生成的键,则多个客户端可以同时向同一位置添加子项目,而不会引起写入冲突。push() 生成的唯一键是以时间戳为基础的,因此列表项目会自动按时间顺序排列。

您可以使用对 push() 方法返回的新数据的引用,获取子项目的自动生成的键的值或为子项目设置数据。对 push() 引用调用 getKey() 将返回自动生成的键的值。

然后,您可以使用这些自动生成的键简化数据结构的平展过程。如需了解详细信息,请参阅数据扇出示例

侦听子项目事件

在处理列表时,您的应用应侦听子项目事件,而不是用于各个对象的值事件。

当因为某项操作(例如通过 push() 方法添加新的子项目,或通过 updateChildren() 方法更新子项目)而使得某个项目的子项目发生更改时,就会触发子项目事件。同时使用所有这些事件有助于侦听数据库中某个特定项目是否有更改。

要侦听 DatabaseReference 中发生的子项目事件,请附加一个 ChildEventListener

侦听器 事件回调函数 典型用法
ChildEventListener onChildAdded() 检索项目列表,或侦听项目列表中是否添加了新项目。该回调将针对每个现有的子项目触发一次,并在每次向指定的路径添加新的子项目时再次触发。传递给侦听器的 DataSnapshot 包含新子项目的数据。
onChildChanged() 侦听对列表中的项目做出的更改。每次修改子项目(包括对子项目的后代所做的任何修改)时,均会触发此事件。传递给事件侦听器的 DataSnapshot 包含子项目的更新数据。
onChildRemoved() 侦听从列表中移除的项目。传递给事件回调的 DataSnapshot 包含已移除的子项目的数据。
onChildMoved() 侦听经过排序的列表的项目顺序是否有更改。每当引发子项目重新排序的更新触发了 onChildChanged() 回调函数时,就会触发此事件。当数据已通过 orderByChildorderByValue 排序后,就可以使用此事件。

例如,社交博客应用可以结合使用这些方法来监控博文评论中的活动,如下所示:

ChildEventListener childEventListener = new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
        Log.d(TAG, "onChildAdded:" + dataSnapshot.getKey());

        // A new comment has been added, add it to the displayed list
        Comment comment = dataSnapshot.getValue(Comment.class);

        // ...
    }

    @Override
    public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
        Log.d(TAG, "onChildChanged:" + dataSnapshot.getKey());

        // A comment has changed, use the key to determine if we are displaying this
        // comment and if so displayed the changed comment.
        Comment newComment = dataSnapshot.getValue(Comment.class);
        String commentKey = dataSnapshot.getKey();

        // ...
    }

    @Override
    public void onChildRemoved(DataSnapshot dataSnapshot) {
        Log.d(TAG, "onChildRemoved:" + dataSnapshot.getKey());

        // A comment has changed, use the key to determine if we are displaying this
        // comment and if so remove it.
        String commentKey = dataSnapshot.getKey();

        // ...
    }

    @Override
    public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
        Log.d(TAG, "onChildMoved:" + dataSnapshot.getKey());

        // A comment has changed position, use the key to determine if we are
        // displaying this comment and if so move it.
        Comment movedComment = dataSnapshot.getValue(Comment.class);
        String commentKey = dataSnapshot.getKey();

        // ...
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        Log.w(TAG, "postComments:onCancelled", databaseError.toException());
        Toast.makeText(mContext, "Failed to load comments.",
                Toast.LENGTH_SHORT).show();
    }
};
ref.addChildEventListener(childEventListener);

侦听值事件

尽管使用 ChildEventListener 是推荐的读取数据列表的方式,但有些情况下向列表引用附加 ValueEventListener 会很有用。

如果将一个 ValueEventListener 附加到某个数据列表,系统会以单个 DataSnapshot 的形式返回整个数据列表,然后您可以遍历该数据快照来访问各个子项目。

即使查询仅存在一个匹配项时,该快照仍是一个列表;只是这个列表仅包含单个项目。要访问该项目,您需要遍历查询结果:

// My top posts by number of stars
myTopPostsQuery.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        for (DataSnapshot postSnapshot: dataSnapshot.getChildren()) {
            // TODO: handle the post
        }
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
        // ...
    }
});

当您希望通过一次操作提取某个列表的所有子项目(而不是侦听额外 onChildAdded 事件)时,此模式可能会很有用。

分离侦听器

通过对 Firebase 数据库引用调用 removeEventListener() 方法可移除回调函数。

如果多次将侦听器添加到某一数据位置,则每次发生事件时,系统都会多次调用该侦听器,您必须执行相同次数的分离操作才能将其完全移除。

对父侦听器调用 removeEventListener() 时不会自动移除在其子项目上注册的侦听器;您还必须对所有子侦听器调用 removeEventListener() 才能移除回调函数。

排序和过滤数据

您可以使用实时数据库 Query 类来检索按键、按值或按子项目的值排序的数据。您还可以对排序后的结果进行过滤,从而得到特定数量的结果,或者键或值在某个范围内的结果。

将数据排序

要检索经过排序的数据,请先指定一项排序依据方法,确定如何对结果排序:

方法 用法
orderByChild() 按指定子键的值或嵌套子路径对结果排序。
orderByKey() 按子键对结果排序。
orderByValue() 按子值对结果排序。

您每次只能使用一种排序依据方法。对同一查询多次调用排序依据方法会引发错误。

以下示例演示了如何检索按所得星数排序的用户热门博文列表:

// My top posts by number of stars
String myUserId = getUid();
Query myTopPostsQuery = databaseReference.child("user-posts").child(myUserId)
        .orderByChild("starCount");
myTopPostsQuery.addChildEventListener(new ChildEventListener() {
    // TODO: implement the ChildEventListener methods as documented above
    // ...
});

此示例定义了一个查询,如果将该查询与一个子节点侦听器结合使用,就能使客户端数据与数据库中用户在该路径下的博文同步,并按每篇博文获得的星数进行排序。这种使用 ID 作为索引键的技术称为“数据扇出”,您可以在构建您的数据库中详加了解。

调用 orderByChild() 方法可指定对结果排序所依据的子键。在本例中,博文按各自 "starCount" 子项目的值进行排序。您具有与下例类似的数据,查询也可以按嵌套的子项目进行排序:

"posts": {
  "ts-functions": {
    "metrics": {
      "views" : 1200000,
      "likes" : 251000,
      "shares": 1200,
    },
    "title" : "Why you should use TypeScript for writing Cloud Functions",
    "author": "Doug",
  },
  "android-arch-3": {
    "metrics": {
      "views" : 900000,
      "likes" : 117000,
      "shares": 144,
    },
    "title" : "Using Android Architecture Components with Firebase Realtime Database (Part 3)",
    "author": "Doug",
  }
},

在本例中,通过在 metrics 调用中指定嵌套子项目的相对路径,我们可以按嵌套在 orderByChild() 键下的值对列表元素进行排序。

 
// Most viewed posts
Query myMostViewedPostsQuery = databaseReference.child("posts")
        .orderByChild("metrics/views");
myMostViewedPostsQuery.addChildEventListener(new ChildEventListener() {
    // TODO: implement the ChildEventListener methods as documented above
    // ...
});

如需详细了解如何对其他数据类型进行排序,请参阅如何对查询数据进行排序

过滤数据

要过滤数据,您可以在构建查询时将某种限制方法或范围方法与排序依据方法结合使用。

方法 用法
limitToFirst() 设置要返回的最大项目数:从经过排序的结果列表开头算起。
limitToLast() 设置要返回的最大项目数:从经过排序的结果列表结尾算起。
startAt() 返回大于或等于指定键或值的项目,具体取决于所选的排序依据方法。
endAt() 返回小于或等于指定键或值的项目,具体取决于所选的排序依据方法。
equalTo() 返回等于指定键或值的项目,具体取决于所选的排序依据方法。

与排序依据方法不同,您可以结合使用多种限制或范围函数。例如,您可以结合使用 startAt()endAt() 方法,将结果限制在指定的取值范围内。

即使查询仅存在一个匹配项时,该快照仍是一个列表;只是这个列表仅包含单个项目。要访问该项目,您需要遍历查询结果:

// My top posts by number of stars
myTopPostsQuery.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        for (DataSnapshot postSnapshot: dataSnapshot.getChildren()) {
            // TODO: handle the post
        }
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
        // ...
    }
});

限制结果数

您可以使用 limitToFirst()limitToLast() 方法来设置给定回调函数要同步的子项目数上限。例如,如果使用 limitToFirst() 将数量上限设为 100,则起初最多会收到 100 次 onChildAdded() 回调。如果您在 Firebase 数据库中存储的项目不到 100 个,则每个项目均会触发一次 onChildAdded() 回调。

随着项目发生更改,对于落入查询范围的项目,您将收到 onChildAdded() 回调;对于不再属于查询范围的项目,您将收到 onChildRemoved() 回调,因此总数始终保持为 100。

以下示例演示了示例博客应用如何定义查询以检索所有用户中 100 篇最新博文的列表:

// Last 100 posts, these are automatically the 100 most recent
// due to sorting by push() keys
Query recentPostsQuery = databaseReference.child("posts")
        .limitToFirst(100);

此示例只定义了一个查询,要实际同步数据,还需要附加一个侦听器

按键或值过滤

您可以使用 startAt()endAt()equalTo() 为查询选择任意起始点、结束点和等值点。这对于将数据分页或查找其子项目具有特定值的项目非常有用。

如何对查询数据进行排序

本部分将介绍如何通过 Query 类中的每种排序依据方法对数据进行排序。

orderByChild

使用 orderByChild() 时,系统会按如下方式对包含指定子键的数据进行排序:

  1. 指定子键的值为 null 的子项目排在最前。
  2. 接下来是指定子键的值为 false 的子项目。如果多个子项目的值均为 false,则按照键以字典顺序对它们进行排序。
  3. 接下来是指定子键为 true 值的子项目。如果多个子项目的值均为 true,则按键以字典顺序对它们进行排序。
  4. 接下来是值为数字的子项目,按升序排序。如果指定子节点的多个子项目具有相同的数字值,则按键对它们进行排序。
  5. 字符串显示在数字后面并按字典顺序以升序排列。如果指定子节点的多个子项目具有相同的值,则按键以字典顺序对它们进行排序。
  6. 指定子键值为对象的子项目放在最后,并按照键以字典顺序排列(升序)。

orderByKey

当使用 orderByKey() 对数据进行排序时,系统会按照键以升序返回数据。

  1. 键可以解析为二进制 32 位整数的子项目排在最前,按升序排列。
  2. 以字符串值作为键的子项目排在后面,按字典顺序以升序排列。

orderByValue

使用 orderByValue() 时,系统将按子项目本身的值对其进行排序。排序标准与 orderByChild() 中相同,但这里使用的是节点本身的值而非指定子键的值。

后续步骤

发送以下问题的反馈:

此网页
Firebase 实时数据库
需要帮助?请访问我们的支持页面