Правильный способ фильтрации элементов базы данных с помощью RxAndroid

Я использую предоставленный пример и настроил код под мое требование. Я изучаю Rx и не совсем знаком с тем, как это работает. Итак, в моем фрагменте у меня есть

private static String LIST_QUERY = "SELECT * FROM " + TodoItem.TABLE;

И мой onResume выглядит так:

@Override
public void onResume() {
    super.onResume();

    subscription = db.createQuery(TodoItem.TABLE, LIST_QUERY)
            .mapToList(TodoItem.MAPPER)
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(adapter);
}

Адаптер показал данные в виде списка, и он работает нормально, но теперь я добавил TextView поверх списка и хочу фильтровать результаты всякий раз, когда пользователь вводит текст. Я полагаю, что RxAndroid предоставляет оператор debounce, чтобы сделать это эффективным (и не запрашивать БД мгновенно), но я не уверен, как можно объединить этот код подписки для прослушивания изменений textView и фильтрации в соответствии с этим текстом.

Я уже пытался изменить LIST_QUERY на "SELECT * FROM " + TodoItem.TABLE + " WHERE " + TodoItem.DESCRIPTION + " LIKE \"" + query + "\" LIMIT 5”;

а потом попробовал:

    etSearch.addTextChangedListener(new TextWatcher() {

        @Override
        public void onTextChanged(CharSequence searchQuery, int i, int i1, int i2) {
            query = searchQuery.toString();
            adapter.notifyDataSetChanged();
        }

        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { //ignore }

        @Override
        public void afterTextChanged(Editable editable) { //ignore }
    });

Это привело к тому, что список стал пустым (я думал, что это нормально, так как исходный запрос - пустая строка), но когда я набрал какой-то текст, запрос вообще не обновлял / не фильтровал список.

Я попробовал альтернативный подход и добавил take(5) (так как я хочу LIMIT 5) после mapToList, и это сработало и показало только 5 элементов. А потом я добавил.filter(, набрал new и позволил Android Studio сгенерировать код Func1, и это выглядело так:

    subscription = db.createQuery(ListItem.TABLE, LIST_QUERY)
            .mapToList(Item.MAPPER)
            .filter(new Func1<List<ListItem>, Boolean>() {
                @Override
                public Boolean call(List<ListItem> items) {
                    return null;
                }
            })

Так что проблема в том, что он просит меня отфильтровать весь список. Я попробовал это:

public Boolean call(List<ListItem> items) {
   for(ListItem i: items)
      if(!i.description().startsWith(query))
          items.remove(i);
   return true;
}

Но по-прежнему отображается весь список, а изменения в тексте не вносят изменений в список вообще. Я полагаю, что что-то упустил, что я должен подписаться на EditText, но я не уверен, как бы я это сделал и совместил это с существующим кодом подписки базы данных. Ищем решения.

1 ответ

Решение

Есть несколько разных способов сделать это. Если вы хотите, чтобы фильтр существовал в Java, то вы можете использовать asRows оператор на QueryObservable:

database.createQuery(ListItem.TABLE, ListItem.QUERY)
  .flatMap(query -> query.asRows(Item.MAPPER)
    .filter(item -> item.description.startsWith(query))
    .toList())

Это будет Observable<List<Item>> только с элементами, которые соответствуют запросу.

Однако ваш оригинальный подход - идеальный способ сделать это, вы просто использовали LIKE неправильно. Вы, вероятно, хотите включить символы подстановки (% или же _) включить результаты, которые не соответствуют запросу точно. Например:

"SELECT * FROM " + TodoItem.TABLE + " WHERE " + TodoItem.DESCRIPTION + " LIKE \"%" + query + "%\" LIMIT 5”;

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

Другая часть, которую вы упомянули, заключается в том, что список не обновляется должным образом. Вы хотите использовать RxAndroid здесь, а не textChangedListeners, скорее всего. По вашему мнению у вас будет что-то вроде

RxTextView.textChanges(editText)
  .switchMap(query -> database.createQuery(ListItem.TABLE, ListItem.query(query))
    .mapToList(ListItem.MAPPER))
  .observeOn(AndroidSchedulers.mainThread())
  .subscribe(list -> {
    adapter.setData(list);
    adapter.notifyDataSetChanged();
  });

Таким образом, вы не будете беспокоиться о каких-либо гонках между выполнением запроса и адаптером, получающим вызов notify. ListItem.query(query) Метод просто создает эту строку sqlite с запросом.

Другие вопросы по тегам