Observer Pattern en Android

Índice

Un ejemplo del mundo real para entender este patrón sería el siguiente:

Esto, tan fácil y tan sencillo, es el patrón observador. En el mismo a los subscriptores los llamaremos Observers, y al emisor que les envía la información, Subject.

Hablando de forma menos física y más digital, decimos que con este patrón se establece una relación one-to-many en la que, cuando el estado del Subject se modifica, los Observer automáticamente se actualizan. En esta relación lo único que necesita saber el Subject de los Observer es que implementan una clase abstracta o interfaz (según las posibilidades que nos dé el lenguaje con el que trabajemos), y que cuando modifique su estado ha de llamar al método de la interfaz que implementen los Observer para que se haga efectiva dicha actualización.

Observer.svg

Figure 1: UML-Observer-Wikipedia

Hay muchísima información en la web de este patrón, es extremadamente útil y sencillo. Pero si queréis alguna fuente más clásica y más concreta, os recomiendo no ya el GOF, sino el maravilloso Head First Design Pattern. Seguro que en aquél está bien explicado, de hecho creo que es el origen del patrón, pero con el segundo os quedará meridianamente claro.

Observer

Un Observer implementará la interface con el mismo nombre, y lo único que hará será actualizarse cada vez que reciba nueva información por parte del emisor de la misma (el Subject).

public interface Observer {
    void update();
}

Supongamos por ejemplo que queremos ser notificados de cualquier touchdown que ocurra any given sunday, es decir, cualquier anotación que tenga lugar en una jornada de domingo de la NFL. Para ello, implementaríamos la interface Observer en una clase encargada de recibir dichos resultados:

public class TDProcessor implements Observer {
    @Override public void update(TouchDown touchDown) {
        doSomethingWithTouchDown(touchDown);
    }
}
public interface Observer {
    void update(TouchDown touchDown);
}

Y ya está. Solo nos quedaría procesar la información recibida. Así de sencillo.

Subject

El Subject es el elemento que se encarga de proporcionar los datos a los Observer. Pero para poder informar a aquéllos antes deben de haberse registrado. En Java, la clase que realice dicha función implementará la correspondiente interface:

public interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

Además, para poder saber qué elementos se han subscrito, las clases que la implementen (podremos según el caso tener más de un Subject) deberán tener una lista en la cual almacenen los Observer que se registren. Podríamos usar una clase abstracta que incluya la lista y heredar de ella, pero conceptualmente casa mejor el concepto de interface.

Así, en nuestro ejemplo, la central que informa de las anotaciones tendrá una implementación parecida a la siguiente:

public class TDGenerator implements Subject {
    List<Observer> observers = new ArrayList<>();
    TouchDown touchDown;

    @Override public void registerObserver(Observer o) {
        observers.add(o)
    }
    @Override public void removeObserver(Observer o) {
        observers.remove(o)
    }
    @Override public void notifyObservers() {
        for (Observer o: observers)
            o.update(touchDown);
    }
    public void setTouchDown(TouchDown touchDown) {
        this.touchDown = touchDown;
        this.notifyObservers();
    }
}

Podemos ver que cada vez que tengamos un nuevo resultado (que podemos obtener desde un medio externo, una petición HTTP o de cualquier forma que se nos ocurra) se notificará a los Observer que se hayan registrado de dicho cambio. Así de sencillo.

Ejemplo de aplicación en Android

Para hacerlo un poco más divertido, vamos a implementar una aplicación de Android compuesta por una Activity y tres Fragments asociados los cuales recibirán los datos de la primera. Éstos tres se mostrarán en un ViewPager y cada uno mostrará unos datos distintos. Los datos que irá enviando (emitiendo) la Activity (realmente el Presenter asociado a través de nuestro modelo, ya sea una api rest, una adquisición de datos en tiempo real o cualquie cosa que se nos pueda ocurrir) serán recibidos por los Presenter de cada Fragment, los cuáles mostrarán solamente los datos que les interese a cada uno desechando el resto.

Generando los datos

Imaginemos que tenemos una fuente de datos que nos informa del coste de las acciones de varias empresas, pero nosotros solamente estamos interesados en dos de ellas. En nuestro modelo, la clase que se encarga de generar los valores (de forma totalmente aleatoria) será:

public class StockPrice {
    public enum OPTIONS { INDRA, GOWEX, TELEPIZZA, BANCAJA };
    public Receiver receiver;

    public StockPrice(Receiver receiver) {
        this.receiver = receiver;
        this.emit();
    }

    private void emit() {
        final Handler handler = new Handler();
        new Thread(new Runnable() {
            @Override public void run() {
                while (true) {
                    final Pair<OPTIONS, Double> price = getPrice();
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            receiver.priceChanged(price);
                        }
                    });
                }
            }
        }).start();
    }

    private Pair<OPTIONS, Double> getPrice() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new Pair<>(getRandomOption(), Math.random()*2000);
    }

    private OPTIONS getRandomOption() {
        int option = (int) Math.floor(Math.random() * 4);
        return OPTIONS.values()[option];
    }
}

Vemos que tiene un atributo que implementa la interface Receiver. Éste será el mismo que el Subject. De hecho podríamos haber incluido el método priceChanged() en la propia interface Subject, pero conceptualmente son cosas distintas, así que es mejor tener las funcionalidades bien separadas. Cada vez que se genera un nuevo precio, el receptor lo recibirá. La generación se hace de forma asíncrona para poder modelar cualquier evento que nos ocupe un tiempo elevado y que no pueda ser ejecutado en el UIThread.

Obteniendo los datos

La clase que se encarga de recibir los datos será el Presenter asociado a la MainActivity:

public class ActivityPresenter implements Receiver {
    private Pair<StockPrice.OPTIONS, Double> price;
    ...
    @Override public void priceChanged(Pair<StockPrice.OPTIONS, Double> newPrice) {
        price = newPrice;
    }
}

public interface Receiver {
    void priceChanged(Pair<StockPrice.OPTIONS, Double> newPrice);
}

Emitiendo datos desde el Subject a los Observer

Esta misma clase será el Subject del Observer Pattern, y por lo tanto tendrá también los métodos de la interface correspondiente:

public class ActivityPresenter implements Subject, Receiver {
    private List<Observer> observers;
    ...
    @Override public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override public void notifyObservers() {
        for (Observer observer: observers)
            observer.update(price);
    }
}

Recepción de datos por parte de los Observer

Ya solo nos queda recibir y procesar los datos emitidos por el Subject. Los Observer serán los Presenter asociados a los Fragment, y serán ellos los que decidirán que datos mostrar y cuáles no. Tendremos tres Fragment, uno que mostrará todos los datos emitidos y otros dos que solo mostrarán aquéllos que les interesan:

public abstract class FragmentPresenter implements Observer {
    protected OnPriceChangedListener mListener;

    public void onCreate(OnPriceChangedListener listener) {
        this.mListener = listener;
    }

    public void onCreateView(OnFragmentInteractionListener listener) {
        listener.onFragmentInteraction(this);
    }
}

public class FragPresenterA extends FragmentPresenter {
    @Override public void update(Pair<StockPrice.OPTIONS, Double> pair) {
        mListener.setPrice(pair);
        Log.e("********", pair.first + " - " + pair.second);
    }
}

public class FragPresenterB extends FragmentPresenter {
    private final StockPrice.OPTIONS NAME = StockPrice.OPTIONS.GOWEX;

    @Override public void update(Pair<StockPrice.OPTIONS, Double> pair) {
        if (pair.first == NAME)
            mListener.setPrice(pair);
    }
}

public class FragPresenterC extends FragmentPresenter {
    private final StockPrice.OPTIONS NAME = StockPrice.OPTIONS.BANCAJA;

    @Override public void update(Pair<StockPrice.OPTIONS, Double> pair) {
        if (pair.first == NAME)
            mListener.setPrice(pair);
    }
}

El atributo de tipo OnPriceChangedListener simplemente será el Fragment al que cada Presenter está asociado, y que se encargará de reflejar los datos recibidos en pantalla.

public interface OnPriceChangedListener {
    void setPrice(Pair<StockPrice.OPTIONS, Double> pair);
}
public class CustomFragment extends Fragment implements OnPriceChangedListener
{
    private ArrayList<String> mData = new ArrayList<>();
    private ArrayAdapter<String> mAdapter;
    ...
    @Override
    public void setPrice(Pair<StockPrice.OPTIONS, Double> pair) {
        mData.add(pair.first.toString() + " : " + pair.second.toString());
        mAdapter.notifyDataSetChanged();
        mListView.setSelection(mData.size() - 1);
    }
}

Comentario sobre arquitectura de la aplicación
El ejemplo implementa el patrón MVP cuya explicación queda fuera del objetivo de este artículo. De todas formas, para todo aquél que le interese, o que no entienda muy bien las razones por las que está implementada esta aplicación de esta forma concreta, recomienda esta referencia, y de regalo esta otra. Y si queréis ver en vez de MVP, el patrón MVVM en acción, el siguiente repositorio y la presentación asociada son fantásticas.

Comentario sobre filtrado de resultados
Solo a modo de curiosidad, podríamos, en vez de filtrar los resultados que queremos mostrar, tener varias fuentes (Subject) distintas de información y subscribirnos a unas u otras según qué nos interese. La opción elegida, además de más sencilla, es parecida a la forma de trabajar de RxJava.

Resultado

A continuación se puede ver el resultado de la aplicación. Tenemos tres pestañas cada una de las cuáles muestra solamente los datos que nos interesan.

GIFrecord_2015-12-21_175006.gif?raw=true

Figure 2: gif

Implementación en Java

El patrón Observer viene de fábrica en Java, en concreto en java.util.Observable y java.util.Observer. Jamás he gastado dichas interfaces, pero es interesante que os quedéis con el hecho de que nuestro Subject es equivalente a este Observable: como puede ser observado, puede ser seguido para ver qué cambios le ocurren, por eso también se le puede llamar de esta forma. De hecho, SPOILER!!!!! en la programación reactiva es así como nos referiremos a él.

Uso de buses en Android

Como último comentario, podemos no meternos en estos líos y usar algún bus en Android para este mismo trabajo. Conozco dos, Otto y EventBus, cada uno resuelve el problema de una forma. Recomiendo el segundo, de hecho es el único que he gastado. Aquí las razones (en mi caso básicamente la eficiencia).

Enlaces

A continuación el repositorio donde se encuentra el código de ejemplo de la aplicación de Android.
https://github.com/ingeniaoprograma/ObserverPattern_ActivityFragments