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

基于Room LiveData与ViewModel的搜索过滤异常问题求助

Alright, let's break down why your search filtering is misbehaving and fix it step by step. The core problems lie in how you're initializing your ViewModel and handling dynamic search queries with Architecture Components.


What's Going Wrong?

  1. Static ViewModel Data Source: Your DataViewModelSearch sets currentData once in its constructor. Every time you call refresh(), you create a new factory—but ViewModelProvider reuses the existing DataViewModelSearch instance for your Activity. That means the new search text never actually updates the underlying LiveData.
  2. Duplicate Observers: Each text change creates a new ViewModel and adds a new Observer. This leads to multiple observers listening for data updates, causing inconsistent adapter behavior and potential memory leaks.
  3. Mixed Data Sources: Your Activity starts by observing DataListViewModel instead of your search-specific ViewModel, leading to mismatched initial and filtered data.

Fixes Step by Step

1. Refactor DataViewModelSearch for Dynamic Queries

We'll use MutableLiveData to hold the search query, and Transformations.switchMap to automatically trigger new database queries when the query changes:

public class DataViewModelSearch extends AndroidViewModel {
    private final MutableLiveData<String> searchQuery = new MutableLiveData<>();
    private final LiveData<List<Data>> currentData;
    private final DataRepository repository;

    public DataViewModelSearch(@NonNull Application application) {
        super(application);
        repository = new DataRepository(application);
        
        // Automatically switch queries when search text changes
        currentData = Transformations.switchMap(searchQuery, query -> {
            // Handle empty query to return all data
            String searchPattern = TextUtils.isEmpty(query) ? "%%" : "%" + query + "%";
            return repository.getAllSearch(searchPattern);
        });
    }

    // Update the search query externally
    public void setSearchQuery(String query) {
        searchQuery.setValue(query);
    }

    public LiveData<List<Data>> getAllSearch() {
        return currentData;
    }

    // Simplified factory (no need to pass search text anymore)
    public static class ModelFactorySearch extends ViewModelProvider.NewInstanceFactory {
        @NonNull private final Application application;

        public ModelFactorySearch(@NonNull Application application) {
            super();
            this.application = application;
        }

        @NonNull @SuppressWarnings("unchecked")
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            if (modelClass.isAssignableFrom(DataViewModelSearch.class)) {
                return (T) new DataViewModelSearch(application);
            }
            throw new IllegalArgumentException("Unknown ViewModel class");
        }
    }
}

2. Fix SearchActivity to Use a Single ViewModel Instance

Get rid of the repeated ViewModel creation—initialize it once, then just update the search query when text changes:

public class SearchActivity extends AppCompatActivity {
    private SearchAdapter adapter;
    RvObserver observer;
    EditText etFilter;
    RecyclerView recyclerView;
    private DataViewModelSearch searchViewModel; // Single instance

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_search);
        
        // Toolbar and RecyclerView setup (keep your existing code here)
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        observer = new RvObserver();
        etFilter = findViewById(R.id.et_filter);
        recyclerView = findViewById(R.id.rv_search);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this, LinearLayoutManager.VERTICAL);
        dividerItemDecoration.setDrawable(this.getResources().getDrawable(R.drawable.divider));
        recyclerView.addItemDecoration(dividerItemDecoration);
        adapter = new SearchAdapter(onSearchClickListener, this);
        recyclerView.setAdapter(adapter);

        // Initialize the search ViewModel once
        DataViewModelSearch.ModelFactorySearch factory = new DataViewModelSearch.ModelFactorySearch(getApplication());
        searchViewModel = ViewModelProviders.of(this, factory).get(DataViewModelSearch.class);

        // Observe data ONCE—updates will trigger automatically when query changes
        searchViewModel.getAllSearch().observe(this, data -> {
            adapter.setData(data);
            adapter.notifyDataSetChanged();
        });

        // Load all data initially
        searchViewModel.setSearchQuery("");

        // Update search query on text change
        etFilter.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                searchViewModel.setSearchQuery(charSequence.toString());
            }

            @Override
            public void afterTextChanged(Editable editable) {}
        });
    }

    // Keep your existing onResume/onPause methods here
    @Override
    protected void onResume() {
        super.onResume();
        adapter.registerAdapterDataObserver(observer);
    }

    @Override
    protected void onPause() {
        super.onPause();
        adapter.unregisterAdapterDataObserver(observer);
    }
}

3. Optional: Case-Insensitive Search

If you want your search to ignore case, update your DAO query to use SQLite's COLLATE NOCASE:

@Query("SELECT * FROM Data WHERE title LIKE :search COLLATE NOCASE")
LiveData<List<Data>> getAllSearch(String search);

Why This Works

  • Transformations.switchMap: This listens for changes to the search query, automatically cancels the previous database subscription, and subscribes to the new query—perfect for dynamic filtering.
  • Single ViewModel Instance: Follows Architecture Component best practices, ensuring your ViewModel stays tied to the Activity lifecycle and avoids memory leaks.
  • Unified Data Flow: All data (initial load and filtered results) comes from the same ViewModel, eliminating inconsistencies.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:34:36