web-dev-qa-db-ja.com

ページングライブラリフィルタ/検索

ここで説明されているようなAndroidページングライブラリを使用しています: https://developer.Android.com/topic/libraries/architecture/paging.html

しかし、名前でユーザーを検索するためのEditTextもあります。

一致するユーザーのみを表示するために、ページングライブラリからの結果をフィルター処理するにはどうすればよいですか?

31
user3292244

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によって処理されます。 LiveDataをキャッシュする場合は、メソッドに渡す匿名インスタンスで実行できます。

    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<?>には意味がありますが、ページングでは、実際にファクトリをパラメーター化し、データソースを無効にして、新しいデータソースを無料で評価できます。クエリホルダー自体を再作成せずに。

したがって、ページングを使用している場合は、他の回答で言及されている方法がより良いオプションです。


元の回答:

次のようなアダプタをどのように持っているか知っています。

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);
    }
}
33
EpicPandaForce

私は、EpicPandaForceが答えたのと同様のアプローチを使用しました。動作している間、このサブスクライブ/サブスクライブ解除は退屈に思えます。 Roomとは別のDBを使い始めたので、とにかく独自の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からのクエリで既にFactoryを渡すことができます。

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を無効にします。次に、ファクトリでcreateが呼び出され、DataSourceの新しいインスタンスが新しいクエリで作成され、内部の既存のLiveDataに渡されます。

19