【问题标题】:How to paginate Firestore with Android?如何使用 Android 对 Firestore 进行分页?
【发布时间】:2018-11-17 09:53:14
【问题描述】:

我阅读了 Firestore 文档和 Internet (stackoverflow) 上有关 Firestore 分页的所有文章,但没有运气。我试图在文档中实现确切的代码,但没有任何反应。我有一个包含项目(超过 1250 个或更多)的基本数据库,我想逐步获取它们。通过滚动加载 15 个项目(到数据库中的最后一个项目)。

如果使用文档代码:

// Construct query for first 25 cities, ordered by population
Query first = db.collection("cities")
    .orderBy("population")
    .limit(25);

first.get()
    .addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
    @Override
    public void onSuccess(QuerySnapshot documentSnapshots) {
        // ...

        // Get the last visible document
        DocumentSnapshot lastVisible = documentSnapshots.getDocuments()
            .get(documentSnapshots.size() -1);

        // Construct a new query starting at this document,
        // get the next 25 cities.
        Query next = db.collection("cities")
            .orderBy("population")
            .startAfter(lastVisible)
            .limit(25);

        // Use the query for pagination
        // ...
    }
});

怎么办?文档没有太多细节。

PS:当用户滚动时,我需要使用回收站视图(不是列表视图)。谢谢

【问题讨论】:

标签: java android firebase google-cloud-firestore


【解决方案1】:

您也可以使用 Firebase-UI-Firestore 提供的FirestorePagingAdapter

你需要安装这个依赖

 implementation 'com.firebaseui:firebase-ui-firestore:latest_version_here'

解决方案

第 1 步:创建一个全局 Firestore 分页适配器变量并传递 Model 类和 ViewHolder,以及 Model 变量。

private FirestorePagingAdapter<Model, ModelViewHolder> adapter;
private Model model;

第 2 步: 创建一个 Firebase 查询

Query query = db.collection("cities")
    .orderBy("population");

第 3 步: 让我们构建 pagedlist 配置。这里会传递每个页面要查询多少数据;

PagedList.Config config = new PagedList.Config.Builder()
                .setEnablePlaceholders(false)
                .setPrefetchDistance(10)
                .setPageSize(15)
                .build();

第 4 步:设置配置后,现在让我们构建 Firestore 分页选项,您将在其中传递 queryconfig

 FirestorePagingOptions<Model> options = new FirestorePagingOptions.Builder<Model>()
                .setLifecycleOwner(this)
                .setQuery(query, config, snapshot -> {
                    model = snapshot.toObject(Model.class);
                    return model;
                })
                .build();

步数:5 现在让我们将数据传递给 Recylerview

 adapter = new FirestorePagingAdapter<Model, ModelViewHolder>(options) {
            @Override
            protected void onBindViewHolder(@NonNull ModelViewHolder holder, int position, @NonNull Model model) {
                holder.bindTO(model);
            }

            @NonNull
            @Override
            public ModelViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
                View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_model, parent, false);
                return new ModelViewHolder(view);
            }

            @Override
            protected void onError(@NonNull Exception e) {
                super.onError(e);
                     //logic here
            }

            @Override
            protected void onLoadingStateChanged(@NonNull LoadingState state) {
                switch (state) {
                    case LOADING_INITIAL:
                        break;

                    case LOADING_MORE:
                        break;

                    case LOADED:
                        notifyDataSetChanged();
                        break;

                    case ERROR:
                        Toast.makeText(requireActivity(), "Error", Toast.LENGTH_SHORT).show();
                         //logic here
                        break;

                    case FINISHED:
                        //logic here
                        break;
                }
            }
        };
        productRecycler.setAdapter(adapter);
        adapter.notifyDataSetChanged();
    }

编码愉快!

【讨论】:

    【解决方案2】:

    正如official documentation中提到的,解决这个问题的关键是使用startAfter()方法。因此,您可以通过将查询游标与limit() 方法组合来对查询进行分页。您将能够使用批次中的最后一个文档作为下一个批次的光标的开始。

    要解决这个分页问题,​​请参阅我的回答 post,我在其中逐步解释了如何从 Cloud Firestore 数据库中以较小的块加载数据并显示它在一个ListView 按钮点击。

    解决方案:

    要从您的 Firestore 数据库中获取数据并将其以较小的块显示在 RecyclerView 中,请按照以下步骤操作。

    让我们以上面我使用过产品的例子为例。你可以使用产品、城市或任何你想要的东西。原理是一样的。假设您想在用户滚动时加载更多产品,我将使用RecyclerView.OnScrollListener

    让我们首先定义RecyclerView,将布局管理器设置为LinearLayoutManager,然后创建一个列表。我们还使用空列表实例化适配器并将适配器设置为我们的RecyclerView

    RecyclerView recyclerView = findViewById(R.id.recycler_view);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    List<ProductModel> list = new ArrayList<>();
    ProductAdapter productAdapter = new ProductAdapter(list);
    recyclerView.setAdapter(productAdapter);
    

    假设我们有一个如下所示的数据库结构:

    Firestore-root
       |
       --- products (collection)
             |
             --- productId (document)
                    |
                    --- productName: "Product Name"
    

    还有一个看起来像这样的模型类:

    public class ProductModel {
        private String productName;
    
        public ProductModel() {}
    
        public ProductModel(String productName) {this.productName = productName;}
    
        public String getProductName() {return productName;}
    }
    

    适配器类应该是这样的:

    private class ProductAdapter extends RecyclerView.Adapter<ProductViewHolder> {
        private List<ProductModel> list;
    
        ProductAdapter(List<ProductModel> list) {
            this.list = list;
        }
    
        @NonNull
        @Override
        public ProductViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_product, parent, false);
            return new ProductViewHolder(view);
        }
    
        @Override
        public void onBindViewHolder(@NonNull ProductViewHolder productViewHolder, int position) {
            String productName = list.get(position).getProductName();
            productViewHolder.setProductName(productName);
        }
    
        @Override
        public int getItemCount() {
            return list.size();
        }
    }
    

    item_product 布局仅包含一个视图,TextView

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/text_view"
        android:textSize="25sp"/>
    

    这就是持有者类的样子:

    private class ProductViewHolder extends RecyclerView.ViewHolder {
        private View view;
    
        ProductViewHolder(View itemView) {
            super(itemView);
            view = itemView;
        }
    
        void setProductName(String productName) {
            TextView textView = view.findViewById(R.id.text_view);
            textView.setText(productName);
        }
    }
    

    现在,让我们将限制定义为全局变量并将其设置为15

    private int limit = 15;
    

    现在让我们使用这个限制来定义查询:

    FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
    CollectionReference productsRef = rootRef.collection("products");
    Query query = productsRef.orderBy("productName", Query.Direction.ASCENDING).limit(limit);
    

    以下是在您的情况下也能发挥作用的代码:

    query.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
        @Override
        public void onComplete(@NonNull Task<QuerySnapshot> task) {
            if (task.isSuccessful()) {
                for (DocumentSnapshot document : task.getResult()) {
                    ProductModel productModel = document.toObject(ProductModel.class);
                    list.add(productModel);
                }
                productAdapter.notifyDataSetChanged();
                lastVisible = task.getResult().getDocuments().get(task.getResult().size() - 1);
    
                RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
                    @Override
                    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                        super.onScrollStateChanged(recyclerView, newState);
                        if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
                            isScrolling = true;
                        }
                    }
    
                    @Override
                    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                        super.onScrolled(recyclerView, dx, dy);
    
                        LinearLayoutManager linearLayoutManager = ((LinearLayoutManager) recyclerView.getLayoutManager());
                        int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
                        int visibleItemCount = linearLayoutManager.getChildCount();
                        int totalItemCount = linearLayoutManager.getItemCount();
    
                        if (isScrolling && (firstVisibleItemPosition + visibleItemCount == totalItemCount) && !isLastItemReached) {
                            isScrolling = false;
                            Query nextQuery = productsRef.orderBy("productName", Query.Direction.ASCENDING).startAfter(lastVisible).limit(limit);
                            nextQuery.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                                @Override
                                public void onComplete(@NonNull Task<QuerySnapshot> t) {
                                    if (t.isSuccessful()) {
                                        for (DocumentSnapshot d : t.getResult()) {
                                            ProductModel productModel = d.toObject(ProductModel.class);
                                            list.add(productModel);
                                        }
                                        productAdapter.notifyDataSetChanged();
                                        lastVisible = t.getResult().getDocuments().get(t.getResult().size() - 1);
    
                                        if (t.getResult().size() < limit) {
                                            isLastItemReached = true;
                                        }
                                    }
                                }
                            });
                        }
                    }
                };
                recyclerView.addOnScrollListener(onScrollListener);
            }
        }
    });
    

    其中lastVisible 是一个DocumentSnapshot 对象,它表示查询中的最后一个可见项。在这种情况下,每 15 次,它被声明为一个全局变量:

    private DocumentSnapshot lastVisible;
    

    isScrollingisLastItemReached也是全局变量,声明为:

    private boolean isScrolling = false;
    private boolean isLastItemReached = false;
    

    如果您想实时获取数据,那么您需要使用addSnapshotListener(),而不是使用get() 调用,如有关listening to multiple documents in a collection 的官方文档中所述。更多信息您可以找到以下文章:

    【讨论】:

    • 我看到了你的帖子,但是是列表视图,我想要回收者视图和滚动。你能帮我解决这个问题吗?我编辑了我的帖子。
    • 正如我所说,记录显示在ListView 中,但这只是一个示例。您可以使用RecyclerView 实现相同的目的。你试过了吗?
    • 我已经尝试了很多例子,文档,我被困了好几天。请帮帮我。你有很多好帖子。
    • 怎么说亚历克斯,我花了 3 个小时,但我终于做到了。顺便谢谢你!
    • @kontashi35 我看不出你添加那篇文章的原因,因为它和我的回答一模一样:|
    【解决方案3】:

    FirebaseUI-Android 最近也推出了 Firestore 分页器。

    我在我的代码中使用了它,而且效果很好 - 请记住,它使用 .get() 而不是 .addSnapshotListener() 运行,因此回收器不是实时的。

    在此处查看文档:

    https://github.com/firebase/FirebaseUI-Android/tree/master/firestore#using-the-firestorepagingadapter

    【讨论】:

    • FirebaseUI-Android 组件可以快速轻松地启动和运行。如果您需要执行 Firebase 不允许的更高级查询或过滤,您可以使用 PagedListAdapter 创建自己的 DataSource,正如我在此处的回答中所述。 stackoverflow.com/questions/51859652/…
    • 我试过用这个,它可能是最快的方法。但是,如果您有 +1000 多个文档,则使用 FirebaseUI 进行分页似乎不是正确的方法。它使用 .get() 查询一次加载所有文档,然后在本地对其进行分页。知道我们怎样才能使它更有活力吗?一次只获取 10-20 条记录?
    • @SiddheshDighe 为什么你认为它会一次加载所有文档?我认为它一次只加载一页,并且您设置页面的大小。见:github.com/firebase/FirebaseUI-Android/blob/master/firestore/…
    • 他需要加载所有文件吗?因为如果他加载所有文件,它就不能正常工作......
    猜你喜欢
    • 1970-01-01
    • 2019-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-14
    • 2020-10-09
    相关资源
    最近更新 更多