本文檔介紹如何在 Firebase 中使用數據列表。要了解讀寫 Firebase 數據的基礎知識,請參閱在 Android 上讀寫數據。
獲取數據庫引用
要從數據庫讀取和寫入數據,您需要一個DatabaseReference
實例:
Kotlin+KTX
private lateinit var database: DatabaseReference // ... database = Firebase.database.reference
Java
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() 回調由導致子項重新排序的更新觸發,就會觸發此事件。它與使用orderByChild 或orderByValue 排序的數據一起使用。 |
例如,社交博客應用程序可能會同時使用這些方法來監控帖子評論中的活動,如下所示:
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)
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);
監聽值事件
雖然使用ChildEventListener
是讀取數據列表的推薦方法,但在某些情況下,將ValueEventListener
附加到列表引用很有用。
將ValueEventListener
附加到數據列表會將整個數據列表作為單個DataSnapshot
返回,然後您可以循環訪問各個子項。
即使查詢只有一個匹配項,快照仍然是一個列表;它只包含一個項目。要訪問該項目,您需要遍歷結果:
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()) // ... } })
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()); // ... } });
當您想在單個操作中獲取列表的所有子項而不是監聽額外的onChildAdded
事件時,此模式會很有用。
分離監聽器
通過對 Firebase 數據庫引用調用removeEventListener()
方法來刪除回調。
如果一個偵聽器已多次添加到數據位置,則每個事件都會多次調用它,並且您必須將其分離相同的次數才能將其完全刪除。
在父監聽器上調用removeEventListener()
不會自動刪除在其子節點上註冊的監聽器;還必須對任何子偵聽器調用removeEventListener()
以刪除回調。
排序和過濾數據
您可以使用實時數據庫Query
類來檢索按鍵、值或子值排序的數據。您還可以將排序結果過濾為特定數量的結果或一定範圍的鍵或值。
排序數據
要檢索排序的數據,首先指定一種排序方法以確定結果的排序方式:
方法 | 用法 |
---|---|
orderByChild() | 按指定子鍵或嵌套子路徑的值對結果進行排序。 | orderByKey() | 按子鍵排序結果。 |
orderByValue() | 按子值排序結果。 |
您一次只能使用一種排序方法。在同一查詢中多次調用 order-by 方法會引發錯誤。
以下示例演示瞭如何檢索按星數排序的用戶熱門帖子列表:
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 // ... })
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 // ... });
這定義了一個查詢,當與子偵聽器結合時,根據用戶 ID 將客戶端與數據庫路徑中的用戶帖子同步,按每個帖子收到的星數排序。這種使用 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", } },
在此示例中,我們可以通過在orderByChild()
調用中指定嵌套子項的相對路徑,按嵌套在metrics
鍵下的值對我們的列表元素進行排序。
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 // ... })
Java
// Most viewed posts Query myMostViewedPostsQuery = databaseReference.child("posts") .orderByChild("metrics/views"); myMostViewedPostsQuery.addChildEventListener(new ChildEventListener() { // TODO: implement the ChildEventListener methods as documented above // ... });
有關其他數據類型如何排序的更多信息,請參閱查詢數據的排序方式。
過濾數據
要過濾數據,您可以在構造查詢時將任何 limit 或 range 方法與 order-by 方法結合使用。
方法 | 用法 |
---|---|
limitToFirst() | 設置從有序結果列表的開頭返回的最大項目數。 |
limitToLast() | 設置從有序結果列表末尾返回的最大項目數。 |
startAt() | 根據所選的排序方法,返回大於或等於指定鍵或值的項目。 |
startAfter() | 根據所選的排序方法,返回大於指定鍵或值的項目。 |
endAt() | 根據所選的排序方法,返回小於或等於指定鍵或值的項目。 |
endBefore() | 根據所選的排序方法,返回小於指定鍵或值的項目。 |
equalTo() | 根據所選的排序方法,返回等於指定鍵或值的項目。 |
與排序方法不同,您可以組合多個限製或範圍函數。例如,您可以結合使用startAt()
和endAt()
方法來將結果限制在指定的值範圍內。
即使查詢只有一個匹配項,快照仍然是一個列表;它只包含一個項目。要訪問該項目,您需要遍歷結果:
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()) // ... } })
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()); // ... } });
限制結果數量
您可以使用limitToFirst()
和limitToLast()
方法來設置要為給定回調同步的最大子級數。例如,如果您使用limitToFirst()
將限制設置為 100,則您最初最多只能收到 100 個onChildAdded()
回調。如果您的 Firebase 數據庫中存儲的項目少於 100 個,則會為每個項目觸發一個onChildAdded()
回調。
隨著項目的更改,您會收到進入查詢的項目的onChildRemoved()
onChildAdded()
和退出查詢的項目的 onChildRemoved() 回調,以便總數保持在 100。
以下示例演示了示例博客應用程序如何定義查詢以檢索所有用戶最近 100 篇帖子的列表:
Kotlin+KTX
// Last 100 posts, these are automatically the 100 most recent // due to sorting by push() keys. databaseReference.child("posts").limitToFirst(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);
這個例子只定義了一個查詢,實際同步數據需要有一個附加的監聽器。
按鍵或值過濾
您可以使用startAt()
、 startAfter()
、 endAt()
、 endBefore()
和equalTo()
為查詢選擇任意起點、終點和等價點。這對於分頁數據或查找包含具有特定值的子項的項目很有用。
查詢數據的排序方式
本節說明如何通過Query
類中的每個排序方法對數據進行排序。
orderByChild
使用orderByChild()
時,包含指定子鍵的數據按如下方式排序:
- 指定子鍵的
null
值的子項排在第一位。 - 接下來是指定子鍵值為
false
的子項。如果多個孩子的值為false
,則它們按字典順序按鍵排序。 - 接下來是指定子鍵值為
true
的子項。如果多個孩子的值為true
,則它們按字典順序按鍵排序。 - 接下來是具有數值的孩子,按升序排序。如果指定子節點的多個子節點的數值相同,則按key排序。
- 字符串位於數字之後,並按字典順序升序排序。如果指定子節點的多個子節點具有相同的值,則它們按鍵按字典順序排序。
- 對象排在最後,並按鍵按字典順序升序排序。
orderByKey
當使用orderByKey()
對數據進行排序時,數據將按鍵按升序返回。
- 具有可以解析為 32 位整數的鍵的孩子排在第一位,按升序排序。
- 接下來是以字符串值作為鍵的子項,按字典順序升序排序。
orderByValue
使用orderByValue()
時,子項按其值排序。排序標準與orderByChild()
中的相同,除了使用節點的值而不是指定子鍵的值。