Observer Pattern en Android
Índice
Un ejemplo del mundo real para entender este patrón sería el siguiente:
- Una editorial publica una revista.
- Nosotros queremos recibir cada ejemplar publicado de la misma cómodamente en nuestro buzón, sin necesidad de ir al kiosko a comprarla.
- Para ello, nos subscribimos, y cada vez que sale un nuevo número nos encontramos con nuestro ejemplar sin tener que hacer una petición de compra específica del mismo.
- Cuando por la razón que sea ya no queremos recibir más números, simplemente damos de baja nuestra subscripción.
- Una vez nos damos de baja, no sabemos nada del emisor de la revista, que puede seguir enviando números a aquellas personas que sigan subscritas.
- Si en el futuro queremos volver a recibirla podemos volver a subscribirnos, pero sólo recibiremos los números una vez hagamos efectiva nuestra nueva subscripción. Así, los números emitidos en el período entre nuestra baja y nuestra nueva alta no podrán ser recuperados.
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.
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.
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