¿Cómo paginar Firestore con Android?
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
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 ListView
clic 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 LinearLayoutManager
y 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_product
diseñ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 lastVisible
hay un DocumentSnapshot
objeto que representa el último elemento visible de la consulta. En este caso, cada 15 y se declara como variable global:
private DocumentSnapshot lastVisible;
Y isScrolling
y isLastItemReached
tambié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?
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