StateFlow fetching the same data again on Back Navigation

Viewed 1598

I am working with RecyclerView and using Retrofit to fetch the data from Server. I am using Kotlin with MVVM Design Pattern. I have used LiveData it was working fine. But with Stateflow causing issues when we navigate to another Fragment and Comes back to the Same Fragment again. It just fetches the same data again. Below is the code for ViewModel and the observer:

//View Model

private val _allTimeSheetsResponse =
        MutableStateFlow<ResponsesResult<AllTimeSheetsResponse>>(ResponsesResult.Empty)
    val allTimeSheetsResponse : StateFlow<ResponsesResult<AllTimeSheetsResponse>> get() = _allTimeSheetsResponse

    fun getAllTimeSheets(auth: String) =
        viewModelScope.launch {
            timeSheetsRepository.getAllTimeSheets(auth).collect {
                _allTimeSheetsResponse.value = it
            }
        }

//Observer

lifecycleScope.launchWhenStarted{
            timeSheetsViewModel.allTimeSheetsResponse.collect { timeSheetsResponse ->
                when (timeSheetsResponse) {
                    is ResponsesResult.Loading -> binding.progressBarLayout.show()
                    is ResponsesResult.Failure -> {
                        binding.progressBarLayout.gone()
                        binding.nothingFoundLayout.show()
                        handleApiError(timeSheetsResponse)
                    }
                    is ResponsesResult.Success -> {
                        binding.progressBarLayout.gone()
                        if (timeSheetsResponse.value.payload.isNotEmpty()) {
                            showAllTimeSheetsRecyclerAdapter.submitList(timeSheetsResponse.value.payload)
                        } else {
                            binding.nothingFoundLayout.show()
                        }
                    }
                    else -> Unit
                }
            }
        }
4 Answers

Because you call getAllTimeSheets many times (eg. onCreateView or onViewCreated). Trying call it when accessing allTimeSheetsResponse` for the first time.

Your ViewModel's getAllTimeSheets() function starts a new coroutine to collect from the repo's cold Flow each time you call it, so each time the Fragment comes back, presumably. You should remove this function and simply convert the repo's cold Flow directly to a StateFlow:

val allTimeSheetsResponse: StateFlow<ResponsesResult<AllTimeSheetsResponse>> = 
    timeSheetsRepository.getAllTimeSheets(auth)
        .stateIn(viewModelScope, SharingStarted.Eagerly, ResponsesResult.Empty)

You can pass the auth parameter into the ViewModel's factory through to its constructor.

When you are using the navigationComponent and call navController.navigate() to open a fragment, in the background destination fragment replaces with the old destination's fragment. so old fragment will keep in the fragmentManager backStack. but its view will destroy. and when navigate back, old fragment comes from backStack (not created again) and just its view creates.

So it's better to call getAllTimeSheets() in Fragment's onCreate. (to call one time). When fetching done, all data will set in _allTimeSheetsResponse

And then you should observe allTimeSheetsResponse in onViewCreated with viewLifecycleOwner scope.

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    viewModel.allTimeSheetsResponse.onEach { response ->

        // do sth with response

    }.launchIn(viewLifecycleOwner.lifecycleScope)
}

    fun getAllTimeSheets(auth: String) :StateFlow<ResponsesResult<AllTimeSheetsResponse>> {
        var mutableStateFlow = MutableStateFlow<ResponsesResult<AllTimeSheetsResponse>>(ResponsesResult.Empty)
        viewModelScope.launch {
            timeSheetsRepository.getAllTimeSheets(auth).collect {
                mutableStateFlow.value = it
            }
        }

        return mutableStateFlow
    }

Related