You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ViewModel中通过SearchView过滤LiveData<List<Note>>并通知观察者?

Room集成SearchView过滤笔记列表问题

我有Note实体、包含SQL逻辑的NoteDAO、继承RoomDatabase的NoteDatabase类,还有关联NoteDAO和NoteDatabase的NoteRepository。NoteViewModel通过NoteRepository与数据库交互。

在Fragment的onCreateView中,我通过NoteViewModel获取所有笔记,观察数据后用adapter.submitList更新RecyclerView的条目。

但集成SearchView时遇到问题:能正常获取查询文本,但尝试过滤NoteViewModel中类型为LiveData<List<Note>>的allNotes变量时,无法按预期通知观察者更新列表。

试过把allNotes改成MutableLiveData,想在filterNotes方法里用setValue或postValue通知观察者,但不仅过滤没生效,连未过滤的原始笔记列表都拿不到了。


现有代码

FirstFragment代码

@Override
public View onCreateView(
        .......
) {
    ........

    RecyclerView recyclerView = binding.recyclerView;
    final NoteListAdapter adapter = new NoteListAdapter(new NoteListAdapter.WordDiff());
    recyclerView.setAdapter(adapter);
    recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));

    mNoteViewModel = new ViewModelProvider(requireActivity()).get(NoteViewModel.class);

    mNoteViewModel.getAllNotes().observe(getViewLifecycleOwner(), new Observer<List<Note>>() {
                @Override
                public void onChanged(List<Note> notes) {
                    Log.d("MainPage", "onChanged: " + notes);
                    adapter.submitList(notes);
                }
            });
   
    ........
}

NoteViewModel代码

public class NoteViewModel extends AndroidViewModel {

    private final NoteRepository mRepository;

    private final LiveData<List<Note>> mAllNotes;

    public NoteViewModel(@NonNull Application application) {
        super(application);
        mRepository = new NoteRepository(application);
        mAllNotes = mRepository.getAllNotes();
    }

    LiveData<List<Note>> getAllNotes() {
        return mAllNotes;
    }

    public void insert (Note note) { mRepository.insert(note); }
}

NoteRepository代码

public class NoteRepository {

    private final NoteDAO mNoteDAO;

    @NotNull
    private final LiveData<List<Note>> mAllNotes;

    NoteRepository(Application application) {
        NoteRoomDatabase db = NoteRoomDatabase.getDatabase(application);
        mNoteDAO = db.noteDAO();
        mAllNotes = mNoteDAO.getAllNotes();
    }

    public LiveData<List<Note>> getAllNotes() {
        return mAllNotes;
    }

    public void insert(Note note) {
        NoteRoomDatabase.databaseWriteExecutor.execute(() -> {
            mNoteDAO.insert(note);
        });
    }

}

NoteDAO代码

@Dao
public interface NoteDAO {

    @Insert
    void insert(Note note);

    @Query("SELECT * FROM notes ORDER BY id DESC")
    LiveData<List<Note>> getAllNotes();

    @Query("SELECT * FROM notes where title = :title ORDER BY id DESC")
    LiveData<List<Note>> getNotesByTitle(String title);

    @Query("SELECT * FROM notes where id = :id ORDER BY id DESC")
    LiveData<Note> getNoteById(long id);

    @Query("SELECT * FROM notes where color = :color ORDER BY id DESC")
    LiveData<List<Note>> getNotesByColor(String color);

}

解决方法

1. 优化DAO的搜索查询

把精确匹配的getNotesByTitle改成模糊匹配,更符合搜索场景:

@Dao
public interface NoteDAO {
    // ... 其他方法不变

    @Query("SELECT * FROM notes WHERE title LIKE '%' || :searchTerm || '%' ORDER BY id DESC")
    LiveData<List<Note>> getNotesBySearchTerm(String searchTerm);
}

2. 给Repository添加搜索方法

public class NoteRepository {
    // ... 其他代码不变

    public LiveData<List<Note>> getNotesBySearchTerm(String searchTerm) {
        return mNoteDAO.getNotesBySearchTerm(searchTerm);
    }
}

3. ViewModel用MediatorLiveData切换数据源

不要直接修改mAllNotes的类型,用MediatorLiveData来合并全量数据和搜索结果,既保留Room的自动更新,又支持动态过滤:

public class NoteViewModel extends AndroidViewModel {
    private final NoteRepository mRepository;
    // 存放搜索关键词
    private final MutableLiveData<String> mSearchQuery = new MutableLiveData<>();
    // 对外暴露的过滤后数据
    private final MediatorLiveData<List<Note>> mFilteredNotes = new MediatorLiveData<>();

    public NoteViewModel(@NonNull Application application) {
        super(application);
        mRepository = new NoteRepository(application);
        
        // 先加载全量笔记数据
        LiveData<List<Note>> allNotes = mRepository.getAllNotes();
        mFilteredNotes.addSource(allNotes, mFilteredNotes::setValue);

        // 监听搜索关键词变化,切换数据源
        mFilteredNotes.addSource(mSearchQuery, searchTerm -> {
            // 先移除之前的数据源,避免重复回调
            mFilteredNotes.removeSource(allNotes);
            LiveData<List<Note>> targetData;
            
            if (TextUtils.isEmpty(searchTerm)) {
                // 搜索框为空时,显示全量数据
                targetData = allNotes;
            } else {
                // 有搜索词时,加载搜索结果
                targetData = mRepository.getNotesBySearchTerm(searchTerm);
            }
            // 添加新的数据源
            mFilteredNotes.addSource(targetData, mFilteredNotes::setValue);
        });
    }

    // 对外提供观察过滤后数据的入口
    public LiveData<List<Note>> getFilteredNotes() {
        return mFilteredNotes;
    }

    // 设置搜索关键词
    public void setSearchQuery(String searchTerm) {
        mSearchQuery.setValue(searchTerm);
    }

    // ... 插入方法不变
    public void insert(Note note) { mRepository.insert(note); }
}

4. Fragment中修改观察对象并绑定SearchView

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // ... 其他初始化代码不变

    mNoteViewModel = new ViewModelProvider(requireActivity()).get(NoteViewModel.class);

    // 改为观察过滤后的笔记数据
    mNoteViewModel.getFilteredNotes().observe(getViewLifecycleOwner(), notes -> {
        Log.d("MainPage", "onChanged: " + notes);
        adapter.submitList(notes);
    });

    // 绑定SearchView的输入监听
    SearchView searchView = binding.searchView;
    searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String query) {
            mNoteViewModel.setSearchQuery(query);
            return true;
        }

        @Override
        public boolean onQueryTextChange(String newText) {
            mNoteViewModel.setSearchQuery(newText);
            return true;
        }
    });

    return binding.getRoot();
}

为什么之前的方法无效?

Room返回的LiveData是自带数据库监听的不可变数据源,如果把它赋值给MutableLiveData后手动调用setValue,会破坏Room原本的数据源监听逻辑,导致无法获取原始数据更新。

用MediatorLiveData可以灵活切换不同的LiveData数据源,既保留Room自动感知数据库变化的特性,又能根据搜索关键词动态切换查询结果,完美解决过滤通知的问题。

内容的提问来源于stack exchange,提问作者ahmetkca

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 17:14:55