How to paginate data from firestore with MVVM using staggered grid recycler view

Viewed 84

I want to paginate data from Firestore, using RecyclerView when I scroll I want to fetch new documents when all items are seen

here is my implementation : what's happening in my implementation is when I switch to the fragment which contains the recycler view, it loads data in chunks with a limit of 10,10,10... but all at once even when I do not scroll it, it loads all chunks at once.

di ->

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideQueryByName() = FirebaseFirestore.getInstance()
    .collectionGroup(Constants.POSTS_COLLECTION).whereEqualTo("doc_id", 
   "jetpack")
    .orderBy(Constants.POSTS_ORDER_BY, 
    Query.Direction.DESCENDING).limit(Constants.PAGE_SIZE.toLong())
}

paging source - >

  class FirestorePagingSource (
  private val queryProductsByName: Query
           ) : PagingSource<QuerySnapshot, PostsModel>() {
  override fun getRefreshKey(state: PagingState<QuerySnapshot, 
  PostsModel>): QuerySnapshot? {
  return null
        }

override suspend fun load(params: LoadParams<QuerySnapshot>): LoadResult<QuerySnapshot, PostsModel> {
    return try {
        val currentPage = params.key ?: queryProductsByName.get().await()
        Log.e("data",currentPage.size().toString())
        val lastVisibleProduct = currentPage.documents[currentPage.size() - 1]
        val nextPage = queryProductsByName.startAfter(lastVisibleProduct).get().await()
        LoadResult.Page(
            data = currentPage.toObjects(PostsModel::class.java),
            prevKey = null,
            nextKey = nextPage
        )

    } catch (e: Exception) {
        LoadResult.Error(e)
    }

 }
}

adapter ->

class PostsAdapter : PagingDataAdapter<PostsModel, PostsAdapter.PostsViewHolder>(Companion) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostsViewHolder {
    val layoutInflater = LayoutInflater.from(parent.context)
    val dataBinding = PostsDataBinding.inflate(
        layoutInflater,
        parent,
        false
    )
    return PostsViewHolder(dataBinding)
}

override fun onBindViewHolder(holder: PostsViewHolder, position: Int) {
    val product = getItem(position) ?: return
    holder.bindProduct(product)

    val density = Resources.getSystem().displayMetrics.density

    if (position == 0) {
        holder.itemView.layoutParams.height = 420 * density.toInt()
    } else {
        holder.itemView.layoutParams.height = 450 * density.toInt()
    }
}

companion object : DiffUtil.ItemCallback<PostsModel>() {
    override fun areItemsTheSame(oldItem: PostsModel, newItem: PostsModel): Boolean {
        return oldItem.upload_date == newItem.upload_date
    }

    override fun areContentsTheSame(oldItem: PostsModel, newItem: PostsModel): Boolean {
        return oldItem == newItem
    }
}

inner class PostsViewHolder(
    private val dataBinding: PostsDataBinding
) : RecyclerView.ViewHolder(dataBinding.root) {
    fun bindProduct(product: PostsModel) {
        dataBinding.posts = product
    }
  }
 }

view model ->

 @HiltViewModel
 class PostsViewModel @Inject constructor(
private val queryPostsByName: Query
   ) : ViewModel() {
val flow = Pager(
    PagingConfig(
        pageSize = 10
    )
  ) {
    FirestorePagingSource(queryPostsByName)
 }.flow.cachedIn(viewModelScope)
}

fragment ->

   private fun setProgressBarAccordingToLoadState() {
    dataBinding.rvUserPosts.adapter = adapter
}

private fun getPosts() {
    lifecycleScope.launch {
        viewModel2.flow.collectLatest {
            adapter.submitData(it)

        }
    }
}

private fun setPostsAdapter() {
    lifecycleScope.launch {
        adapter.loadStateFlow.collectLatest {
            dataBinding.progressBar.isVisible = it.append is LoadState.Loading
        }
    }
}

recycler view item ->

  <?xml version="1.0" encoding="utf-8"?>

<data class="PostsDataBinding">
    <variable
        name="posts"
        type="com.ansh.jetpack.mvvm.data.PostsModel" />
</data>

<androidx.cardview.widget.CardView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="2dp"
    app:cardCornerRadius="8dp">



    <ImageView
        android:id="@+id/ivCover"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        app:imageUrl="@{posts.img_hd_url}"
        android:src="@color/cardview_dark_background" />


</androidx.cardview.widget.CardView>

recycler view ->

 <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/rvUserPosts"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical"
          
   app:layoutManager=
      "androidx.recyclerview.widget.StaggeredGridLayoutManager"
                app:spanCount="2"
                tools:listitem="@layout/rv_user_posts_item" />
1 Answers

I recognize this code, and most likely you're getting it from this resource:

How to implement pagination in Firestore using Jetpack Compose?

Since I wrote that article and the corresponding code, I can assure you that it doesn't get all the data in the database, it only loads a group of 10 documents, one after the other.

Why do I think you get all the data at once? It is simply because you don't have enough documents in the database.

When you first launch the app, you get 10 documents. Since the first screen can hold more than 10 elements, actually 12, another request is done. So far you got 20 documents. As mentioned in the article, the Paging v3 library contains a PagingConfig class, that has a public constructor which contains an argument called prefetchDistance which:

Prefetch distance which defines how far from the edge of loaded content access must be to trigger further loading.

What this basically means, is that the library loads another 10 documents in advance, so you don't have a laggy experience. So when launching the app for the first time, you request 30 documents, since the limit is set to 10. If you only have 30 documents in the database, yes, you're downloading them all, but if you have, for example, 100 documents, you'll see that when you reach the 20th document, another 10 documents are loaded, and so on, until you get them all.

Edit:

If you understand Java, then you can create your own pagination mechanism as explained in my answer from the following post:

Related