Writing a Transformation

Another RxJava transformation operator is filter(). This takes a stream of objects and a function that tests each object and returns true for the ones to be sent downstream. The ones that test out to false are dropped. Hence, the stream becomes filtered by whatever rule is encoded in that function.

Transformations does not have a filter() method, but we can write one, to see what a transformation method looks like.

Earlier in the book, we had the Sensor/LiveList sample, where we had a LiveData reporting sensor events, specifically the light level. The Sensor/LiveFilter sample project is a clone of that project, one that introduces a filter, to only report those readings that fall between 20 and 40 lux.

To that end, we have a LiveTransmogrifiers class that serves as a home for our transformation methods:

package com.commonsware.android.livedata;

import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MediatorLiveData;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;

class LiveTransmogrifiers {
  interface Confirmer<T> {
    boolean test(T thingy);
  }

  @MainThread
  static <X> LiveData<X> filter(@NonNull LiveData<X> source,
                                @NonNull final Confirmer<X> confirmer) {
    final MediatorLiveData<X> result=new MediatorLiveData<>();

    result.addSource(source, x -> {
      if (confirmer.test(x)) {
        result.setValue(x);
      }
    });

    return(result);
  }
}

The RxJava filter() operator uses a Predicate as the function for testing an object to determine if it should be passed or not. Unfortunately, Predicate is part of the Java 8 classes added in Android 7.0, and so it is unavailable for older devices. So, we have a Confirmer interface that fills that role. The test() method on a Confirmer needs to return true for objects that should pass the filter, false otherwise.

The filter() method on LiveTransmogrifiers takes a LiveData of some type and a Confirmer of that type. It then uses a MediatorLiveData, which is a LiveData object that can chain onto an existing LiveData and expose the onChanged() method for outside parties to use. In this case, our lambda uses the Confirmer to see if the new object passes the test(), and if it does, we call setValue() on the MediatorLiveData to have that object flow along to anything that observes that MediatorLiveData. filter() then returns that MediatorLiveData. The net effect is as if filter() wraps the original LiveData in another LiveData that applies our filtering rule.

We can now use filter() to limit the readings that we get from the sensor:

    final LiveData<SensorLiveData.Event> filtered=
      LiveTransmogrifiers.filter(state.sensorLiveData,
        event -> (event.values[0]>20 && event.values[0]<40));

    filtered.observe(this, event -> adapter.add(event));

We pass our original SensorLiveData to filter(), along with a Confirmer (in the form of a lambda expression) that sees if the light level is between 20 and 40. Then, we observe the results of the filter() call and only add those objects — not every reading from the SensorLiveData — to the EventLogAdapter.

The net result, if you compare and contrast the output of this sample with the original, is that while the original reports everything, this new sample only reports a subset of the data.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.