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)
}
}