Paging Library Filter/Search Paging Library Filter/Search android android

Paging Library Filter/Search


You can solve this with a MediatorLiveData.

Specifically Transformations.switchMap.

// original code, improved laterpublic void reloadTasks() {    if(liveResults != null) {        liveResults.removeObserver(this);    }    liveResults = getFilteredResults();    liveResults.observeForever(this);}

But if you think about it, you should be able to solve this without use of observeForever, especially if we consider that switchMap is also doing something similar.

So what we need is a LiveData<SelectedOption> that is switch-mapped to the LiveData<PagedList<T>> that we need.

private final MutableLiveData<String> filterText = savedStateHandle.getLiveData("filterText")private final LiveData<List<T>> data;public MyViewModel() {    data = Transformations.switchMap(            filterText,            (input) -> {                 if(input == null || input.equals("")) {                     return repository.getData();                 } else {                     return repository.getFilteredData(input); }                }            });  }  public LiveData<List<T>> getData() {      return data;  }

This way the actual changes from one to another are handled by a MediatorLiveData.


I have used an approach similar to as answered by EpicPandaForce. While it is working, this subscribing/unsubscribing seems tedious. I have started using another DB than Room, so I needed to create my own DataSource.Factory anyway. Apparently it is possible to invalidate a current DataSource and DataSource.Factory creates a new DataSource, that is where I use the search parameter.

My DataSource.Factory:

class SweetSearchDataSourceFactory(private val box: Box<SweetDb>) :DataSource.Factory<Int, SweetUi>() {var query = ""override fun create(): DataSource<Int, SweetUi> {    val lazyList = box.query().contains(SweetDb_.name, query).build().findLazyCached()    return SweetSearchDataSource(lazyList).map { SweetUi(it) }}fun search(text: String) {    query = text}}

I am using ObjectBox here, but you can just return your room DAO query on create (I guess as it already is a DataSourceFactory, call its own create).

I did not test it, but this might work:

class SweetSearchDataSourceFactory(private val dao: SweetsDao) :DataSource.Factory<Int, SweetUi>() {var query = ""override fun create(): DataSource<Int, SweetUi> {    return dao.searchSweets(query).map { SweetUi(it) }.create()}fun search(text: String) {    query = text}}

Of course one can just pass a Factory already with the query from dao.

ViewModel:

class SweetsSearchListViewModel@Inject constructor(private val dataSourceFactory: SweetSearchDataSourceFactory) : BaseViewModel() {companion object {    private const val INITIAL_LOAD_KEY = 0    private const val PAGE_SIZE = 10    private const val PREFETCH_DISTANCE = 20}lateinit var sweets: LiveData<PagedList<SweetUi>>init {    val config = PagedList.Config.Builder()        .setPageSize(PAGE_SIZE)        .setPrefetchDistance(PREFETCH_DISTANCE)        .setEnablePlaceholders(true)        .build()    sweets = LivePagedListBuilder(dataSourceFactory, config).build()}fun searchSweets(text: String) {    dataSourceFactory.search(text)    sweets.value?.dataSource?.invalidate()}}

However the search query is received, just call searchSweets on ViewModel. It sets search query in the Factory, then invalidates the DataSource. In turn, create is called in the Factory and new instance of DataSource is created with new query and passed to existing LiveData under the hood..


You can go with other answers above, but here is another way to do that: You can make the Factory to produce a different DataSource based on your demand. This is how it's done:In your DataSource.Factory class, provide setters for parameters needed to initialize the YourDataSource

private String searchText;...public void setSearchText(String newSearchText){    this.searchText = newSearchText;}@NonNull@Overridepublic DataSource<Integer, SearchItem> create() {    YourDataSource dataSource = new YourDataSource(searchText); //create DataSource with parameter you provided    return dataSource;}

When users input new search text, let your ViewModel class to set the new search text and then call invalidated on the DataSource. In your Activity/Fragment:

yourViewModel.setNewSearchText(searchText); //set new text when user searchs for a text

In your ViewModel, define that method to update the Factory class's searchText:

public void setNewSearchText(String newText){   //you have to call this statement to update the searchText in yourDataSourceFactory first   yourDataSourceFactory.setSearchText(newText);   searchPagedList.getValue().getDataSource().invalidate(); //notify yourDataSourceFactory to create new DataSource for searchPagedList}

When DataSource is invalidated, DataSource.Factory will call its create() method to create newly DataSource with the newText value you have set. Results will be the same