如何通过Room DAO的排序与条件查询刷新RecyclerView?
解决方法:通过ViewModel调用排序/筛选查询刷新RecyclerView
要实现通过筛选FAB或Spinner触发排序、条件查询并刷新RecyclerView,我们需要从DAO→Repository→ViewModel→Fragment逐层添加支持,同时利用LiveData自动观察数据变化。以下是详细步骤:
1. 修正DAO层:返回LiveData避免主线程阻塞
首先,你的DAO方法需要返回LiveData<List<Item>>,Room会自动在后台线程执行查询并通过LiveData通知观察者,避免主线程崩溃:
@Dao public interface ItemDAO { @Query("SELECT * from item_table ") LiveData<List<Item>> getItemList(); @Query("SELECT * from item_table ORDER by :order DESC") LiveData<List<Item>> getOrderItemList(String order); @Query("SELECT * from item_table WHERE :whereCondition") LiveData<List<Item>> getWhereItemList(String whereCondition); // 新增:同时支持筛选+排序的组合查询(可选但推荐) @Query("SELECT * from item_table WHERE :whereCondition ORDER by :order DESC") LiveData<List<Item>> getFilteredAndOrderedItems(String whereCondition, String order); }
2. 改造Repository层:添加排序/筛选查询方法
在Repository中封装DAO的新方法,统一对外暴露:
public class ItemRepository { private ItemDAO itemDAO; private LiveData<List<Item>> allItems; public ItemRepository(Application application) { ItemDatabase db = ItemDatabase.getDatabase(application); itemDAO = db.itemDAO(); allItems = itemDAO.getItemList(); } public LiveData<List<Item>> getAllItems() { return allItems; } // 排序查询 public LiveData<List<Item>> getOrderedItems(String order) { return itemDAO.getOrderItemList(order); } // 条件查询 public LiveData<List<Item>> getFilteredItems(String whereCondition) { return itemDAO.getWhereItemList(whereCondition); } // 组合查询(筛选+排序) public LiveData<List<Item>> getFilteredAndOrderedItems(String whereCondition, String order) { return itemDAO.getFilteredAndOrderedItems(whereCondition, order); } }
3. 升级ViewModel层:动态切换数据源
在ViewModel中维护当前的排序/筛选条件,通过MediatorLiveData合并不同查询的结果,实现自动切换数据源:
public class ItemViewModel extends AndroidViewModel { private ItemRepository myRepository; private LiveData<List<Item>> myAllItems; // 保存当前排序条件(null表示无排序) private final MutableLiveData<String> currentSortOrder = new MutableLiveData<>(); // 保存当前筛选条件(null表示无筛选) private final MutableLiveData<String> currentWhereCondition = new MutableLiveData<>(); // 合并后的最终数据源 private final MediatorLiveData<List<Item>> currentItems = new MediatorLiveData<>(); public ItemViewModel(Application application) { super(application); myRepository = new ItemRepository(application); myAllItems = myRepository.getAllItems(); // 默认加载全部数据 currentItems.addSource(myAllItems, currentItems::setValue); // 监听排序条件变化,自动切换数据源 currentSortOrder.observeForever(order -> updateCurrentItems()); // 监听筛选条件变化,自动切换数据源 currentWhereCondition.observeForever(condition -> updateCurrentItems()); } // 根据当前条件更新数据源 private void updateCurrentItems() { // 先移除旧的数据源 currentItems.removeSources(currentItems.getSources()); String order = currentSortOrder.getValue(); String condition = currentWhereCondition.getValue(); if (order != null && condition != null && !order.isEmpty() && !condition.isEmpty()) { // 同时有筛选+排序 currentItems.addSource(myRepository.getFilteredAndOrderedItems(condition, order), currentItems::setValue); } else if (order != null && !order.isEmpty()) { // 只有排序 currentItems.addSource(myRepository.getOrderedItems(order), currentItems::setValue); } else if (condition != null && !condition.isEmpty()) { // 只有筛选 currentItems.addSource(myRepository.getFilteredItems(condition), currentItems::setValue); } else { // 无筛选无排序,加载全部数据 currentItems.addSource(myAllItems, currentItems::setValue); } } // 对外暴露合并后的数据源 public LiveData<List<Item>> getCurrentItems() { return currentItems; } // 设置排序条件(传入数据库字段名,如"title"、"year") public void setSortOrder(String order) { currentSortOrder.setValue(order); } // 设置筛选条件 public void setWhereCondition(String whereCondition) { currentWhereCondition.setValue(whereCondition); } // 重置所有筛选和排序 public void resetFilters() { currentSortOrder.setValue(null); currentWhereCondition.setValue(null); } }
4. 改造Fragment:绑定新数据源并处理用户交互
修改Fragment的初始化代码,改为观察ViewModel的getCurrentItems(),然后为FAB/Spinner添加点击事件:
4.1 初始化RecyclerView和ViewModel
// 注意:ViewModelProviders已废弃,改用ViewModelProvider private ItemViewModel myItemViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.your_fragment_layout); RecyclerView myRecyclerView = findViewById(R.id.recyclerview); final ItemListAdapter myAdapter = new ItemListAdapter(this); myRecyclerView.setAdapter(myAdapter); myRecyclerView.setLayoutManager(new LinearLayoutManager(this)); myItemViewModel = new ViewModelProvider(this).get(ItemViewModel.class); // 观察合并后的数据源,自动刷新列表 myItemViewModel.getCurrentItems().observe(this, items -> { // 优化:避免空指针 myAdapter.setItems(items != null ? items : new ArrayList<>()); }); }
4.2 处理排序FAB点击(弹出选择对话框)
FloatingActionButton fabSort = findViewById(R.id.fab_sort); fabSort.setOnClickListener(v -> { String[] sortOptions = {"按字母排序", "按年份排序"}; ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, sortOptions); new AlertDialog.Builder(this) .setTitle("选择排序方式") .setAdapter(adapter, (dialog, which) -> { String sortColumn = switch (which) { case 0 -> "title"; // 替换为你的Item表对应字段 case 1 -> "year"; default -> "title"; }; myItemViewModel.setSortOrder(sortColumn); }) .setNegativeButton("取消", null) .show(); });
4.3 处理筛选FAB点击(弹出复选框对话框)
首先创建筛选对话框的布局filter_dialog.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> <CheckBox android:id="@+id/cb_complete" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="已完成"/> <CheckBox android:id="@+id/cb_action" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="动作类型"/> </LinearLayout>
然后在Fragment中处理点击事件:
FloatingActionButton fabFilter = findViewById(R.id.fab_filter); fabFilter.setOnClickListener(v -> { View dialogView = getLayoutInflater().inflate(R.layout.filter_dialog, null); CheckBox cbComplete = dialogView.findViewById(R.id.cb_complete); CheckBox cbAction = dialogView.findViewById(R.id.cb_action); new AlertDialog.Builder(this) .setTitle("筛选条件") .setView(dialogView) .setPositiveButton("应用", (dialog, which) -> { List<String> conditions = new ArrayList<>(); if (cbComplete.isChecked()) { conditions.add("status = 'complete'"); } if (cbAction.isChecked()) { conditions.add("genre LIKE '%Action%'"); } String whereCondition = null; if (!conditions.isEmpty()) { whereCondition = "(" + String.join(" AND ", conditions) + ")"; } myItemViewModel.setWhereCondition(whereCondition); }) .setNegativeButton("重置", (dialog, which) -> { myItemViewModel.resetFilters(); cbComplete.setChecked(false); cbAction.setChecked(false); }) .show(); });
关键注意事项
- SQL注入风险:直接拼接
whereCondition字符串存在SQL注入风险,推荐使用Room的参数绑定方式(比如将每个条件的参数单独传入DAO),如果需要动态多条件筛选,可以考虑使用Room的@RawQuery。 - Adapter优化:在
setItems方法中处理空列表,避免空指针异常。 - 线程安全:所有DAO查询返回
LiveData,Room会自动处理后台线程,无需手动切换线程。
内容的提问来源于stack exchange,提问作者Glasindor
相关产品推荐
相关产品推荐

