ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

Infinite auto-scrolling lists with RecyclerView & LazyLists in Compose

Exploring different approaches to create infinite auto-scrolling lists on Android.

Rahul Sainani
ProAndroidDev
Published in
5 min readMay 13, 2021

--

Photo by Angely Acevedo on Unsplash
Infinite Scrolling lists with RecyclerView (left) and LazyRow (right).

RecyclerView + ListAdapter implementation

RecyclerView is a really cool and powerful tool to display list(s) of content on Android. There are tons of great posts and samples about different RecyclerView solutions so this post won’t cover that. The primary focus of this post is on creating infinite auto-scrolling lists.

Made with ❤️ using RecyclerView (The GIF has reduced frame rate)

How do we approach this problem?

One thing that comes to mind is to create a list of items that are repeated so many times, that we can almost fake it as an infinite list. Although a workable solution, it’s a bit wasteful, we can try to do better, can’t we? 🤓

Let’s get straight into the code to setup FeaturesAdapter which implements ListAdapter.

Adapter implementing ListAdapter which computes diffs between lists as it’s updated.

Why ListAdapter?

RecyclerView.Adapter base class for presenting List data in a RecyclerView, including computing diffs between Lists on a background thread. This class is a convenience wrapper around AsyncListDiffer that implements Adapter common default behavior for item access and counting.

But why does diffing matter when we just want to show the same few elements in a loop? Let’s dive into the code and see.

The function has a param for the list of features which could be provided by the ViewModel. The list is submitted to the adapter as the initial list and a coroutine is launched with a call to autoScrollFeaturesList. This is the core logic right below.

Let’s break it down, shall we?

  1. To begin with, there is a recursive function that calls itself as the RecyclerView has to scroll infinitely.
  2. RecyclerView scrolls by 5 pixels if it can scroll horizontally, that means it hasn’t reached the end of the list.
  3. If the RecyclerView cannot scroll anymore, that means it has reached the end of the list, now we split the existing list into two parts:
    - The first part starts from the first visible item in the list to the last item.
    - The second part starts from the first item of the existing list till the first visible item (not inclusive).
  4. The new list is submitted to the adapter. This is where the list adapter diffing comes in handy. The adapter figures out that the visible part of the list is the same as the first part of the new list, so there are no visual updates in the RecyclerView at that moment and now there are list items on the right.
  5. Then step 2 gets triggered again, scrolling by 5 pixels.

This is a suspend function so the coroutine will get cancelled as the scope is destroyed and we don’t have to worry about explicitly stopping it.

An approximate visual representation of step 3 and 4.

The GIF tries to explain visually the process of submitting the new list to the adapter. If it looks similar to recycling of views, it is because that was the source GIF that was edited to create this … well, let’s move on. 😄

Now with Compose

Made with ❤️ using Compose (The GIF has reduced frame rate)

LazyList is an internal implementation for displaying lists in Compose. For this post, we will use LazyRow that is perfect for our horizontal scrolling list.

Let’s write FeatureTile and FeatureList Composables.

FeatureTile–analogous to FeaturesAdapter.kt
FeatureList with auto-scrolling

What’s really happening here?

FeatureList takes in the list of features and shows them in a LazyRow. Here we take advantage of State.

… when the state of your app changes, Jetpack Compose schedules recomposition. Recomposition is running the composables that may have changed in response to state changes, and Jetpack Compose updates the composition to reflect any change. — State and Jetpack Compose

Let’s break it down like a fraction

  1. A MutableState object is initialized with the list of features provided to the FeatureList composable. So if the list is updated the LazyRow composable will recompose with the new list.
  2. items() is used to add a list of items and the last parameter is the lambda where the item content is defined.
  3. When the last item is emitted, itemsListState is updated with the new list, similar to the RecyclerView approach used above. Since itemsListState is observed by the composable, and a change to that, yes you guessed it, schedules a recomposition for LazyRow.
  4. An interesting difference between LazyLists and RecyclerView (with ListAdapter) is that the scroll state is saved with LazyLists in a way that if the list is updated, the scroll state would not change. If the scroll state is at the end of the list, on updating the list, the scroll state will still remain at the end of the list. So we need to reset the scroll state before updating the list to have the desired effect. The scroll state is reset to item with index 0 for the updated list, which is the first visible item in the current list, so we do not see any visual changes.
  5. When FeaturesList is entering the composition, the LaunchedEffect block is triggered and the initial call to the recursive function autoScroll takes place. The coroutine is cancelled when FeaturesList composable leaves the composition.
  6. To end with, autoScroll scrolls the list forward with some delay between each scroll similar to the RecyclerView approach.

Bonus: AutoScrollingLazyRow

As composables compose so well, it makes sense to create a generic implementation of AutoScrollingLazyRow that is easy to use and reuse.

A generic AutoScrollingLazyRow component 🔌

Final thoughts & tangential observations

When using LaunchedEffect with key as Unit, only LazyRow is recomposed, that makes sense and is expected behavior. However, if the key for LaunchedEffect is set to itemsListState, Features List is also recomposed. LaunchedEffect re-launches when the key changes but since nothing else in the scope of FeaturesList uses itemsListState, it was interesting that setting incorrect keys for LaunchedEffect can cause unwanted recompositions.

Infinite auto-scrolling vertical lists can also be created using the same technique. A minor caveat with the Compose variant is that user input is disabled for simplicity. This post highlights a single approach for infinite auto-scrolling lists and there might be many diverse ways to achieve this in Compose!

References

That’s all folks! Feel free to comment or message me if you have any questions, suggestions or ideas! 🙏

GitHub | LinkedIn | Twitter

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Responses (6)