最佳实践:使用 Room 和 LiveData 的运行时过滤器
Best practice: Runtime filters with Room and LiveData
我正在处理一个屏幕,该屏幕使用回收器显示 Room 包装的 DB 的内容。适配器从隐藏对 Room DAO 对象的查询调用的 ViewModel 获取 LiveData。因此,LiveData 对象实际上是一个 ComputableLiveData 对象,它知道 Room DB 的更改。
现在我想在屏幕上添加过滤器选项。我将在这个 Room-LiveData-ViewModel 设置中在哪里/如何实现它?
适配器或 ViewModel 应该 "postfilter" LiveData 中的结果吗?我是否应该为每次过滤器更改从房间重新查询数据?我可以为此重用底层的(可计算的)LiveData 吗?如果不是,我真的应该为每个过滤器更改创建新的 LiveData 吗?
这里讨论了一个类似的问题:Reload RecyclerView after data change with Room, ViewModel and LiveData
所以,我最终这样做了:
- 片段将过滤器状态转发给 ViewModel。副作用:过滤器状态可能被多个(即由于配置更改而导致的后续)片段实例使用。也许你想要那个,也许不是。我愿意。
- ViewModel 包含一个 MediatorLiveData 实例。它只有一个来源:Room DB LiveData 对象。源只是将更改转发给调解器。如果过滤器被片段改变,则源被重新查询交换。
回答我的详细问题:
- 无后置过滤
- 是,重新查询过滤器更改
- 我不重用 ComputableLiveData(不确定是否可行)
关于评论中的讨论:
- 我不应用分页
关于 Room 的最后说明:我错了吗?还是我需要为每个要应用的过滤器组合编写单独的 DAO 方法?好的,我可以通过字符串插入 select 语句的可选部分,但那样我就失去了 Room 的好处。某种使语句可组合的语句构建器会很好。
编辑:请注意下面 Ridcully 的评论。他提到了 SupportSQLiteQueryBuilder 和 @RawQuery 来解决我猜的最后一部分。不过我还没有检查出来。
感谢CommonsWare and pskink的帮助!
我正在处理类似的问题。最初我有 RxJava 但现在我正在将它转换为 LiveData。
这就是我在 ViewModel 中的做法:
// Inside ViewModel
MutableLiveData<FilterState> modelFilter = new MutableLiveData<>();
LiveData<PagedList<Model>> 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);
}
然后,这个新的过滤器将 "transformed" 在一个新的 livedata 值中,该值将被发送给观察者(一个片段)。
fragment通过视图模型中的调用获取实时数据观察:
// 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);
}
});
}
希望对您有所帮助。
基于 Francisco 的回答(非常感谢!),下面是我如何基于 EditText 输入实现类似的动态数据库过滤,但在 Kotlin 中。
这是 Dao 查询示例,我根据传入的过滤器字符串执行 select:
// 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 方法 posts 新的过滤器值,这反过来会导致 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
}
请注意,初始过滤器值默认设置为 return 所有项目的通配符 ("%")。如果您不设置此项,则在您调用 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)
}
希望对您有所帮助!!
免责声明:这是我的第一个 post,所以如果我错过了什么,请告诉我。我不确定如何 link Francisco 的回答,否则我会那样做。它确实帮助我实现了目标。
您可以使用 CASE WHEN
和 THEN
对数据库进行排序 看这段代码
创建常量class 用于排序 id
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>
你的仓库Class
fun getItems(name: String, sortBy: Int) : MutableLiveData<Items>{
return myDao.getItems(name,sortBy)
}
我正在处理一个屏幕,该屏幕使用回收器显示 Room 包装的 DB 的内容。适配器从隐藏对 Room DAO 对象的查询调用的 ViewModel 获取 LiveData。因此,LiveData 对象实际上是一个 ComputableLiveData 对象,它知道 Room DB 的更改。
现在我想在屏幕上添加过滤器选项。我将在这个 Room-LiveData-ViewModel 设置中在哪里/如何实现它?
适配器或 ViewModel 应该 "postfilter" LiveData 中的结果吗?我是否应该为每次过滤器更改从房间重新查询数据?我可以为此重用底层的(可计算的)LiveData 吗?如果不是,我真的应该为每个过滤器更改创建新的 LiveData 吗?
这里讨论了一个类似的问题:Reload RecyclerView after data change with Room, ViewModel and LiveData
所以,我最终这样做了:
- 片段将过滤器状态转发给 ViewModel。副作用:过滤器状态可能被多个(即由于配置更改而导致的后续)片段实例使用。也许你想要那个,也许不是。我愿意。
- ViewModel 包含一个 MediatorLiveData 实例。它只有一个来源:Room DB LiveData 对象。源只是将更改转发给调解器。如果过滤器被片段改变,则源被重新查询交换。
回答我的详细问题:
- 无后置过滤
- 是,重新查询过滤器更改
- 我不重用 ComputableLiveData(不确定是否可行)
关于评论中的讨论:
- 我不应用分页
关于 Room 的最后说明:我错了吗?还是我需要为每个要应用的过滤器组合编写单独的 DAO 方法?好的,我可以通过字符串插入 select 语句的可选部分,但那样我就失去了 Room 的好处。某种使语句可组合的语句构建器会很好。
编辑:请注意下面 Ridcully 的评论。他提到了 SupportSQLiteQueryBuilder 和 @RawQuery 来解决我猜的最后一部分。不过我还没有检查出来。
感谢CommonsWare and pskink的帮助!
我正在处理类似的问题。最初我有 RxJava 但现在我正在将它转换为 LiveData。
这就是我在 ViewModel 中的做法:
// Inside ViewModel
MutableLiveData<FilterState> modelFilter = new MutableLiveData<>();
LiveData<PagedList<Model>> 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);
}
然后,这个新的过滤器将 "transformed" 在一个新的 livedata 值中,该值将被发送给观察者(一个片段)。
fragment通过视图模型中的调用获取实时数据观察:
// 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);
}
});
}
希望对您有所帮助。
基于 Francisco 的回答(非常感谢!),下面是我如何基于 EditText 输入实现类似的动态数据库过滤,但在 Kotlin 中。
这是 Dao 查询示例,我根据传入的过滤器字符串执行 select:
// 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 方法 posts 新的过滤器值,这反过来会导致 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
}
请注意,初始过滤器值默认设置为 return 所有项目的通配符 ("%")。如果您不设置此项,则在您调用 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)
}
希望对您有所帮助!!
免责声明:这是我的第一个 post,所以如果我错过了什么,请告诉我。我不确定如何 link Francisco 的回答,否则我会那样做。它确实帮助我实现了目标。
您可以使用 CASE WHEN
和 THEN
对数据库进行排序 看这段代码
创建常量class 用于排序 id
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>
你的仓库Class
fun getItems(name: String, sortBy: Int) : MutableLiveData<Items>{
return myDao.getItems(name,sortBy)
}