Android FirebaseUI for Cloud Firestore - FirestoreRecyclerAdapter

根據上一篇 FireStore 文章的延伸, 今天這篇打算分享 FirestoreRecyclerAdapterFirestoreRecyclerAdapter 是 FirebaseUI-Android 的其中一個部分的工具,在學習 firestore 發現這個東西非常好用,順便自己做個筆記用途,如有錯誤的地方,敬請留言告知。


物件模型

public class Contact {

    private String name;
    private String email;
    private String phone;
    private int priority;

    public Contact() {
        // Empty construtor needed
    }

    public Contact(String name, String email, String phone) {
        this.name = name;
        this.email = email;
        this.phone = phone;
    }

    public Contact(String name, String email, String phone, int priority) {
        this.name = name;
        this.email = email;
        this.phone = phone;
        this.priority = priority;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }

    public String getPhone() {
        return phone;
    }

    public int getPriority() {
        return priority;
    }
}
關於這個模型類的一些注意事項:
  • getter和setter遵循JavaBean命名模式,允許Firestore將數據映射到字段名稱(例如: getName() 提供 name 字段)。
  • 該類有一個空構造函數,這是Firestore自動數據映射所必需的。
對於正確構造的模型類(如Contact上面的類),Firestore可以執行自動序列化 DocumentReference#set() 和自動反 序列化 DocumentSnapshot#toObject() 。有關Firestore中數據映射的詳細信息,請參閱有關自定義對象的文檔。

Querying

這個 Class 主要用來查詢,在應用的主屏幕上,您可能希望顯示50條最新的聊天消息,排列順序依照 priority 欄位,升冪或降冪拍序。在Firestore中,您將使用以下查詢:
Query query = FirebaseFirestore.getInstance()
        .collection("PhoneBook")
        .orderBy("priority", Query.Direction.ASCENDING)
        .limit(50);
要在沒有FirebaseUI的情況下檢索此數據,您可以使用addSnapshotListener偵聽實時查詢更新:
query.addSnapshotListener(new EventListener<QuerySnapshot>() {
    @Override
    public void onEvent(@Nullable QuerySnapshot snapshot,
                        @Nullable FirebaseFirestoreException e) {
        if (e != null) {
            // Handle error
            //...
            return;
        }

        // Convert query snapshot to a list of chats
        List<Contact> contacts = snapshot.toObjects(Contact.class);

        // Update UI
        // ...
    }
});

使用FirebaseUI填充 RecyclerView 

如果您正在顯示數據列表,則可能希望將 Contact 對象(這邊使用電話簿,聯絡資料作為範例)綁定到a RecyclerView 。這意味著實現自定義 RecyclerView.Adapter ,並與協調的更新 EventListener Query

不用擔心,FirebaseUI會自動為您完成所有這些操作!

選擇適配器

FirebaseUI為Cloud Firestore提供兩種類型的RecyclerView適配器:
  •  FirestoreRecyclerAdapter - 將a綁定 Query 到a RecyclerView 並響應所有包含要添加,刪除,移動或更改的項目的實時事件。最好與小結果集一起使用,因為所有結果都會立即加載。
  •  FirestorePagingAdapter - 通過在頁面中加載數據將a綁定 Query 到a RecyclerView 。最適用於大型靜態數據集。此適配器不會遵守實時事件,因此它不會檢測新的/已刪除的項目或對已加載的項目的更改。

使用 FirestoreRecyclerAdapter 

FirestoreRecyclerAdapter 綁定 Query RecyclerView 。添加,刪除或更改文檔時,這些更新會自動實時應用於您的UI。

首先,通過構建配置適配器 FirestoreRecyclerOptions 。在這種情況下,我們將繼續我們的電話簿範例:
        // Configure recycler adapter options:
        //  * query is the Query object defined above.
        //  * Chat.class instructs the adapter to convert each DocumentSnapshot to a Chat object
        FirestoreRecyclerOptions<Contact> options = new FirestoreRecyclerOptions.Builder<Contact>()
                .setQuery(query, Contact.class)
                .build();
如果需要自定義解析模型類的方式,可以使用自定義 SnapshotParser
...setQuery(..., new SnapshotParser<Contact>() {
    @NonNull
    @Override
    public Chat parseSnapshot(@NonNull DocumentSnapshot snapshot) {
        return ...;
    }
});
接下來創建 FirestoreRecyclerAdapter 對象。您應該已經有一個 ViewHolder 子類來顯示每個項目。在這種情況下,我們將使用自定義 ContactHolder 類:
public class ContactAdapter extends FirestoreRecyclerAdapter<Contact, ContactAdapter.ContactViewHolder> {

    public ContactAdapter(@NonNull FirestoreRecyclerOptions<Contact> options) {
        super(options);
    }

    @Override
    protected void onBindViewHolder(@NonNull ContactViewHolder holder, int position, @NonNull Contact model) {
        holder.tvName.setText(model.getName());
        holder.tvEmail.setText(model.getEmail());
        holder.tvPhone.setText(model.getPhone());
        holder.tvPriority.setText(String.valueOf(model.getPriority()));
    }

    @NonNull
    @Override
    public ContactViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int position) {
        View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.contact_item,
                viewGroup, false);
        return new ContactViewHolder(view);
    }

    class ContactViewHolder extends RecyclerView.ViewHolder {
        TextView tvName;
        TextView tvEmail;
        TextView tvPhone;
        TextView tvPriority;

        public ContactViewHolder(@NonNull View itemView) {
            super(itemView);

            tvName = itemView.findViewById(R.id.tv_name);
            tvEmail = itemView.findViewById(R.id.tv_email);
            tvPhone = itemView.findViewById(R.id.tv_phone_num);
            tvPriority = itemView.findViewById(R.id.tv_priority);
        }
    }
}

 FirestoreRecyclerAdapter  lifecycle

開始/停止收聽
    @Override
    protected void onStart() {
        super.onStart();
        adapter.startListening();
    }

    @Override
    protected void onStop() {
        super.onStop();
        adapter.stopListening();
    }
自動收聽
如果您不想手動啟動/停止收聽,您可以使用 Android架構組件自動管理生命週期 FirestoreRecyclerAdapter 。傳遞 LifecycleOwner 給 FirestoreRecyclerOptions.Builder#setLifecycleOwner(...) 和,FirebaseUI將自動開始和停止收聽 onStart() onStop()
FirestoreRecyclerOptions<Contact> options = new FirestoreRecyclerOptions.Builder<Contact>()
                .setQuery(query, Contact.class)
                .setLifecycleOwner(this)
                .build();

數據和錯誤事件

使用時, FirestoreRecyclerAdapter 您可能希望每次數據更改或出現錯誤時執行某些操作。為此,請覆蓋 適配器的 onDataChanged() onError() 方法:
FirestoreRecyclerAdapter adapter = new FirestoreRecyclerAdapter<Chat, ChatHolder>(options) {
    // ...

    @Override
    public void onDataChanged() {
        // Called each time there is a new query snapshot. You may want to use this method
        // to hide a loading spinner or check for the "no documents" state and update your UI.
        // ...
    }

    @Override
    public void onError(FirebaseFirestoreException e) {
        // Called when there is an error getting a query snapshot. You may want to update
        // your UI to display an error message to the user.
        // ...
    }
};

使用 FirestorePagingAdapter 

FirestorePagingAdapter 綁定 Query RecyclerView 在頁面加載的文件。這導致時間和內存有效綁定,但是它放棄了由此分配的實時事件 FirestoreRecyclerAdapter

FirestorePagingAdapter 是建立在頂部的Android分頁支持庫。在應用程序中使用適配器之前,必須在支持庫上添加依賴項:
implementation 'android.arch.paging:runtime:1.x.x'
首先,通過構建配置適配器 FirestorePagingOptions 。由於分頁適配器不適合聊天應用程序(它不會檢測新消息),我們將考慮加載泛型的適配器 Item
// The "base query" is a query with no startAt/endAt/limit clauses that the adapter can use
// to form smaller queries for each page.  It should only include where() and orderBy() clauses
Query baseQuery = mItemsCollection.orderBy("value", Query.Direction.ASCENDING);

// This configuration comes from the Paging Support Library
// https://developer.android.com/reference/android/arch/paging/PagedList.Config.html
PagedList.Config config = new PagedList.Config.Builder()
        .setEnablePlaceholders(false)
        .setPrefetchDistance(10)
        .setPageSize(20)
        .build();

// The options for the adapter combine the paging configuration with query information
// and application-specific options for lifecycle, etc.
FirestorePagingOptions<Item> options = new FirestorePagingOptions.Builder<Item>()
        .setLifecycleOwner(this)
        .setQuery(baseQuery, config, Item.class)
        .build();

以下兩點跟上述的 FirestoreRecyclerAdapter ㄧ樣
  • 如果需要自定義解析模型類的方式,可以使用自定義 SnapshotParser 
  • 創建 FirestorePagingAdapter 對象。您應該已經有一個 ViewHolder 子類來顯示每個項目。在這種情況下,我們將使用自定義 ItemViewHolder

 FirestorePagingAdapter 生命週期

以下兩點跟上述的 FirestoreRecyclerAdapter ㄧ樣
  • 開始/停止收聽
  • 自動收聽
尋呼事件
使用時 FirestorePagingAdapter ,您可能希望每次數據更改或出現錯誤時執行某些操作。為此,請覆蓋 onLoadingStateChanged() 適配器的方法:
FirestorePagingAdapter<Item, ItemViewHolder> adapter =
        new FirestorePagingAdapter<Item, ItemViewHolder>(options) {

            // ...

            @Override
            protected void onLoadingStateChanged(@NonNull LoadingState state) {
                switch (state) {
                    case LOADING_INITIAL:
                        // The initial load has begun
                        // ...
                    case LOADING_MORE:
                        // The adapter has started to load an additional page
                        // ...
                    case LOADED:
                        // The previous load (either initial or additional) completed
                        // ...
                    case ERROR:
                        // The previous load (either initial or additional) failed. Call
                        // the retry() method in order to retry the load operation.
                        // ...
                }
            }
        };

FirebaseUI for Cloud Firestore

FirebaseUI的其中一小部分介紹完畢。

留言

這個網誌中的熱門文章

Android - 使用 adb 安装apk

Android TextView autosizing 自動調整大小

Kotlin - 實現Android中的Parcelable