Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I perform LiveData transformations on a background thread?

Tags:

I have a need to transform one type of data, returned by a LiveData object, into another form on a background thread to prevent UI lag.

In my specific case, I have:

  • MyDBRow objects (POJOs consisting of primitive longs and Strings);
  • a Room DAO instance emitting these via a LiveData<List<MyDBRow>>; and
  • a UI expecting richer MyRichObject objects (POJOs with the primitives inflated into e.g. date/time objects)

so I need to transform my LiveData<List<MyDBRow>> into a LiveData<List<MyRichObject>>, but not on the UI thread.

The Transformations.map(LiveData<X>, Function<X, Y>) method does this needed transformation, but I can't use this because it executes the transformation on the main thread:

Applies the given function on the main thread to each value emitted by source LiveData and returns LiveData, which emits resulting values.

The given function func will be executed on the main thread.

What is a clean way to make LiveData transformations occur:

  1. somewhere off the main thread, and
  2. only as needed (i.e. only when something is observing the intended transformation)?
like image 690
Alex Peters Avatar asked Nov 19 '17 07:11

Alex Peters


People also ask

Does LiveData work on background thread?

The original, “source” LiveData can be monitored by a new Observer instance. This Observer instance, when source LiveData is emitted, can prepare a background thread to perform the needed transformation and then emit it via a new, “transformed” LiveData .

Which of these are the public transformations methods for the LiveData?

Conclusions. Use map, switchMap and distinctUntilChanged for all LiveData transformations.

Can we use LiveData without ViewModel?

With LiveData, this communication is safer: the data will only be received by the View if it's active, thanks to its lifecycle awareness. The advantage, in short, is that you don't need to manually cancel subscriptions between View and ViewModel.


1 Answers

  • The original, “source” LiveData can be monitored by a new Observer instance.
  • This Observer instance, when source LiveData is emitted, can prepare a background thread to perform the needed transformation and then emit it via a new, “transformed” LiveData.
  • The transformed LiveData can attach the aforementioned Observer to the source LiveData when it has active Observers, and detach them when it doesn't, ensuring that the source LiveData is only being observed when necessary.

The question gives an example source LiveData<List<MyDBRow>> and needs a transformed LiveData<List<MyRichObject>>. A combined transformed LiveData and Observer could look something like this:

class MyRichObjectLiveData         extends LiveData<List<MyRichObject>>         implements Observer<List<MyDBRow>> {     @NonNull private LiveData<List<MyDBRow>> sourceLiveData;      MyRichObjectLiveData(@NonNull LiveData<List<MyDBRow>> sourceLiveData) {         this.sourceLiveData = sourceLiveData;     }      // only watch the source LiveData when something is observing this     // transformed LiveData     @Override protected void onActive()   { sourceLiveData.observeForever(this); }     @Override protected void onInactive() { sourceLiveData.removeObserver(this); }      // receive source LiveData emission     @Override public void onChanged(@Nullable List<MyDBRow> dbRows) {         // set up a background thread to complete the transformation         AsyncTask.execute(new Runnable() {             @Override public void run() {                 assert dbRows != null;                 List<MyRichObject> myRichObjects = new LinkedList<>();                 for (MyDBRow myDBRow : myDBRows) {                     myRichObjects.add(MyRichObjectBuilder.from(myDBRow).build());                 }                 // use LiveData method postValue (rather than setValue) on                 // background threads                 postValue(myRichObjects);             }         });     } } 

If multiple such transformations are needed, the above logic could be made generic like this:

abstract class TransformedLiveData<Source, Transformed>         extends LiveData<Transformed>         implements Observer<Source> {     @Override protected void onActive()   { getSource().observeForever(this); }     @Override protected void onInactive() { getSource().removeObserver(this); }      @Override public void onChanged(@Nullable Source source) {         AsyncTask.execute(new Runnable() {             @Override public void run() {                 postValue(getTransformed(source));             }         });     }      protected abstract LiveData<Source> getSource();     protected abstract Transformed getTransformed(Source source); } 

and the subclass for the example given by the question could look something like this:

class MyRichObjectLiveData         extends TransformedLiveData<List<MyDBRow>, List<MyRichObject>> {     @NonNull private LiveData<List<MyDBRow>> sourceLiveData;      MyRichObjectLiveData(@NonNull LiveData<List<MyDBRow>> sourceLiveData) {         this.sourceLiveData = sourceLiveData;     }      @Override protected LiveData<List<MyDBRow>> getSource() {         return sourceLiveData;     }      @Override protected List<MyRichObject> getTransformed(List<MyDBRow> myDBRows) {         List<MyRichObject> myRichObjects = new LinkedList<>();         for (MyDBRow myDBRow : myDBRows) {             myRichObjects.add(MyRichObjectBuilder.from(myDBRow).build());         }         return myRichObjects;     } } 
like image 115
Alex Peters Avatar answered Sep 19 '22 06:09

Alex Peters