Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Restore PagedListAdapter position when resuming activity

I've been experimenting with PagedListAdapter and can't figure out how to restore adapters position correctly.

Last attempt was to save lastKey from current list.

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)

    val lastKey = adapter.currentList?.lastKey as Int

    outState.putInt("lastKey", lastKey)
}

but when restoring my adapter and passing lastKey to PagedListBuilder what I last saw and what is being displayed differs by quite a bit.

    val dataSourceFactory = dao.reportsDataSourceFactory()
    val builder = RxPagedListBuilder(
        dataSourceFactory,
        PagedList.Config.Builder()
            .setEnablePlaceholders(false)
            .setInitialLoadSizeHint(60)
            .setPageSize(20)
            .setPrefetchDistance(60)
            .build()
    )
        .setInitialLoadKey(initialLoadKey)
        .setBoundaryCallback(boundaryCallback)

If I'm in the middle of page #4 when resuming - adapter will be at position at the beginning of page #4. Ideally adapter should be restored in exactly the same position as last seen.

Various attempts to save LayoutManager state

outState.putParcelable("layout_manager_state", recycler_view.layoutManager.onSaveInstanceState()) 

and then restore it

recycler_view.layoutManager.onRestoreInstanceState(it.getParcelable("layout_manager_state"))

failed miserably. Any suggestions are welcome :)

like image 449
Martynas Jurkus Avatar asked Aug 03 '18 08:08

Martynas Jurkus


1 Answers

Finally managed to get it working.

Precondition - your PagedListAdapter must support null placeholders! setEnablePlaceholders(true). Read more here

    val dataSourceFactory = dao.reportsDataSourceFactory()
    val builder = RxPagedListBuilder(
        dataSourceFactory,
        PagedList.Config.Builder()
            .setEnablePlaceholders(true) //in my original implementation it was false
            .setInitialLoadSizeHint(60)
            .setPageSize(20)
            .setPrefetchDistance(60)
            .build()
    )

Save state as usual:

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)

    val lastKey = adapter.currentList?.lastKey as Int

    outState.putInt("lastKey", lastKey)
    outState.putParcelable("layout_manager_state", recycler_view.layoutManager.onSaveInstanceState())
}

but when restoring - first save state as variable and only restore saved state after submitting list to the PagedListAdapter

private fun showReports(pagedList: PagedList<Report>?) {
    adapter.submitList(pagedList)

    lastLayoutManagerState?.let {
        report_list.layoutManager.onRestoreInstanceState(lastLayoutManagerState)
        lastLayoutManagerState = null
    }
}

where lastLayoutManagerState is:

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)

    viewModel = withViewModel(viewModelFactory) {
        observe(reports, ::showReports)
    }

    report_list.adapter = adapter

    lastLayoutManagerState = savedInstanceState?.getParcelable("layout_manager_state")

    val lastKey = savedInstanceState?.getInt("lastKey")
    viewModel.getReports(lastKey)
}

Oh and when binding ViewHolder in onBindViewHolder I just bail out fast if item is null.

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    val item = getItem(position) ?: return
    ... 
}

Because it will be null as otherwise adapter item count won't match with saved state item count (guessing here) and that is why in some of my experiments layout was jumping around on pages 2+ while it worked on page 1.

If there are better ways how to approach this without manually storing and then using lastLayoutManagerState - let me know.

like image 52
Martynas Jurkus Avatar answered Nov 15 '22 22:11

Martynas Jurkus