Posted on • Originally published ateuantorano.co.uk on
Searching Paged Data In Jetpack Compose
I've recently created a new Android app using theJetpack Compose UI App Development Toolkit which consisted of a list of items which needed to be searchable. As the list was quite long, and the API I was talking to implemented support for pagination, I made use of thePaging library to lazy load items into the list as it scrolled.
Nice and easy so far. Now came the time to implement searching for items. I wanted to be able to toggle a search box at the top of the page and have the list of items filter itself based upon the search term as the user typed. After a little trial and error and a good while searching through Google's documentation given how rarely I work with Android, I came up with a nice and simple solution.
The end result that we're aiming for is something like this:
My ViewModel layers all useKotlin Flows to store state, meaning my basic view model for the paged data looked something like the following:
@HiltViewModelclassProductListViewModel@Injectconstructor(privatevalrepository:Repository):ViewModel(){valproducts=Pager(PagingConfig(pageSize=10,enablePlaceholders=false,)){ProductPagingSource(repository=repository,search=query,)}.flow.cachedIn(viewModelScope)}
(Note, I useHilt to inject dependencies into my view models, but that's not important for this post)
To add searching to this, I needed to add a few new properties and a couple of functions:
- A boolean to toggle whether the search box is visible.
- A string to contain the current search term.
- Functions to toggle the search box visibility, and to set the search term.
My view model now looked like this:
@HiltViewModelclassProductListViewModel@Injectconstructor(privatevalrepository:Repository):ViewModel(){privateval_search=MutableStateFlow("")valsearch=_search.asStateFlow().stateIn(scope=viewModelScope,started=SharingStarted.WhileSubscribed(),initialValue="",)privateval_isSearchShowing=MutableStateFlow(false)valisSearchShowing=_isSearchShowing.asStateFlow().stateIn(scope=viewModelScope,started=SharingStarted.WhileSubscribed(),initialValue=false,)valproducts=Pager(PagingConfig(pageSize=10,enablePlaceholders=false,)){ProductPagingSource(repository=repository,search=query,)}.flow.cachedIn(viewModelScope)funsetSearch(query:String){_search.value=query}funtoggleIsSearchShowing(){_isSearchShowing.value=!_isSearchShowing.value}}
Now we just need to update the paged data when the search term changes. Luckily, this is extremely easy when working with flows as we can use a flow transformation (namelyflatMapLatest
) so that when the search term changes a new flow is emitted:
@HiltViewModelclassProductListViewModel@Injectconstructor(privatevalrepository:Repository):ViewModel(){privateval_search=MutableStateFlow("")valsearch=_search.asStateFlow().stateIn(scope=viewModelScope,started=SharingStarted.WhileSubscribed(),initialValue="",)privateval_isSearchShowing=MutableStateFlow(false)valisSearchShowing=_isSearchShowing.asStateFlow().stateIn(scope=viewModelScope,started=SharingStarted.WhileSubscribed(),initialValue=false,)valproductsSearchResults=search.debounce(300.milliseconds).flatMapLatest{query->Pager(PagingConfig(pageSize=10,enablePlaceholders=false,)){ProductPagingSource(repository=repository,search=query,)}.flow.cachedIn(viewModelScope)}funsetSearch(query:String){_search.value=query}funtoggleIsSearchShowing(){_isSearchShowing.value=!_isSearchShowing.value}}
We also apply thedebounce
transformation so that as the user is typing we don't constantly refresh the paging data. In this case, I set the debounce interval to 300 milliseconds.
All that's left to do is to hook the view model up to a view - an exercise left to the reader, though you cansee my implementation here.
A full sample project isavailable on GitHub, which ties this approach together with dummy product data coming from a web request.
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse