分页库过滤/搜索

时间:2018-03-09 11:16:09

标签: android search android-paging

我正在使用Android Paging Library,如下所述: https://developer.android.com/topic/libraries/architecture/paging.html

但我也有一个EditText用于按名称搜索用户。

如何过滤分页库中的结果以仅显示匹配的用户?

3 个答案:

答案 0 :(得分:27)

从2019年开始编辑:等等,我想你可以用MediatorLiveData解决这个问题。

特别是Transformations.switchMap和一些额外的魔法。

目前我正在使用

public void reloadTasks() {
    if(liveResults != null) {
        liveResults.removeObserver(this);
    }
    liveResults = getFilteredResults();
    liveResults.observeForever(this);
}

但是如果你考虑一下,你应该能够在不使用observeForever的情况下解决这个问题,特别是如果我们认为switchMap也做了类似的事情。

所以我们需要的是LiveData<SelectedOption>切换映射到我们需要的LiveData<PagedList<T>>

private MutableLiveData<String> filterText = new MutableLiveData<>();

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处理。如果我们想要缓存LiveDatas,那么我们可以在传递给方法的匿名实例中进行。

    data = Transformations.switchMap(
            filterText, new Function<String, LiveData<List<T>>>() {
                private Map<String, LiveData<List<T>>> cachedLiveData = new HashMap<>();

                @Override
                public LiveData<List<T>> apply(String input) {
                    // ...
                }
            }

编辑:实际上。虽然这对于常规LiveData<?>有意义,但是通过Paging,您可以实际参数化Factory,然后使数据源无效并获得免费评估的新数据源。无需重新创建查询持有者本身。

因此,当您使用Paging时,其他答案中提到的方法是更好的选择。

原始答案:

你知道如何使用这样的适配器:

public class TaskAdapter
        extends PagedListAdapter<Task, TaskAdapter.ViewHolder> {
    public TaskAdapter() {
        super(Task.DIFF_ITEM_CALLBACK);
    }

在ViewModel中,您可以设置实时分页列表并公开它:

private LiveData<PagedList<Task>> liveResults;

public TaskViewModel() {
    liveResults = new LivePagedListBuilder<>(taskDao.tasksSortedByDate(),
        new PagedList.Config.Builder() //
              .setPageSize(20) //
              .setPrefetchDistance(20) //
              .setEnablePlaceholders(true) //
              .build())
            .setInitialLoadKey(0)
            .build();

然后观察ViewModel中的分页列表并将其设置为适配器:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    viewModel.getTasks().observe(this, pagedList -> {
        //noinspection Convert2MethodRef
        taskAdapter.submitList(pagedList); //used to be `setList`
    });

嗯,棘手的是,如果你想让它参数化,那么你必须在这里替换以下内容,并让视图能够观察它:

    liveResults = new LivePagedListBuilder<>(userDao.usersByName(input) // <-- !!

所以你必须更换LiveData。 O_O

在这种情况下你可以做的是从现有的LiveData中删除观察者,用新的LiveData替换它,然后开始观察它。

private void startListening() {
    viewModel.getTasks().observe(this, pagedList -> {
        //noinspection Convert2MethodRef
        taskAdapter.submitList(pagedList); // used to be `setList`
    });
}

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    startListening();
}

@OnTextChanged(R.id.edit_text)
public void onTextChanged(Editable editable) {
    String username = editable.toString();
    replaceSubscription(userName);
}

private void replaceSubscription(String userName) {
    viewModel.replaceSubscription(this, userName);
    startListening();
}

public class UserViewModel extends ViewModel {
    private LiveData<PagedList<User>> liveResults;

    private String userName;

    private LiveData<PagedList<User>> createFilteredUsers(String userName) {
       // TODO: handle if `null` and load all data instead
       return new LivePagedListBuilder<>(userDao.usersByName(userName),
            new PagedList.Config.Builder() //
                  .setPageSize(20) //
                  .setPrefetchDistance(20) //
                  .setEnablePlaceholders(true) //
                  .build())
                .setInitialLoadKey(0)
                .build();
    }

    public UserViewModel(UserDao userDao, @Nullable String userName) { // null or restored, from ViewModelProviders.of(Factory)
        liveResults = createFilteredUsers(userName);
    }

    public void replaceSubscription(LifecycleOwner lifecycleOwner, String userName) {
        this.userName = userName;
        liveResults.removeObservers(lifecycleOwner);
        liveResults = createFilteredUsers(userName);
    }
}

答案 1 :(得分:18)

我使用了类似于EpicPandaForce回答的方法。在工作期间,这种订阅/取消订阅似乎很乏味。我已经开始使用除Room以外的其他数据库,因此无论如何我都需要创建自己的DataSource.Factory。显然可以使当前的DataSource无效,并且DataSource.Factory创建一个新的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,但是您可以在创建时返回房间DAO查询(我想因为它已经是一个DataSourceFactory,所以请调用其自己的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中调用create,并使用新查询创建DataSource的新实例,并将其传递给后台的现有LiveData。

答案 2 :(得分: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。在您的活动/片段中:

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 失效时,DataSource.Factory 将调用其 create() 方法以您设置的 newText 值创建新的 DataSource。结果是一样的