Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practice: Runtime filters with Room and LiveData

Tags:

I am working on a screen that shows the contents of a Room wrapped DB using a recycler. The adapter gets the LiveData from a ViewModel that hides the query call on the Room DAO object. So, the LiveData object is actually a ComputableLiveData object that is aware of changes to the Room DB.

Now I want to add filter options to the screen. Where / how would I implement this in this Room-LiveData-ViewModel setup?

Should the adapter or ViewModel "postfilter" the results in the LiveData? Should I requery the data from room for every filter change? Can I reuse the underlying (Computable)LiveData for that? If not, should I really create new LiveData for every filter change?

A similar question is discussed here: Reload RecyclerView after data change with Room, ViewModel and LiveData

like image 297
Oderik Avatar asked Feb 13 '18 15:02

Oderik


People also ask

Why use flow instead of LiveData?

StateFlow and LiveData have similarities. Both are observable data holder classes, and both follow a similar pattern when used in your app architecture. The StateFlow and LiveData do behave differently: StateFlow requires an initial state to be passed into the constructor, while LiveData does not.

Is LiveData deprecated?

This function is deprecated. If LiveData already has data set, it will be delivered to the onChanged. The observer will only receive events if the owner is in Lifecycle.

What is difference between LiveData and MutableLiveData?

By using LiveData we can only observe the data and cannot set the data. MutableLiveData is mutable and is a subclass of LiveData. In MutableLiveData we can observe and set the values using postValue() and setValue() methods (the former being thread-safe) so that we can dispatch values to any live or active observers.

In which method should you observe a LiveData object?

Attach the Observer object to the LiveData object using the observe() method. The observe() method takes a LifecycleOwner object. This subscribes the Observer object to the LiveData object so that it is notified of changes. You usually attach the Observer object in a UI controller, such as an activity or fragment.


2 Answers

I'm working in a similar problem. Initially I had RxJava but now I'm converting it to LiveData.

This is how I'm doing inside my ViewModel:

// Inside ViewModel MutableLiveData<FilterState> modelFilter = new MutableLiveData<>(); LiveData<PagedList<Model>> modelLiveData; 

This modelLivedata is constructed in the following way inside view model constructor:

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

When the view model receives another filter to be applied, it does:

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

Then, this new filter will be "transformed" in a new livedata value which will be sent to the observer (a fragment).

The fragment gets the livedata to observe through a call in the view model:

// In ViewModel public LiveData<PagedList<Model>> getModelLiveData() {      return modelLiveData;  } 

And inside my fragment I have:

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

I hope it helps.

like image 162
Francisco Junior Avatar answered Oct 19 '22 12:10

Francisco Junior


So, I ended up doing it like this:

  • The fragment fowards the filter state to the ViewModel. Side effect: the filter state may be used by multiple (i.e. subsequent due to configuration change) fragment instances. Maybe you want that, maybe not. I do.
  • The ViewModel holds a MediatorLiveData instance. It has a single source: The Room DB LiveData object. The source simply forards changes to the mediator. If the filter is changed by the fragment, the source is swapped by a requery.

Answering my detailed questions:

  • No postfiltering
  • Yes, requery on filter change
  • I don't reuse the ComputableLiveData (not sure wether it would be possible)

Regarding the discussion in the comments:

  • I don't apply paging

Final note on Room: Am I wrong or do I need to write seperate DAO methods for every filter combination I want to apply? Ok, I could insert optional parts of the select statement via a String, but then I would lose the benefits of Room. Some kind of statement builder that makes statements composable would be nice.

EDIT: Please note the comment by Ridcully below. He mentions SupportSQLiteQueryBuilder together with @RawQuery to address the last part I guess. I didn't check it out yet though.

Thanks to CommonsWare and pskink for your help!

like image 34
Oderik Avatar answered Oct 19 '22 10:10

Oderik