Правильный способ фильтрации элементов базы данных с помощью 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 с запросом.