Restoring scroll position of nested RecyclerViews

Photo by Ali Rizvi on Unsplash

Introduction

Netflix and Spotify

If you ever tried to create an interface similar to the above image, you probably faced losing the scroll state position of the nested RecyclerViews. Let’s understand why it happens and what is a possible solution.

Scenario

Nested RecyclerViews

The straightforward implementation is: a RecyclerView using a vertical LinearLayoutManager and a ListAdapter handling 2 types, one of which is a View containing a RecyclerView using a horizontal LinearLayoutManager. This combination creates what we call nested RecyclerViews.

Problems

  1. When scrolling vertically, horizontal scroll state positions become a mess.
  2. When navigating forward and back, nested RecyclerViews lose their horizontal scroll position, scrolling to the initial position.
Vertical scroll (left) — Fragment view recreation (right)

Under the hood

First scenario: Scrolling vertically
Source of the problem: View recycling

Therefore, our view containing the nested RecyclerView is being reused and the scroll position is at the same state it was when recycled. This happens because the state position is not something we set onBindViewHolder().

Second scenario: Navigation
Source of the problem: View restoration

All Android framework-provided views have their own implementation of onSaveInstanceState() and onRestoreInstanceState(), so you don't have to manage view state within your fragment.

But in our scenario, only the main RecyclerView has its scroll state restored. This happens because each view of the hierarchy starting from the root is responsible to call the function to store and restore state. RecyclerView implementation calls onSaveInstanceState()/onRestoreInstanceState() from only its LinearLayoutManager (that holds the scroll position state) and do not make the call to its nested components.

Solution

I'm presenting a possible implementation using ListAdapter inheritance but we can adapt it using composition.

For the first problem, we need to store the state for each view that is being recycled and restore itonBindViewHolder().

For the second problem, we also need to save the state from the visible views. We track them when onBindViewHolder() is called and save the state when a new submitList() is called. This implementation also works for content refreshing.

I'll not go through the implementation details. It should be clear reading the code comments and running the sample project. Also, this solution does not cover the Fragment recreation scenario but it's viable using the same solution key points.

Conclusion

Android Developer @iFood

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store