Home > Software design >  No response from Api when collected StateFlow does not change kotlin
No response from Api when collected StateFlow does not change kotlin

Time:12-05

In my fragment I have a RecyclerView, which displays results from the query I enter in options menu. It is an API from which I receive TV-shows list.

The query needs string with a len of 3 at least. When it's 1 or 2 the adapter is cleared.

override fun onQueryTextChange(newText: String?): Boolean {
if (newText != null && newText.length > 2) {
 if (!newText.isNullOrBlank() && newText.length > 2)
 viewModel.searchMovies(newText)
 } 
else {
adapter.setMoviesList(emptyList())
     }
return true
}

However, I encountered an issue after entering e.g. "cat" twice. I received a list of shows having cat in it. After removing query from optionmenu and taping it again the adapter was empty. And there was no same search. For me -> because the flow value didn't change.

In ViewModel I have:

    private val _moviesStateFlow = MutableStateFlow<List<TvMazeShowResponse>>(emptyList())
    val moviesStateFlow = _moviesStateFlow as StateFlow<List<TvMazeShowResponse>>

    fun searchMovies(query: String) {
        viewModelScope.launch {
            val response = api.getApiResponse(query)
            _moviesStateFlow.emit(response)
        }
    }

And this StateFlow I collect in fragment.

        lifecycleScope.launch {
            viewModel.moviesStateFlow.collect {
                adapter.setMoviesList(it)
            }
        }

To fix the problem I added another function in VM

    fun clearFlow() {
        viewModelScope.launch {
            _moviesStateFlow.emit(emptyList())
        }
    }

And now in the fragment in onQueryTextChange in else I added.

else {
adapter.setMoviesList(emptyList())
viewModel.clearFlow()
}

Now it works as expected. But is there a better way to achieve that?

CodePudding user response:

To make your code less convoluted, avoid doing logic in your UI classes (Fragment/Activity/Adapter) and make your ViewModel provide the single source of truth.

override fun onQueryTextChange(newText: String?): Boolean {
    viewModel.searchMovies(newText.orEmpty())
    return true
}

// In ViewModel
fun searchMovies(query: String) {
    val trimmedQuery = query.trim()
    viewModelScope.launch {
        val response = if (trimmedQuery.length <= 2) emptyList() else api.getApiResponse(trimmedQuery)
        _moviesStateFlow.emit(response)
    }
}

To avoid running multiple obsolete queries if the user is typing quickly, I suggest cancelling previous searches when starting new ones.

private val searchJob? = null

fun searchMovies(query: String) {
    val trimmedQuery = query.trim()
    searchJob?.cancel()
    searchJob = viewModelScope.launch {
        val response = if (trimmedQuery.length <= 2) emptyList() else api.getApiResponse(trimmedQuery)
        _moviesStateFlow.emit(response)
    }
}
  • Related