0

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" />
Alex Mamo
  • 130,605
  • 17
  • 163
  • 193

1 Answers1

1

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:

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • that collection is having around 65+ doccuments ! not 30 , in the logcat I can clearly see it is loading like 10,10,10,10,10,10,0 .. all at once , is that loading machanism of 15 at once same for the staggered grid with 2 span count ? – Himanshu Yadav Anshu Sep 12 '22 at 16:42
  • As I see you're using a collectionGroup, so it's about multiple collections rather than a single one. Maybe all those collections dosesn't contain as much data as you say, or you're having an issue with the adapter? Have you tried to use the solution in the repo using Jetpack Compose? – Alex Mamo Sep 12 '22 at 16:52
  • yes you are right , its coming from collection group query and some of them not even having 10 documents , so how can we handle this ? like if one collection is having 1000 docs and another have only 5 docs then it will misbehave ? i never tried jetpack compose , i am doing android since 1 year – Himanshu Yadav Anshu Sep 12 '22 at 17:01
  • i was using MVC pattern with View model and live data , but i failed to implement pagination , so i learned MVVM , again failed , now i need to learn Jetpack compose i guess – Himanshu Yadav Anshu Sep 12 '22 at 17:28
  • Ok, give it a try and tell me if it loads the data in chunks. – Alex Mamo Sep 12 '22 at 18:13
  • Have you tried it? Does it behave the way you want? – Alex Mamo Sep 13 '22 at 05:01
  • not yet ! I am looking for solution without compose , as m not getting time to switch to compose ! – Himanshu Yadav Anshu Sep 13 '22 at 06:28
  • You say, it is loading like 10,10,10,10,10,10. Are those different documents or there are always the same? – Alex Mamo Sep 13 '22 at 07:40
  • all are same type of documents , containing image url , i think there may be a problem in adapter , like it really dont know when to load the next page , how its getting the last visible item even i do not scroll , how should i check from where its getting the command to load the next page ? , because https://proandroiddev.com/android-firestore-pagination-flow-ce676046dc44 in this its getting the last visible position , but i am not gettin where shoud i write the flow part – Himanshu Yadav Anshu Sep 13 '22 at 07:56
  • I was asking if the 10 documents are the same as the following 10, and the following 10, and so on, or are they different documents? The paging 3 library does that for you. There is no need to do anything. If you want to create your own pagination mechanism, then please check the URL in my updated answer. – Alex Mamo Sep 13 '22 at 08:02
  • its loads 10,10,10,10,10....5,0 – Himanshu Yadav Anshu Sep 13 '22 at 08:54
  • So you all 55 documents in one go, this is what is happening? – Alex Mamo Sep 13 '22 at 09:27
  • Yes , i think it loads by limit and then if it is less then the limit its loads the remaining i.e 5 in this case , all chunks in 3 seconds or less – Himanshu Yadav Anshu Sep 13 '22 at 09:43
  • If you change the limit to 15, for example, are you getting 15,15,15,10? – Alex Mamo Sep 13 '22 at 10:21
  • Yes , i tried changing the page size and limit , i am getting like this as you mentioned – Himanshu Yadav Anshu Sep 13 '22 at 12:06
  • How many times does `.collectLatest` and `submitData(it)` fire? For each 10 document group? – Alex Mamo Sep 13 '22 at 13:17
  • adapter.loadStateFlow.collectLatest { Log.e("data","collect latest") } this method is getting fired 2 times for the limit , 3 times if less then the limit , 1 time if there is no docs – Himanshu Yadav Anshu Sep 13 '22 at 14:41
  • So getting the data, works, right? The problem is when you display it. – Alex Mamo Sep 13 '22 at 14:42
  • no , getting data is not workin i guess , co i cant see the log , for viewModel2.flow.collectLatest { adapter.submitData(it) Log.e("data","Submit data") } – Himanshu Yadav Anshu Sep 13 '22 at 14:43