10

I am new in Paging 3 library in android kotlin. I want unlimited data. So I found Paging 3 library is helpful in my case. I used PagingSource to create a list. I am not using Room. I have nested recyclerView. I am using PagingDataAdapter with diff util for my Recyclerview. I used the recommended tutorial for Paging library from codelab and I succeeded without any problem. I am facing difficult to update the item. I used paging source to create list and inside list i have some data which are coming from sever. I completely all this without any problem. But how to update adapter or notify data has changed in reyclerview. I already mechanism to fetch updated list. I searched how to update the adapter in some place but every where is mention to use invalidate() from DataSource. DataSource is used in paging 2 right?. Now this is inside the Paging 3 as per Documentation in Migrate to Paging 3. I used Flow to retrieve data. This code is inside viewmodel class.

fun createRepo(data: List<String>, repository: RepositoryData): Flow<PagingData<UnlimitData>> {
    return repository.getStreamData(data).cachedIn(viewModelScope)
}

I am passing list, which is coming from sever. getStreamData function return the items with int and string data. My Data class

data class UnlimitData(val id: Int, val name: String)

createRepo is calling in my activity class to send data in adpater.

lifecycleScope.launch {
        viewModel.createRepo(serverData,repository).collectLatest { data ->
            adapter.submitData(data)
        }
}

This is my Adapter code:-

class unlimitedAdapter() :
    PagingDataAdapter<UnlimitData, RecyclerView.ViewHolder>(COMPARATOR) {

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    val item = getItem(position)
    if (item != null) {
        (holder as UnlimitedViewHolder).bind(item)
    }
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    return UnlimitedViewHolder.create(parent)
}

companion object {
    private val COMPARATOR = object : DiffUtil.ItemCallback<UnlimitData>() {
        override fun areItemsTheSame(oldItem: UnlimitData, newItem: UnlimitData): Boolean =
            oldItem.id == newItem.id

        override fun areContentsTheSame(oldItem: UnlimitData, newItem: UnlimitData): Boolean = oldItem == newItem
    }
}

}

I added logic to insert/Update data in list using RetroFit. My list is updated successfully, but i am unable to refresh reyclerview.

Thanks in advance.

Kotlin Learner
  • 3,995
  • 6
  • 47
  • 127

2 Answers2

3

In order for paging to pick up new items, you will need to call PagingSource.invalidate() to inform Pager that it needs to fetch a new PagingSource and reload pages. You'll want to keep track of all the PagingSources your factory produces and invalidate them anytime you update the backing dataset from network.

EDIT: Something like this, but this is a very rough prototype

abstract class InvalidatingPagingSourceFactory<K,V> : () -> PagingSource<K,V> {
    private val list = mutableListOf()

    abstract fun create()

    override final fun invoke() {
        create().also { list.add(it) }
    }

    fun invalidate() {
        while (list.isNotEmpty()) { list.removeFirst().invalidate() }
    }
}
dlam
  • 3,547
  • 17
  • 20
  • Hi @dlam thanks for response. Can you please explain me in detail how to track of all PagingSource. I am new in Paging library. Thanks – Kotlin Learner Nov 08 '20 at 14:27
  • In the `PagingSourceFactory` you pass to `Pager`, you'll need something that keeps a list of refs to the `PagingSource`s you create, and have some kind of callback that tells you when your backing dataset changes and invalidates + clears that list. Some sample code in this bug might help you get started: https://issuetracker.google.com/160716447 – dlam Nov 10 '20 at 20:50
  • Hi @dlam thanks for reply. Can i get more clear working example. – Kotlin Learner Nov 11 '20 at 12:49
  • Did you click the link? – dlam Nov 12 '20 at 16:40
  • Is there anything specific I could help expand on? You essentially need to call `.invalidate()` (which is also a pagingsource API) to get Paging to create a new `PagingSource` using the Factory you provided to reload data. What have you tried and what doesn't work? – dlam Aug 09 '21 at 18:32
  • @dlam I saw your commit about InvalidatingPagingSourceFactory. How can I access this InvalidatingPagingSourceFactory and surface it from a PagingDataAdapter or ViewModel ? – J. Doe Aug 19 '21 at 10:42
  • `InvalidatingPagingSourceFactory` is just a helper class which tracks all the `PagingSource` it creates, so you can invalidate with a single function call instead of tracking them yourself. You would pass it into `Pager()` which requires a `() -> PagingSource` for the `pagingSourceFactory`. – dlam Aug 20 '21 at 20:00
  • 1
    @dlam At the moment my ViewModel only has access to the `Pager`'s `flow()`, which returns a `Flow>`, so at that point the access to the `Pager` is already lost. Does that mean that I have to save the `Pager` somewhere in the ViewModel in order to use it? It wouldn't be very convenient as in order to achieve that, the ViewModel, which was agnostic of the data source before, will now need to save one Pager for every data source (local and remote). Do you have a concrete sample project to illustrate the use of `InvalidatingPagingSourceFactory` ? – J. Doe Aug 23 '21 at 12:04
  • You don't need to pass the `Pager` around, you just need to pass `InvalidatingPagingSourceFactory` in `Pager` and keep a reference of `InvalidatingPagingSourceFactory` around. What you want to do, is to hook up `InvalidatingPagingSourceFactory.invalidate()` to your source of truth. So it depends on how you intend to listen to that. What layer is your `Pager` defined in, and where do you expect external invalidation signals to come from? – dlam Aug 25 '21 at 21:19
  • Wherever you declare your `Pager`, you should have access to the repository layer because you need to give `Pager` the repository abstraction, `PagingSource`, which should also give you access to your external signal used to trigger invalidate. – dlam Aug 25 '21 at 21:21
  • 3
    @dlam can you please give us working example how invalidate works. To be honest it's very difficult to understand for beginner how to learn and read from library. Thanks – Kotlin Learner Oct 31 '21 at 22:27
  • `PagingSource` has a method called `invalidate`. Calling this method, calls the `pagingSourceFactory` lambda function in `Pager` class, thus creating a new instance of `PagingSource` that reflects new data from your source. Programmatically, The PagingSource abstract class has an instance of `InvalidateCallbackTracker` which handles this invalidation. For more info: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:paging/paging-common/src/main/kotlin/androidx/paging/PagingSource.kt?q=file:androidx%2Fpaging%2FPagingSource.kt%20class:androidx.paging.PagingSource – Nandha Kumar Apr 12 '23 at 09:48
0

When We use adapter.refresh() method it will refresh the latest data and bind with the layout.

But It is not reLoading the recycler view with Position. In that case, you will get the wrong position.

Because in DiffUtils you match correctly. So, whenever you refresh, it will bind only that new and not bonded data.

So, The solution is you have to make false the DiffUtils. In that case, PageSource will update the full list and the position will be updated.

Like : oldItem.id ==-1

 private val COMPARATOR = object : DiffUtil.ItemCallback<UnlimitData>() {
        override fun areItemsTheSame(oldItem: UnlimitData, newItem: UnlimitData): Boolean =
            oldItem.id ==-1

        override fun areContentsTheSame(oldItem: UnlimitData, newItem: UnlimitData): Boolean = oldItem == newItem
    }
Parth Gupta
  • 101
  • 1
  • 4