Update Current Page or Update Data in Paging 3 library Android Kotlin
Asked Answered
M

3

12

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.

Millwright answered 6/11, 2020 at 13:57 Comment(0)
B
4

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() }
    }
}
Ballistic answered 7/11, 2020 at 9:35 Comment(12)
Hi @Ballistic thanks for response. Can you please explain me in detail how to track of all PagingSource. I am new in Paging library. ThanksMillwright
In the PagingSourceFactory you pass to Pager, you'll need something that keeps a list of refs to the PagingSources 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: issuetracker.google.com/160716447Ballistic
Hi @Ballistic thanks for reply. Can i get more clear working example.Millwright
Did you click the link?Ballistic
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?Ballistic
@Ballistic I saw your commit about InvalidatingPagingSourceFactory. How can I access this InvalidatingPagingSourceFactory and surface it from a PagingDataAdapter or ViewModel ?Photogravure
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.Ballistic
@Ballistic At the moment my ViewModel only has access to the Pager's flow(), which returns a Flow<PagingData<Value>>, 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 ?Photogravure
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?Ballistic
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.Ballistic
@Ballistic 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. ThanksMillwright
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: cs.android.com/androidx/platform/frameworks/support/+/…Homorganic
C
1

To use the invalidate() method we need to wrap it in the InvalidatingPagingSourceFactory.

  private val pagingSourceInvalidatingFactory = InvalidatingPagingSourceFactory {
        PagingSource(
            repository = exampleRepository
        )
    }

val pager: Flow<PagingData<ExampleModel>> by lazy {
    Pager(
        config = PagingConfig(pageSize = PAGE_SIZE),
        pagingSourceFactory = pagingSourceInvalidatingFactory
    ).flow
}

And then you can freely use invalidate() on the pagingSourceInvalidatingFactory

Cryo answered 21/11, 2023 at 15:10 Comment(0)
N
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
    }
Nebiim answered 5/1, 2023 at 7:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.