0

Is there a way to update (automatically) the RecyclerView when a list is populated with data?

I created a simple app (here is the repository for the app). In HomeFragment there is a RecyclerView and a button to refresh the data.

The app works fine as long as I have the following code in HomeFragment to update the adapter whenever the StateFlow list gets data.

private fun setupObservers() {
    lifecycleScope.launchWhenStarted {
        vm.state.collect() {
            if (it.list.isNotEmpty()) {
                todoAdapter.data = it.list
            } else {
                todoAdapter.data = emptyList()
            }
        }
    }
}

My question is, is there a away for the RecyclerView to update, without having to observe (or collect) the changes of the list of the StateFlow?

Dimitris
  • 725
  • 1
  • 10
  • 28

1 Answers1

0

Something has to notify the RecyclerView adapter when the data has changed. Either you do it in a collector/observer, or you have to proactively do it in every place in your code where you do something that might affect the data. So, it is much easier and less error-prone to do it by collecting.

Side note, the if/else in your code doesn't accomplish anything useful. No reason to treat an empty list differently if you still end up passing an empty list to the adapter.

It's more correct to use repeatOnLifecycle (or flowWithLifecycle) than launchWhenStarted. See here.

private fun setupObservers() {
    vm.state.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
        .onEach { todoAdapter.data = it.list }
        .launchIn(viewLifecycleOwner.lifecycleScope)
}

I personally like to use an extension function like this to make it more concise wherever I'm collecting flows:

fun <T> Flow<T>.launchAndCollectWithLifecycle(
    lifecycleOwner: LifecycleOwner, 
    state: Lifecycle.State = Lifecycle.State.STARTED, 
    action: suspend (T) -> Unit
) = flowWithLifecycle(lifecycleOwner.lifecycle, state)
        .onEach(action)
        .launchIn(lifecycleOwner.lifecycleScope)

Then your code would become:

private fun setupObservers() {
    vm.state.launchAndCollectWithLifecycle(viewLifecycleOwner) {
        todoAdapter.data = it.list
    }
}
Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • I am under the impression that the RecyclerView can observe changes to the StateFlow which emits them. When the StateFlow holds an element as a String then changes will be emitted to the ui elements without having to observe (or collect) in the fragment. That’s the reason that I’m asking if I’m missing something – Dimitris Jan 24 '23 at 14:23
  • That's an incorrect impression. RecyclerView is a pure Java class with no dependency or knowledge of Kotlin Flows or Jetpack LiveData. It cannot observe anything. More specifically, a RecyclerView.Adapter cannot observe anything unless you add properties to it to pass it a lifecycle. It's up to you to pass data to it. – Tenfour04 Jan 24 '23 at 14:30
  • Maybe you're thinking of [Data Binding](https://developer.android.com/topic/libraries/data-binding)? It can allow you to set up your observers in layout XML files instead of in your Kotlin code. I personally don't use it because I think it just moves a problem from one place to another and I'd rather have my logic in my Kotlin code rather than in XML Strings. – Tenfour04 Jan 24 '23 at 14:32