Gil Ong
Gil Ong

Reputation: 87

Using StateFlow To Update List Adapter

I am trying to switch from LiveData to StateFlow in populating my ListAdapter.

I currently have a MutableLiveData<List<CustomClass>> that I am observing to update the list adapter as such:

  viewModel.mutableLiveDataList.observe(viewLifecycleOwner, Observer {
         networkIngredientAdapter.submitList(it)
}

This works fine. Now I am replacing the MutableLiveData<List<CustomClass>?> with MutableStateFlow<List<CustomClass>?> in the viewModel as such:

 private val _networkResultStateFlow = MutableStateFlow<List<IngredientDataClass>?>(null)
    val networkResultStateFlow : StateFlow<List<IngredientDataClass>?>
    get() = _networkResultStateFlow

 fun loadCustomClassListByNetwork() {
            viewModelScope.launch {
//a network request using Retrofit
 val result = myApi.myService.getItems()
 _networkResultStateFlow.value = result
}
}

I am collecting the new list in a fragment as such:


 lifecycleScope.launchWhenStarted {
 viewModel.networkResultStateFlow.collect(){
                list -> networkIngredientAdapter.submitList(list)}
}

However, the list Adapter does not update when I call loadCustomClassListByNetwork(). Why am I not able to collect the value

Upvotes: 0

Views: 2122

Answers (2)

Gil Ong
Gil Ong

Reputation: 87

I haven't mentioned in the original question but there are two collect calls being made in the created coroutine scope as such:

lifecycleScope.launchWhenStarted {

       viewModel.networkResultStateFlow.collect(){
                list -> networkIngredientAdapter.submitList(list)}

       viewModel.listOfSavedIngredients.collectLatest(){
                  list -> localIngredientAdapter.submitList(list)}
}

Previously only the first collect call was working so that only one list was updating. So I just created two separate coroutine scopes as such and it now works:

lifecycleScope.launchWhenStarted {

       viewModel.networkResultStateFlow.collect(){
                list -> networkIngredientAdapter.submitList(list)}
}

lifecycleScope.launchWhenStarted {

       viewModel.listOfSavedIngredients.collectLatest(){
                  list -> localIngredientAdapter.submitList(list)}
}

Note: Using launch, launchWhenStarted or launchWhenCreated yielded the same results.

I'll edit my response once I figure out the reason for needing separate scopes for each call to collect.

EDIT: So the reason only one listAdapter was updating was because I needed a separate CoroutineScope for each of my StateFlow since Flows by definition run on coroutines. Each flow uses its respective coroutine scope to collect its own value and so you cannot have flows share the same coroutine scope b/c then they would be redundantly collecting the same value. The answer provided by @ruby6221 also creates a new coroutine scope and so likely works but I cannot test it due to an unrelated issue with upgrading my SDK version, otherwise I would set it as the correct answer.

Upvotes: 0

ruby6221
ruby6221

Reputation: 286

Try to replace code in fragment with below:

lifecycleScope.launch {
viewModel.networkResultStateFlow.flowWithLifecycle(lifecycle)
         .collect { }
}

Upvotes: 1

Related Questions