在 Android 上使用資料列表

本文檔介紹如何使用 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()回調由導致子項重新排序的更新觸發時,就會觸發此事件。它與透過orderByChildorderByValue排序的資料一起使用。

例如,社交部落格應用程式可能會同時使用這些方法來監視貼文評論中的活動,如下所示:

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()以刪除回呼。

排序和過濾數據

您可以使用即時資料庫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
    // ...
});

有關其他資料類型如何排序的詳細信息,請參閱查詢資料如何排序

過濾數據

要過濾數據,您可以在建構查詢時將任何限製或範圍方法與排序方法結合。

方法用法
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類別中的每個 order-by 方法對資料進行排序。

orderByChild

使用orderByChild()時,包含指定子鍵的資料會依下列方式排序:

  1. 指定子鍵的值為null子項排在第一位。
  2. 接下來是指定子鍵值為false的子項。如果多個子項的值為false ,則它們按鍵按字典順序排序。
  3. 接下來是指定子鍵值為true的子項。如果多個子項的值為true ,則它們按鍵按字典順序排序。
  4. 接下來是具有數值的子項,依升序排序。如果指定子節點的多個子節點具有相同的數值,則它們會按鍵排序。
  5. 字串位於數字之後,並依字典順序升序排序。如果指定子節點的多個子節點具有相同的值,則它們按鍵按字典順序排序。
  6. 物件排在最後,並依字典順序按鍵升序排序。

orderByKey

當使用orderByKey()對資料進行排序時,資料將按鍵升序傳回。

  1. 具有可解析為 32 位元整數的鍵的子項排在前面,按升序排序。
  2. 接下來是以字串值作為鍵的子項,依字典順序升序排列。

orderByValue

使用orderByValue()時,子項會依其值排序。排序標準與orderByChild()中的相同,只是使用節點的值而不是指定子鍵的值。

下一步