Лучшая практика: фильтры времени выполнения с Room и LiveData

Я работаю на экране, который показывает содержимое комнаты, завернутой в БД, используя утилизацию. Адаптер получает LiveData от ViewModel, которая скрывает вызов запроса для объекта Room DAO. Таким образом, объект LiveData на самом деле является объектом ComputableLiveData, который знает об изменениях в БД комнаты.

Теперь я хочу добавить параметры фильтра на экран. Где / как мне реализовать это в этой настройке Room-LiveData-ViewModel?

Должен ли адаптер или ViewModel "постфильтровать" результаты в LiveData? Должен ли я запрашивать данные из комнаты для каждого изменения фильтра? Могу ли я использовать для этого базовые (вычислимые)LiveData? Если нет, должен ли я действительно создавать новые LiveData для каждого изменения фильтра?

Подобный вопрос обсуждается здесь: перезагрузите RecyclerView после изменения данных с помощью Room, ViewModel и LiveData.

4 ответа

Решение

Итак, я закончил делать это так:

  • Фрагмент направляет состояние фильтра в ViewModel. Побочный эффект: состояние фильтра может использоваться несколькими (т.е. последующими из-за изменения конфигурации) экземплярами фрагмента. Может быть, вы хотите этого, а может и нет. Я делаю.
  • ViewModel содержит экземпляр MediatorLiveData. Он имеет единственный источник: объект Room DB LiveData. Источник просто ищет изменения для посредника. Если фильтр изменяется по фрагменту, источник заменяется запросом.

Отвечая на мои подробные вопросы:

  • Нет постфильтрации
  • Да, запрос на замену фильтра
  • Я не использую ComputableLiveData (не уверен, что это будет возможно)

По поводу обсуждения в комментариях:

  • Я не применяю пейджинг

Последнее замечание о комнате: я не прав или мне нужно написать отдельные методы DAO для каждой комбинации фильтров, которую я хочу применить? Хорошо, я мог бы вставить необязательные части оператора select через строку, но тогда я бы потерял преимущества Room. Был бы неплох какой-то конструктор операторов, который делает составные операторы.

Спасибо CommonsWare и pskink за вашу помощь!

Я работаю в аналогичной проблеме. Изначально у меня был RxJava, но теперь я конвертирую его в LiveData.

Вот как я делаю внутри моей ViewModel:

// Inside ViewModel
MutableLiveData<FilterState> modelFilter = new MutableLiveData<>();
LiveData<PagedList<Model>> modelLiveData;

Эта modelLivedata создается следующим образом в конструкторе модели представления:

        // In ViewModel constructor
        modelLiveData = Transformations.switchMap(modelFilter,
                    new android.arch.core.util.Function<FilterState, LiveData<PagedList<Model>>>() {
                        @Override
                        public LiveData<PagedList<Model>> apply(FilterState filterState) {
                            return modelRepository.getModelLiveData(getQueryFromFilter(filterState));
                        }
                    });

Когда модель представления получает другой фильтр для применения, она делает:

// In ViewModel. This method receives the filtering data and sets the modelFilter 
// mutablelivedata with this new filter. This will be "transformed" in new modelLiveData value.
public void filterModel(FilterState filterState) {

    modelFilter.postValue(filterState);
}

Затем этот новый фильтр будет "преобразован" в новое значение жизненных данных, которое будет отправлено наблюдателю (фрагмент).

Фрагмент получает живые данные для наблюдения посредством вызова в модели представления:

// In ViewModel
public LiveData<PagedList<Model>> getModelLiveData() {

    return modelLiveData;

}

И внутри моего фрагмента у меня есть:

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    ViewModel viewModel = ViewModelProviders.of(this.getActivity()).get(ViewModel.class);

    viewModel.getModelLiveData().observe(this.getViewLifecycleOwner(), new Observer<PagedList<Model>>() {
        @Override
        public void onChanged(@Nullable PagedList<Model> model) {
            modelListViewAdapter.submitList(model);
        }
    });

}

Я надеюсь, что это помогает.

Основываясь на ответе Франциско (большое вам спасибо за это!), Вот как я реализовал аналогичную динамическую фильтрацию базы данных на основе ввода EditText, но в Kotlin.

Вот пример запроса Dao, где я выполняю выбор на основе переданной строки фильтра:

// Dao query with filter
@Query("SELECT * from myitem WHERE name LIKE :filter ORDER BY _id")
fun getItemsFiltered(filter: String): LiveData<List<MyItem>>

У меня есть репозиторий, но в данном случае это просто сквозной проход. Если у вас нет репозитория, вы можете вызвать метод dao прямо из ViewModel.

// Repository
fun getItemsFiltered(filter: String): LiveData<List<MyItem>> {
    return dao.getItemsFiltered(filter)
}

Затем в ViewModel я использую метод Transformations, который также использовал Франциско. Однако мой фильтр - это всего лишь простая строка, завернутая в MutableLiveData. Метод setFilter публикует новое значение фильтра, которое, в свою очередь, вызывает преобразование allItemsFiltered.

// ViewModel
var allItemsFiltered: LiveData<List<MyItem>>
var filter = MutableLiveData<String>("%")

init {
    allItemsFiltered = Transformations.switchMap(filter) { filter ->
        repository.getItemsFiltered(filter)
    }
}

// set the filter for allItemsFiltered
fun setFilter(newFilter: String) {
    // optional: add wildcards to the filter
    val f = when {
        newFilter.isEmpty() -> "%"
        else -> "%$newFilter%"
    }
    filter.postValue(f) // apply the filter
}

Обратите внимание, что начальное значение фильтра установлено на подстановочный знак ("%"), чтобы по умолчанию возвращались все элементы. Если вы не установите это значение, никакие элементы не будут наблюдаться, пока вы не вызовете setFilter.

Вот код во фрагменте, где я наблюдаю allItemsFiltered, а также применяю фильтрацию. Обратите внимание, что я обновляю фильтр, когда мой поисковый EditText изменяется, а также при восстановлении состояния просмотра. Последний установит ваш начальный фильтр, а также восстановит существующее значение фильтра при повороте экрана (если ваше приложение поддерживает это).

// Fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    // observe the filtered items
    viewModel.allItemsFiltered.observe(viewLifecycleOwner, Observer { items ->
        // update the displayed items when the filtered results change
        items.let { adapter.setItems(it) }
    })

    // update the filter as search EditText input is changed
    search_et.addTextChangedListener {text: Editable? ->
        if (text != null) viewModel.setFilter(text.toString())
    }
}

override fun onViewStateRestored(savedInstanceState: Bundle?) {
    super.onViewStateRestored(savedInstanceState)

    // update the filter to current search text (this also restores the filter after screen rotation)
    val filter = search_et.text?.toString() ?: ""
    viewModel.setFilter(filter)

}

Надеюсь, это поможет!!

Отказ от ответственности: это мой первый пост, поэтому дайте мне знать, если я что-то пропустил. Я не уверен, как связать с ответом Франциско, иначе я бы это сделал. Это определенно помогло мне добраться до моей реализации.

Вы можете отсортировать базу данных, используя CASE WHEN а также THENПосмотрите на этот код

Создайте постоянный класс для сортировки идентификатора

      object Constant{
  const val NAME_ASC = 1    
  const val NAME_DESC = 2   
  const val ADDED_ASC = 3  
  const val ADDED_DESC = 4 
}

Интерфейс Дао

      @Query(
    "SELECT * FROM table WHERE name=:name ORDER BY" +
            " CASE WHEN :sortBy = $NAME_ASC THEN title END ASC , " +
            " CASE WHEN :sortBy = $NAME_DESC THEN title END DESC , " +
            " CASE WHEN :sortBy = $ADDED_ASC  THEN added END ASC , " +
            " CASE WHEN :sortBy = $ADDED_DESC THEN added END DESC , " +
)
fun getItems(name: String, sortBy: Int): MutableLiveData<Item>

Ваш репозиторий Класс

      fun getItems(name: String, sortBy: Int) : MutableLiveData<Items>{
    return myDao.getItems(name,sortBy)
  }
Другие вопросы по тегам