¿Cómo paginar Firestore con Android?

Resuelto Johans Bormman asked hace 54 años • 3 respuestas

Leí la documentación de Firestore y todos los artículos en Internet (stackoverflow) sobre la paginación de Firestore, pero no tuve suerte. Intenté implementar el código exacto en los documentos, pero no sucede nada. Tengo una base de datos básica con elementos (más de 1250 o más) y quiero obtenerlos progresivamente. Desplazándose para cargar 15 elementos (hasta el último elemento de la base de datos).

Si usa código de documentos:

// 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
        // ...
    }
});

¿Cómo hacer? La documentación no tiene demasiados detalles.

PD: necesito una vista de reciclador (no una vista de lista) cuando el usuario se desplaza. Gracias

Johans Bormman avatar Jan 01 '70 08:01 Johans Bormman
Aceptado

Como se menciona en la documentación oficial , la clave para resolver este problema es utilizar el método startAfter() . Por lo tanto, puede paginar consultas combinando cursores de consulta con el limit()método. Podrá utilizar el último documento de un lote como inicio del cursor para el siguiente lote.

Para resolver este problema de paginación, consulte mi respuesta de esta publicación , en la que expliqué paso a paso cómo puede cargar datos de una base de datos de Cloud Firestore en fragmentos más pequeños y mostrarlos con solo hacer ListViewclic en un botón.

Solución:

Para obtener los datos de su base de datos de Firestore y mostrarlos en fragmentos más pequeños en un archivo RecyclerView, siga los pasos a continuación.

Tomemos el ejemplo anterior en el que he utilizado productos. Puedes usar productos, ciudades o lo que quieras. Los principios son los mismos. Suponiendo que desea cargar más productos cuando el usuario se desplaza, usaré RecyclerView.OnScrollListener.

Primero definamos RecyclerView, configuremos el administrador de diseño LinearLayoutManagery creemos una lista. También creamos una instancia del adaptador usando la lista vacía y configuramos el adaptador en nuestro 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);

Supongamos que tenemos una estructura de base de datos similar a esta:

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

Y una clase modelo que se ve así:

public class ProductModel {
    private String productName;

    public ProductModel() {}

    public ProductModel(String productName) {this.productName = productName;}

    public String getProductName() {return productName;}
}

Así debería verse la clase de adaptador:

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();
    }
}

El item_productdiseño contiene solo una vista, un archivo TextView.

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

Y así es como debería verse la clase titular:

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);
    }
}

Ahora, definamos un límite como una variable global y establezcamoslo en 15.

private int limit = 15;

Definamos ahora la consulta usando este límite:

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

Aquí está el código que también hace la magia en tu caso:

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);
        }
    }
});

En el cual lastVisiblehay un DocumentSnapshotobjeto que representa el último elemento visible de la consulta. En este caso, cada 15 y se declara como variable global:

private DocumentSnapshot lastVisible;

Y isScrollingy isLastItemReachedtambién son variables globales y se declaran como:

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

Si desea obtener datos en tiempo real, en lugar de utilizar una get()llamada, debe utilizar addSnapshotListener()como se explica en la documentación oficial sobre la escucha de varios documentos en una colección . Más información puedes encontrar el siguiente artículo:

  • ¿Cómo crear una paginación limpia de Firestore con actualizaciones en tiempo real?
Alex Mamo avatar Jun 07 '2018 13:06 Alex Mamo

FirebaseUI-Android también lanzó recientemente un Firestore Paginator.

Lo he usado en mi código y funciona muy bien; solo tenga en cuenta que funciona usando .get() en lugar de .addSnapshotListener(), por lo que el reciclador no está en tiempo real.

Vea los documentos aquí:

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

Jeff Padgett avatar Jun 09 '2018 01:06 Jeff Padgett