Home > front end >  Combine search and sort with kotlin flow
Combine search and sort with kotlin flow

Time:11-13

I need to search and sort data simultaneously. I did it for search but it wont trigger for sort. I'm also using pagination.

User can type in searchView and flow will trigger, but problem is when i change sortState (ascending or descending) it wont trigger flow for searching articles on api endpoint.

ViewModel:

  private val currentQuery = MutableStateFlow(DEFAULT_QUERY)
  private val sortState = MutableStateFlow<SortOrderState>(SortOrderState.Ascending)

  val flow = currentQuery
        .debounce(2300)
        .filter {
            it.trim().isNotEmpty()
        }
        .distinctUntilChanged()
        .flatMapLatest { query ->
            articleRepository.getSearchResult(query.lowercase(Locale.ROOT),sortState.value)
        }

Fragment:

   lifecycleScope.launch {
            viewModel.flow.collectLatest { articles ->
                binding.recyclerViewTop.layoutManager = LinearLayoutManager(context)
                binding.recyclerViewTop.adapter = adapter.withLoadStateHeaderAndFooter(
                    header = ArticleLoadStateAdapter { adapter.retry() },
                    footer = ArticleLoadStateAdapter { adapter.retry() }
                )
                adapter.submitData(articles)
            }
        }

In fragment I have function: viewModel.searchNews(newText) And in Main activity: viewModel.setSortState(SortOrderState.Ascending) (one menu item clicked) to see if MutableStateFlow.value is changed. I can see that in ViewModel i can change these values but if I do:

   val flow=currentQuery.combine(sortState){
        query,state ->
        
    }

I never changes if I click on sort menu item, only if I type something to search.

Edit: sortState is not updating in flow variable, I checked setSortState and I can clearly see that state is changed but in flow I only send ascending all the time. Main activity:

 override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.menu_sortAsc -> {
                viewModel.setSortState(SortOrderState.Ascending)
            }
            R.id.menu_sortDesc -> {
                viewModel.setSortState(SortOrderState.Descening)
            }
        }
        return super.onOptionsItemSelected(item)
    }

ViewModel:

   fun setSortState(sortOrderState: SortOrderState) {
        sortState.value = sortOrderState
    }

SortOrderState:

sealed interface SortOrderState{
    object Ascending : SortOrderState
    object Descening : SortOrderState
}

Edit 2: Collecting in HomeFragment it always gives me Ascending value even if i click on menu item for descending sort

   lifecycleScope.launch {
            viewModel.sortState.collectLatest {
                Log.d(TAG, "onCreateViewSort: $it")
            }

In ViewModel I can see sortState is changed:

   fun setSortState(sortOrderState: SortOrderState) {
           sortState.value = sortOrderState
        Log.d(TAG, "setSortState: ${sortState.value}")
    }

CodePudding user response:

You aren't using your sort state as a Flow. You're only passively using its value, so your output flow won't automatically update when the value changes.

Instead, you need to combine your flows.

Here, I also moved your lowercase transformation before the distinctUntilChanged because I think that makes more logical sense. Also, it makes sense to include the trim in the transformation and not just in the filter.

  val flow = currentQuery
        .debounce(2300)
        .map { it.trim().lowercase(Locale.ROOT) }
        .filter { it.isNotEmpty() }
        .distinctUntilChanged()
        .combine(sortState) { query, sort -> query to sort }
        .flatMapLatest { (query, sort) ->
            articleRepository.getSearchResult(query, sort)
        }

You might also consider tagging this with shareIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 1) so the search doesn't have to restart on a screen rotation.

  • Related