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

Android 最佳实践:使用Room和LiveData的运行时过滤器,android,mvvm,android-room,android-livedata,Android,Mvvm,Android Room,Android Livedata,我在一个屏幕上工作,显示使用回收器包装的DB房间的内容。适配器从ViewModel获取LiveData,该ViewModel隐藏了对Room DAO对象的查询调用。因此,LiveData对象实际上是一个可计算的LiveData对象,它知道房间数据库的更改 现在我想在屏幕上添加过滤器选项。我将在这个房间的何处/如何实现LiveData ViewModel设置 适配器或ViewModel是否应该在LiveData中“后过滤”结果?我是否应该为每次过滤器更换重新查询房间中的数据?我可以为此重用底层(可

我在一个屏幕上工作,显示使用回收器包装的DB房间的内容。适配器从ViewModel获取LiveData,该ViewModel隐藏了对Room DAO对象的查询调用。因此,LiveData对象实际上是一个可计算的LiveData对象,它知道房间数据库的更改

现在我想在屏幕上添加过滤器选项。我将在这个房间的何处/如何实现LiveData ViewModel设置

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


这里也讨论了一个类似的问题:

所以,我最后是这样做的:

  • 片段将过滤器状态传递给ViewModel。副作用:过滤器状态可能被多个(即,由于配置更改而导致的后续)片段实例使用。也许你想要,也许不是。我知道
  • ViewModel包含一个MediatorLiveData实例。它只有一个源:Room DB LiveData对象。源文件只是对中介文件进行更改。如果片段更改了过滤器,则通过重新查询来交换源
回答我的详细问题:

  • 无后过滤
  • 是,重新查询过滤器更换
  • 我不重用ComputeableLiveData(不确定是否可能)
关于评论中的讨论:

  • 我不申请寻呼
房间的最后一点:我是错了还是我需要为我想要应用的每个过滤器组合编写单独的DAO方法?好的,我可以通过字符串插入select语句的可选部分,但是这样我就失去了Room的好处。某种使语句可组合的语句生成器会很好

编辑:请注意下面Ridcully的评论。他提到了SupportSQLiteQueryBuilder和@RawQuery,以解决我猜的最后一部分。不过我还没查出来


谢谢你的帮助

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

以下是我在ViewModel中的操作:

// Inside ViewModel
MutableLiveData<FilterState> modelFilter = new MutableLiveData<>();
LiveData<PagedList<Model>> modelLiveData;
然后,这个新过滤器将被“转换”成一个新的livedata值,该值将被发送给观察者(一个片段)

片段通过视图模型中的调用获取要观察的livedata:

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

    return modelLiveData;

}
//在ViewModel中
公共LiveData getModelLiveData(){
返回模型livedata;
}
在我的片段中,我有:

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

}
@覆盖
ActivityCreated上的公共无效(@Nullable Bundle savedinStateCState){
super.onActivityCreated(savedInstanceState);
ViewModel ViewModel=ViewModelProviders.of(this.getActivity()).get(ViewModel.class);
viewModel.getModelLiveData().observe(this.getViewLifecycleOwner(),new Observer()){
@凌驾
更改后的公共void(@Nullable PagedList model){
modelListViewAdapter.submitList(模型);
}
});
}

我希望这能有所帮助。

基于Francisco的回答(非常感谢!),下面是我如何基于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>>
希望有帮助


免责声明:这是我的第一篇帖子,如果我遗漏了什么,请告诉我。我不知道如何链接到弗朗西斯科的答案,否则我会这么做。它确实帮助我实现了自己的目标。

您的数据集有多大?如果您没有将整个数据集保存在内存中,根据定义,您必须返回数据库以查看筛选器状态的任何更改。您是否使用支持库?我正在寻找与数据集大小无关的答案。;)但感谢您的建议,即后过滤器可能不是最佳实践。@pskink一年来,是否有更新/更好的解决方案来过滤房间?(在问我自己的问题之前先检查一下)。我使用LiveData,所以甚至不能使用LiveData。每次更换过滤器时,我都会重新询问并重新连接观察者。只是感觉不对劲:(@AdiB您不需要
MediatorLiveData
-只需使用
转换。switchMap
在搜索标准的每次更改上Hello@Oderik,我也在尝试过滤我的回收器视图,该视图使用viewmodel的lovedata条目的结果。您能解释一下您找到了什么解决方案,因为我无法理解您的答案吗很明显。当我们在片段生命周期中初始化一次viewmodel时,如何将筛选器状态前置到视图模型中,我在onCreateView中实现了这一点。以及如何使用MediatorLiveData实例重新查询筛选器状态的更改。除此之外,我使用like语句“@Query(”以这种方式选择*FROM network WHERE name,比如:networkName | |“%”)public abstract LiveData getNetworksByName(String networkName);“它只过滤名称是否以给定文本开头,但如何查询名称是否包含”给定的单词。我认为这超出了这个问题的范围。也许你应该发布你自己的。有一个SupportSQLiteQueryBuilder,你可以在DAO中与@RawQuery一起使用。你测试过这段代码吗?它似乎容易受到SQL注入攻击。嗨@pkuszewski,不,我没有。如果你有关于潜在漏洞的更多信息,那么请分享你的能力和如何缓解。谢谢!
@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);
        }
    });

}
// Dao query with filter
@Query("SELECT * from myitem WHERE name LIKE :filter ORDER BY _id")
fun getItemsFiltered(filter: String): LiveData<List<MyItem>>
// Repository
fun getItemsFiltered(filter: String): LiveData<List<MyItem>> {
    return dao.getItemsFiltered(filter)
}
// 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
}
// 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)

}