分页库过滤/搜索

40

问题的答案在这里:https://dev59.com/EVUK5IYBdhLWcg3w4TKN#56394003使用switchMap和MutableLiveData - Momen Zaqout
3个回答

42

你可以使用 MediatorLiveData 来解决这个问题。

具体来说,需要使用 Transformations.switchMap

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

但是如果你仔细想想,应该能够不使用 observeForever 来解决这个问题,特别是考虑到 switchMap 做了类似的事情。

因此,我们需要一个被 switch-mapped 到所需的 LiveData<PagedList<T>>LiveData<SelectedOption>

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

通过MediatorLiveData实现从一个数据源到另一个数据源的实际更改。


你救了我的一天。 - user3292244
1
这个已经起作用了,但是在我的RecyclerView上我会得到一个随机的java.lang.IndexOutOfBoundsException: Inconsistency detected. - abalta
我正在以同样的方式进行,但是连续插入存在问题。每当数据库中插入新项目时,我的列表会被通知并且观察者会观察到排序后的列表,如果在顶部插入任何项目,则列表会闪烁。如果插入在底部,列表视图就没问题,但是如果插入在顶部,则列表会闪烁。 - Gurvinder Singh
我认为如果这些项目是相同的且相等,你就不应该这样做。 - EpicPandaForce
1
@mallaudin 将筛选器参数通过构造函数参数移入数据源,然后如果需要更改它,请使数据源无效。在调用数据源的无效方法之前,请确保您更新了数据源工厂中的筛选器文本。 - EpicPandaForce
显示剩余7条评论

25

我使用了类似EpicPandaForce所回答的方法。虽然它有效,但是订阅/取消订阅似乎很繁琐。我开始使用不同于Room的另一个数据库,因此我需要创建自己的DataSource.Factory。显然可以使当前的DataSource失效,并创建一个新的DataSource,而这就是我使用搜索参数的地方。

我的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
}
}

我在这里使用ObjectBox,但你可以直接返回你的Room DAO查询结果(我猜已经是一个DataSourceFactory了),在create方法中调用它自己的create。

我没有测试过,但这可能有效:

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
}
}
当然可以直接从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()
}
}
无论搜索查询以何种方式接收,只需在ViewModel上调用searchSweets。它会在Factory中设置搜索查询,然后使DataSource无效。接着,在Factory中调用create并创建一个新的DataSource实例,带有新的查询,并在底层将其传递给现有的LiveData。

然而,搜索查询的接收方式并不影响操作,只需要在ViewModel中调用searchSweets函数即可。该函数会将搜索查询设置到工厂(Factory)中,然后使数据源(DataSource)失效。之后,在工厂中调用create函数,使用新的查询创建数据源的新实例,并将其作为现有的LiveData实例的底层传递。


1
是的,这也是使用分页库的一种可能解决方案。我之前使用的设置适用于LiveData<List<T>>RealmResults<T>或任何类似的可观察集合。但在这里,你可以对DataSource.Factory进行参数化,并使数据源无效,然后你将收到一个新的数据源,其中包含新的、经过筛选的设置。对于分页来说,这是一个不错的选择! - EpicPandaForce
“…如果底层数据集被修改,必须创建一个新的PagedList / DataSource对来表示新数据。” - Dmitriy Puchkov
真正的魔力似乎在于“取消订阅/重新订阅”,这是Transformations.switchMap在内部使用MediatorLiveData完成的。 - EpicPandaForce

0
你可以参考上面的其他答案,但这里还有另一种方法:你可以让工厂根据你的需求生产不同的数据源。具体步骤如下: 在你的DataSource.Factory类中,提供设置器来初始化YourDataSource所需的参数。
private String searchText;
...
public void setSearchText(String newSearchText){
    this.searchText = newSearchText;
}
@NonNull
@Override
public DataSource<Integer, SearchItem> create() {
    YourDataSource dataSource = new YourDataSource(searchText); //create DataSource with parameter you provided
    return dataSource;
}

当用户输入新的搜索文本时,让您的ViewModel类设置新的搜索文本,然后在DataSource上调用invalidated。在您的Activity/Fragment中:
yourViewModel.setNewSearchText(searchText); //set new text when user searchs for a text

在您的ViewModel中,定义该方法以更新Factory类的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
}

当数据源被无效化时,DataSource.Factory将调用其create()方法,使用您设置的newText值创建新的数据源。结果将是相同的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接