How to restore recyclerview scroll position when using PagingDataAdapter?
Asked Answered
A

4

8

I have an App that fetches a list of 158 Items from an API, stores it in Room, and displays it to the user. RoomDB is the source of truth.

This is the code on my ViewModel that gets the result from the database:

private val pagingConfig =
    PagingConfig(pageSize = 20, enablePlaceholders = false, maxSize = 300)

fun getList(filters: Filters): Flow<PagingData<Character>> {
    return Pager(pagingConfig) {
        repository.getCharacters(filters)
    }.flow.map {
        it.asDomainModel()
    }
}

This is the code on my fragment that populates the adapter:

private fun fetchData(filters: Filters) {
    lifecycleScope.launch {
        charactersViewModel.getList(filters).collectLatest { pagedList ->
            characterAdapter.submitData(pagedList)
        }
    }
}

Current behaviour:

  • When a configuration change occurs after my 60th Item, the scroll position is lost, I've found out that increasing the pageSize on my pagingConfig from 20 to 55, it fixes this issue.

What I have already tried:

Since I'm fetching the data asynchronously I've tried using this piece of code from this article to prevent loading the data when adapter is empty. but it didn't work

characterAdapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY

What I expect to achieve:

Be able to scroll to the bottom of the list and do configuration changes without loosing the scroll position "without having the need to increase my pageSize as the list gets bigger"

https://github.com/doilio/DC-Characters

Adonic answered 18/7, 2020 at 9:11 Comment(0)
L
3

Don't return a new instance of Flow<PagingData> evertime from your getList method.

Do something like this:

class YourViewModel: ViewModel() {
   private mPagingData = Flow<PagingData<Character>>? = null;

   fun getList(filters: Filters): Flow<PagingData<Character>> {
      if(mPagingData != null) return mPagingData; //This is important
      else
         mPagingData = Pager(pagingConfig) {
              repository.getCharacters(filters)
         }.flow.map {
              it.asDomainModel()
         }
      return mPagingData;
   }
}

Apart from this, make sure you initialize your adapter in onCreate of your fragment.

Lepper answered 19/8, 2021 at 9:34 Comment(0)
C
1

The method you're looking for is PagingSource.getRefreshKey(). It's given the previous state, a PagingState, which has the field PagingState.anchorPosition, the last accessed index (including placeholders).

https://developer.android.com/topic/libraries/architecture/paging/v3-network-db#refresh-in-place

EDIT: I see now that you're using Room's implementation. There was actually a few bugs related to Remote REFRESH and its getRefreshKey implementation that should be resolved in the next release (alpha07). See https://issuetracker.google.com/issues/167260236

Canon answered 18/7, 2020 at 15:40 Comment(3)
Since the author uses Room, I'm wondering if PagingSource created from Room does not work as expected? (Even though in the doc it says "PagingSource implementation handles this for you")Dillydally
Ah, I didn't catch that they said there were using room in the first sentence :) There's a few bug fixes going out with alpha07 that should address this.Canon
"There's a few bug fixes going out with alpha07 that should address this." Sounds great!! Looking forward to it :)Dillydally
P
0

Had the same issue. In my situation, I changed at PagingConfig enablePlaceholders to true and set initialLoadSize to be dividable to pageSize (for example, pageSize=10, initialLoadSize = 20).

Poltroon answered 30/8, 2020 at 19:45 Comment(0)
V
0

PagingDataAdapter has a flow attribute called loadStateFlow that contains state of the processed data inside.

You can try like this:

lifecycleScope.launchWhenCreated {
        adapter.loadStateFlow.map { it.refresh }
            .distinctUntilChanged()
            .collect {
                if (it is LoadState.NotLoading) {
                    // reset to position you wanted
                }
            }
    }

LoadState.NotLoading is the specific state of loadStateFlow that corresponds to the state that processing data is finished.

LoadState.NotLoading

Vulgarian answered 28/12, 2021 at 13:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.