Пейджинговая библиотека Фильтр/Поиск

Я использую пейджинговую библиотеку Android, как описано здесь: https://developer.android.com/topic/libraries/architecture/paging.html

Но у меня также есть EditText для поиска пользователей по имени.

Как я могу фильтровать результаты из библиотеки пейджинга, чтобы отображать только соответствующие пользователи?

Ответы

Ответ 1

РЕДАКТИРОВАТЬ с 2019 года: подождите, я думаю, вы могли бы решить эту проблему с MediatorLiveData.

Конкретно Transformations.switchMap и некоторая дополнительная магия.

В настоящее время я использую

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

Но если вы подумаете об этом, вы сможете решить эту проблему, не используя observeForever за 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, а затем аннулировать источник данных и получить новый источник данных, оцененный бесплатно. Без воссоздания самого владельца запроса.

Таким образом, метод, упомянутый в другом ответе, является лучшим вариантом, когда вы используете пейджинг.


ОРИГИНАЛЬНЫЙ ОТВЕТ:

Вы знаете, как у вас есть адаптер, как это:

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. о_О

В этом случае вы можете удалить наблюдателей из существующих 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);
    }
}

Ответ 2

Я использовал подход, аналогичный тому, на который ответил EpicPandaForce. Пока он работает, эта подписка/отмена подписки кажется утомительной. Я начал использовать другую БД, чем комнату, поэтому мне все равно нужно было создать свой собственный 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, вызовите его собственное создание).

Я не тестировал его, но это может сработать:

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

Конечно, можно просто передать Factory уже с запросом от 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()
}
}

Однако поисковый запрос получен, просто вызовите searchSweets в ViewModel. Он задает поисковый запрос в Factory, а затем недействителен DataSource. В свою очередь, create вызывается в Factory, и новый экземпляр DataSource создается с новым запросом и передается в существующую LiveData под капотом.

Ответ 3

Хотел прокомментировать, но не кредиты для этого. EpicPandaForce, пожалуйста, поделитесь примером кода фильтрации или сортировки в подкачке. Любой поможет мне найти полный учебник или код для того же.