最佳实践:使用Room和LiveData运行时过滤器

时间:2018-02-13 15:05:11

标签: android mvvm android-room android-livedata

我正在使用回收器显示Room wrapped DB的内容的屏幕上。适配器从ViewModel获取LiveData,该ViewModel隐藏Room DAO对象上的查询调用。因此,LiveData对象实际上是一个ComputableLiveData对象,它知道Room DB的更改。

现在我想在屏幕上添加过滤器选项。我在何处/如何在Room-LiveData-ViewModel设置中实现此功能?

适配器或ViewModel“postfilter”应该在LiveData中产生结果吗?我是否应该为每次过滤器更改从房间重新查询数据?我可以重用底层(可计算)LiveData吗?如果没有,我是否应该为每次过滤器更改创建新的LiveData?

此处讨论了类似的问题:Reload RecyclerView after data change with Room, ViewModel and LiveData

3 个答案:

答案 0 :(得分:8)

我正在处理类似的问题。最初我有RxJava,但现在我将其转换为LiveData。

这是我在ViewModel内部进行的操作:

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

此模型Livedata在视图模型构造函数中以以下方式构造:

        // 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);
}

然后,此新过滤器将被转换为新的livedata值,并将其发送给观察者(一个片段)。

该片段获取实时数据,以通过视图模型中的调用进行观察:

// 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);
        }
    });

}

希望对您有帮助。

答案 1 :(得分:3)

所以,我最终这样做了:

  • 片段将过滤器状态转发给ViewModel。副作用:过滤器状态可以由多个(即,由于配置改变而后续)片段实例使用。也许你想要那个,也许不是。我愿意。
  • ViewModel包含一个MediatorLiveData实例。它有一个源:Room DB LiveData对象。消息来源只是改变了调解员。如果片段更改了过滤器,则会通过重新查询来交换源。

回答我的详细问题:

  • 没有后过滤
  • 是,重新查询过滤器更改
  • 我没有重复使用ComputableLiveData(不确定它是否可行)

关于评论中的讨论:

  • 我不申请传呼

关于房间的最后说明:我错了还是我需要为我想要应用的每个滤镜组合编写单独的DAO方法?好的,我可以通过String插入select语句的可选部分,但是我会失去Room的好处。某种使语句可组合的语句构建器会很好。

编辑:请注意以下Ridcully的评论。他提到SupportSQLiteQueryBuilder和@RawQuery来解决我猜的最后一部分。我虽然没有检查出来。

感谢CommonsWarepskink的帮助!

答案 2 :(得分:2)

基于Francisco的回答(非常感谢!),这是我在Kotlin中基于EditText输入实现类似的动态数据库过滤的方式。

以下是Dao查询示例,在此示例中,我基于传入的过滤器字符串执行选择:

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

我有一个存储库,但是在这种情况下,它只是一个简单的传递。如果没有存储库,则可以直接从ViewModel调用dao方法。

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

然后在ViewModel中,我使用Francisco也使用的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)

}

希望有帮助!

免责声明:这是我的第一篇文章,所以让我知道我是否错过了什么。我不确定如何链接到Francisco的答案,否则我会做到的。它无疑帮助我实现了实施。