I'm using the new ConcatAdapter & the Android navigation component to create a complex recyclerView which contains nested horizontal scrollable lists. I'm trying to restore the state of these nested lists on rotation, navigation, etc. I've read this article on the topic, but I'm struggling on how to restore the state in this case.
The adapter.stateRestorationPolicy = PREVENT_WHEN_EMPTY
in the article above, doesn't work when I apply it to my adapter which contains the nested recyclerview. I've tried updating the nested adapters when I receive the data and I've tried adding them to the concat adapter on data receival, but with no succes.
My Fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val concatAdapter = ConcatAdapter()
binding.recyclerView.apply {
adapter = concatAdapter
layoutManager = LinearLayoutManager(requireContext())
}
viewModel.data.observe(viewLifecycleOwner, { listItems ->
concatAdapter.addAdapter(NestedListAdapter(listItems))
})
}
Also tried this:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val nestedListAdapter = NestedListAdapter()
val concatAdapter = ConcatAdapter(nestedListAdapter)
binding.recyclerView.apply {
adapter = concatAdapter
layoutManager = LinearLayoutManager(requireContext())
}
viewModel.movies.observe(viewLifecycleOwner, { items ->
nestedListAdapter.clearItems()
nestedListAdapter.addItems(items)
nestedListAdapter.notifyItemRangeChanged(0, items.size)
})
}
I've also tried to delay binding the adapter to the outer recyclerview:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
lateinit var concatAdapter: ConcatAdapter()
binding.recyclerView.apply {
layoutManager = LinearLayoutManager(requireContext())
}
viewModel.movies.observe(viewLifecycleOwner, { items ->
concatAdapter = ConcatAdapter(MovieHorizontalListAdapter(it.data.toMutableList()))
if (binding.recyclerView.adapter != concatAdapter) {
binding.recyclerView.adapter = concatAdapter
}
})
}
My nested Recyclerview Adapter
class NestedListAdapter(
private val listItems: MutableList<Movie> = mutableListOf()
) : RecyclerView.Adapter<NestedListAdapter.ViewHolder>() {
private val scrollStates: MutableMap<String, Parcelable?> = mutableMapOf()
private val viewPool = RecyclerView.RecycledViewPool()
init {
// this does not do anything, (it's also added in the MovieListAdapter)
stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
}
private fun getSectionID(position: Int): String {
return listItems.getOrNull(position)?.id?.toString().orEmpty()
}
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
val key = getSectionID(holder.layoutPosition)
scrollStates[key] = holder.rv.layoutManager?.onSaveInstanceState()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = NestedListAdapterBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return ViewHolder(binding)
}
override fun getItemCount(): Int = if (listItems.isEmpty()) 0 else 1
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(listItems)
}
inner class ViewHolder(
binding: NestedListAdapterBinding
) : RecyclerView.ViewHolder(binding.root) {
val rv = binding.recyclerView
fun bind(listItems: List<Movie>) {
val lm = LinearLayoutManager(rv.context, HORIZONTAL, false)
lm.initialPrefetchItemCount = 4
rv.apply {
setRecycledViewPool(viewPool)
layoutManager = lm
adapter = MovieListAdapter(listItems)
}
restoreState()
}
// This is to restore the state when recycling items (not rotation)
private fun restoreState() {
val key = getSectionID(layoutPosition)
val state = scrollStates[key]
if (state != null) {
rv.layoutManager?.onRestoreInstanceState(state)
} else {
val padding = rv.resources.getDimension(R.dimen.default_padding).toInt()
rv.addItemDecoration(HorizontalListMarginDecoration(padding))
rv.layoutManager?.scrollToPosition(0)
}
}
}
Any help is appreciated.