本文件將說明如何在 Firebase 中使用資料清單。如要瞭解讀取及寫入 Firebase 資料的基本概念,請參閱「在 Android 上讀取及寫入資料」。
取得 DatabaseReference
如要從資料庫讀取及寫入資料,您需要 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(@NonNull DataSnapshot dataSnapshot) { for (DataSnapshot postSnapshot: dataSnapshot.getChildren()) { // TODO: handle the post } } @Override public void onCancelled(@NonNull DatabaseError databaseError) { // Getting Post failed, log a message Log.w(TAG, "loadPost:onCancelled", databaseError.toException()); // ... } });
如要擷取清單的所有子項,而非監聽其他 onChildAdded
事件,這類模式就很實用。
卸離監聽器
您可以呼叫 Firebase 資料庫參考資料的 removeEventListener()
方法,移除回呼。
如果事件監聽器已多次新增至資料位置,則系統會為每個事件多次呼叫該事件監聽器,因此您必須以相同次數卸離該事件監聽器,才能完全移除。
在父項事件監聽器上呼叫 removeEventListener()
不會自動移除在子項節點上註冊的事件監聽器;您必須在任何子項事件監聽器上呼叫 removeEventListener()
,才能移除回呼。
排序及篩選資料
您可以使用 Realtime Database Query
類別,依鍵值、值或子項值來擷取資料。您也可以將排序結果篩選為特定數量的結果,或篩選出特定範圍的鍵或值。
排序資料
如要擷取已排序的資料,請先指定其中一種排序方法,以決定結果的排序方式:
方法 | 用量 |
---|---|
orderByChild() |
依指定子項鍵或巢狀子項路徑的值排序結果。 |
orderByKey()
| 依子項索引鍵排序結果。 |
orderByValue() |
依子項值排序結果。 |
您一次只能使用 一個 排序方法。在同一項查詢中多次呼叫排序方法會擲回錯誤。
以下範例說明如何擷取使用者的熱門貼文清單,並依星號數量排序:
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 // ... });
如要進一步瞭解其他資料類型的排序方式,請參閱「查詢資料的排序方式」。
篩選資料
如要篩選資料,您可以在建構查詢時,將任何限制或範圍方法與排序方法結合。
方法 | 用量 |
---|---|
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(@NonNull DataSnapshot dataSnapshot) { for (DataSnapshot postSnapshot: dataSnapshot.getChildren()) { // TODO: handle the post } } @Override public void onCancelled(@NonNull 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 篇貼文的清單:
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
,系統會依字典順序依鍵排序。 - 接著是具有數值的子項,以遞增順序排列。如果多個子項的數值與指定子項節點相同,系統會依鍵值排序。
- 字串會排在數字之後,並依字典順序遞增排列。如果多個子項的值與指定子節點相同,系統會依字典順序排序。
- 物件會列在最後,並依字母順序依鍵遞增排序。
orderByKey
使用 orderByKey()
排序資料時,系統會依鍵值以遞增順序傳回資料。
- 可剖析為 32 位元整數的鍵的子項會排在最前面,並以遞增順序排序。
- 接著是使用字串值做為鍵的子項,以字典順序遞增排序。
orderByValue
使用 orderByValue()
時,子項會依值排序。排序條件與 orderByChild()
相同,只是會使用節點的值,而非指定子項鍵的值。