获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

在 Android 上使用数据列表

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

获取数据库引用

要从数据库读取和写入数据,您需要一个DatabaseReference实例:

Java

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

Kotlin+KTX

private lateinit var database: DatabaseReference
// ...
database = Firebase.database.reference

读取和写入列表

附加到数据列表

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

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

您可以使用这些自动生成的键来简化数据结构的扁平化。有关详细信息,请参阅数据扇出示例

监听子事件

使用列表时,您的应用程序应该监听子事件而不是用于单个对象的值事件。

子事件被触发以响应某个操作对节点的子节点发生的特定操作,例如通过push()方法添加的新子节点或通过updateChildren()方法更新的子节点。这些中的每一个都可用于监听数据库中特定节点的更改。

为了侦听DatabaseReference上的子事件,请附加ChildEventListener

听众事件回调典型用法
ChildEventListener onChildAdded()检索项目列表或监听项目列表的添加。此回调为每个现有的孩子触发一次,然后在每次将新的孩子添加到指定路径时再次触发。传递给侦听器的DataSnapshot包含新孩子的数据。
onChildChanged()监听列表中项目的更改。每当修改子节点时都会触发此事件,包括对子节点后代的任何修改。传递给事件侦听器的DataSnapshot包含子项的更新数据。
onChildRemoved()侦听从列表中删除的项目。传递给事件回调的DataSnapshot包含已删除子项的数据。
onChildMoved()监听有序列表中项目顺序的变化。每当onChildChanged()回调由导致子项重新排序的更新触发时,都会触发此事件。它与使用orderByChildorderByValue排序的数据一起使用。

例如,社交博客应用程序可能会同时使用这些方法来监控帖子评论中的活动,如下所示:

Java

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();
    }
};
databaseReference.addChildEventListener(childEventListener);

Kotlin+KTX

val childEventListener = object : ChildEventListener {
    override fun onChildAdded(dataSnapshot: DataSnapshot, previousChildName: String?) {
        Log.d(TAG, "onChildAdded:" + dataSnapshot.key!!)

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

        // ...
    }

    override fun onChildChanged(dataSnapshot: DataSnapshot, previousChildName: String?) {
        Log.d(TAG, "onChildChanged: ${dataSnapshot.key}")

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

        // ...
    }

    override fun onChildRemoved(dataSnapshot: DataSnapshot) {
        Log.d(TAG, "onChildRemoved:" + dataSnapshot.key!!)

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

        // ...
    }

    override fun onChildMoved(dataSnapshot: DataSnapshot, previousChildName: String?) {
        Log.d(TAG, "onChildMoved:" + dataSnapshot.key!!)

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

        // ...
    }

    override fun onCancelled(databaseError: DatabaseError) {
        Log.w(TAG, "postComments:onCancelled", databaseError.toException())
        Toast.makeText(context, "Failed to load comments.",
                Toast.LENGTH_SHORT).show()
    }
}
databaseReference.addChildEventListener(childEventListener)

监听价值事件

虽然使用ChildEventListener是读取数据列表的推荐方法,但在某些情况下将ValueEventListener附加到列表引用很有用。

ValueEventListener附加到数据列表将返回整个数据列表作为单个DataSnapshot ,然后您可以循环访问单个子级。

即使查询只有一个匹配项,快照仍然是一个列表;它只包含一个项目。要访问该项目,您需要遍历结果:

Java

// 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());
        // ...
    }
});

Kotlin+KTX

// My top posts by number of stars
myTopPostsQuery.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {
        for (postSnapshot in dataSnapshot.children) {
            // TODO: handle the post
        }
    }

    override fun onCancelled(databaseError: DatabaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException())
        // ...
    }
})

当您想在单个操作中获取列表的所有子项而不是侦听其他onChildAdded事件时,此模式很有用。

分离监听器

通过在 Firebase 数据库引用上调用removeEventListener()方法来删​​除回调。

如果已将侦听器多次添加到数据位置,则每个事件都会多次调用它,并且您必须将其分离相同的次数才能将其完全删除。

在父侦听器上调用removeEventListener()不会自动删除在其子节点上注册的侦听器;还必须在任何子侦听器上调用removeEventListener()以删除回调。

排序和过滤数据

您可以使用实时数据库Query类来检索按键、按值或按子值排序的数据。您还可以将排序结果筛选为特定数量的结果或键或值范围。

排序数据

要检索已排序的数据,请首先指定一种 order-by 方法来确定结果的排序方式:

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

您一次只能使用一种排序方法。在同一个查询中多次调用 order-by 方法会引发错误。

以下示例演示了如何检索按星数排序的用户热门帖子列表:

Java

// 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
    // ...
});

Kotlin+KTX

// My top posts by number of stars
val myUserId = uid
val myTopPostsQuery = databaseReference.child("user-posts").child(myUserId)
    .orderByChild("starCount")

myTopPostsQuery.addChildEventListener(object : ChildEventListener {
    // TODO: implement the ChildEventListener methods as documented above
    // ...
})

这定义了一个查询,当与子侦听器结合使用时,该查询根据用户 ID 将客户端与数据库中路径中的用户帖子同步,按每个帖子收到的星数排序。这种使用 ID 作为索引键的技术称为数据扇出,您可以在Structure Your Database中了解更多信息。

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",
  }
},

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

Java

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

Kotlin+KTX

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

有关其他数据类型如何排序的详细信息,请参阅查询数据的排序方式

过滤数据

要过滤数据,您可以在构造查询时将任何限制或范围方法与 order-by 方法结合使用。

方法用法
limitToFirst()设置从有序结果列表的开头返回的最大项目数。
limitToLast()设置从有序结果列表末尾返回的最大项目数。
startAt()根据选择的 order-by 方法返回大于或等于指定键或值的项目。
startAfter()根据选择的 order-by 方法返回大于指定键或值的项目。
endAt()根据选择的 order-by 方法返回小于或等于指定键或值的项目。
endBefore()根据选择的 order-by 方法返回小于指定键或值的项目。
equalTo()根据选择的 order-by 方法返回等于指定键或值的项目。

与 order-by 方法不同,您可以组合多个限制或范围函数。例如,您可以组合startAt()endAt()方法来将结果限制在指定的值范围内。

即使查询只有一个匹配项,快照仍然是一个列表;它只包含一个项目。要访问该项目,您需要遍历结果:

Java

// 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());
        // ...
    }
});

Kotlin+KTX

// My top posts by number of stars
myTopPostsQuery.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {
        for (postSnapshot in dataSnapshot.children) {
            // TODO: handle the post
        }
    }

    override fun 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()回调。

随着项目的变化,您会收到进入查询的项目的onChildRemoved() onChildAdded()和退出查询的项目的 onChildRemoved() 回调,因此总数保持在 100。

以下示例演示了示例博客应用程序如何定义一个查询来检索所有用户最近发布的 100 个帖子的列表:

Java

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

Kotlin+KTX

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

这个例子只定义了一个查询,要真正同步数据,它需要一个附加的监听器。

按键或值过滤

您可以使用startAt()startAfter()endAt()endBefore()equalTo()来选择查询的任意起点、终点和等价点。这对于对数据进行分页或查找具有特定值的子项非常有用。

查询数据如何排序

本节说明如何按Query类中的每个 order-by 方法对数据进行排序。

orderByChild

使用orderByChild()时,包含指定子键的数据按如下顺序排列:

  1. 具有指定子键的null值的子项首先出现。
  2. 接下来是指定子键的值为false的子项。如果多个孩子的值为false ,则它们按字典顺序键排序。
  3. 接下来是指定子键的值为true的子项。如果多个孩子的值为true ,则它们按字典顺序键排序。
  4. 接下来是具有数值的子项,按升序排列。如果指定子节点的多个子节点的数值相同,则按key排序。
  5. 字符串在数字之后,并按字典顺序升序排序。如果指定子节点的多个子节点具有相同的值,则它们按字典顺序键排序。
  6. 对象排在最后,并按关键字按字典顺序升序排序。

orderByKey

使用orderByKey()对数据进行排序时,数据按 key 升序返回。

  1. 具有可以被解析为 32 位整数的键的子项排在最前面,按升序排序。
  2. 以字符串值作为键的子项紧随其后,按字典顺序升序排序。

orderByValue

使用orderByValue()时,子项按其值排序。排序标准与orderByChild()中的相同,除了使用节点的值而不是指定子键的值。

下一步