Motivación para hablar sobre Model-View-Presenter puro:
Es bastante complicado, aún a día de hoy, encontrar en la industria un estándar en lo que se refiere a arquitectura en el desarrollo de aplicaciones móviles en Android. Es cierto que desde hace tiempo se plantea un MVP muy básico para romper con los God-Object (Activities responsables de absolutamente todo) y, desde no hace tanto, se está adoptando un MVVM propuesto por Google usando sus componentes de arquitectura (ViewModel, LiveData, LifeCycleObserver…). Si bien estas arquitecturas (sobre todo MVVM) cumplen con el propósito de desacoplar componentes y hacerlos testeables y reutilizables, aún nos encontramos con varios puntos que nos hacen plantearnos si ésta es la mejor arquitectura que podemos utilizar:

 

1.En el primer caso, la vista y el presenter se conocen, así como el presenter y el modelo. Respecto al MVVM, si bien el ViewModel no conoce a la vista, la vista sí que conoce al ViewModel, lo que hace que reutilizar vistas con ViewModels distintos sea complicado. Es decir, se respeta el principio de inversión de dependencias (DIP) únicamente en una dirección (vista => servicio, y no servicio => vista). Esta problemática está, aún a día de hoy, encima de la mesa, ya que una inversión de dependencias en ambas direcciones nos proporciona una mayor cohesión y un menor acoplamiento, pero también aumenta la complejidad. Queda a disposición del lector determinar si merece la pena este trade-off.

 

 

DIP ambos sentidos

MVVM

Complejidad

+

Acoplamiento

+

Cohesión

+

2. En cuanto a la comunicación entre las capas, en el caso del MVP clásico nos encontramos que se realiza a través de callbacks (lo que eventualmente convertirá nuestra aplicación en un callback hell), mientras que en el MVVM se utiliza LiveData, que, si bien nos permite prescindir de los callbacks, no nos proporciona una gran cantidad de operadores para manipular los datos (en el momento de escribir esto, únicamente tenemos map y switchmap).

Son estos dos motivos principalmente los que nos llevan a intentar buscar una mejor solución a la hora de escribir nuestras aplicaciones.

 

Model-View-Presenter puro ( MVP Puro or MVPP )


En nuestro caso, hemos optado por una arquitectura bautizada cómo Model-View-Presenter puro (que también solemos etiquetar como controllerless architecture) que nos permite desacoplar totalmente la vista de la capa del modelo. Esto es posible gracias al presenter, cuya única función es la de conectar una única vista con un único servicio de aplicación (también llamado interactor, o caso de uso); de forma que, para construir una pantalla, podemos llegar a usar N presenters, uno por cada par vista-servicio.

Para ilustrar lo expuesto en este artículo he publicado un pequeño proyecto de ejemplo en github ( Model-View-Presenter puro): MVPP en Github

Así pues, podemos entender nuestra vista y nuestro servicio como cajas negras que emiten eventos y reciben información (a través de “cables” de entrada y salida), y nuestro presenter será el encargado de conectar los cables de entrada de un componente a los de salida de otro y viceversa.

Además, el hecho de usar lambdas para comunicar la vista con el servicio, nos permite no exponer directamente los Observable entre estos componentes. De esta forma podríamos usar RxJava en nuestro servicio, de forma que podríamos manipular los datos con todos los operadores que esta librería nos proporciona; y LiveData en la parte de la vista, lo que nos permitirá hacer una implementación que esté al tanto de los cambios en el ciclo de vida de nuestra activity, o incluso que utilice el ViewModel proporcionado por Google a tal efecto.

 

Vista
En el caso de Android, la implementación de nuestra vista se corresponderá a una Activity o un Fragment (Fragment en el proyecto de ejemplo), pero bien podría ser un ViewModel o incluso un componente visual. No se contempla aquí el caso de que la vista sea una Activity formada por N fragments, ya que cada fragment tendrá M presenters y podríamos asumir que la vista sería cada uno de esos fragments, aunque luego se agrupen en un único (o incluso dentro de otro fragment).

 

Servicio
Nuestro servicio será el encargado de aplicar toda la lógica de negocio y de orquestar los diferentes servicios de dominio si los hubiera, o, directamente los repositories que se encargarán de proporcionar/almacenar información a/desde la aplicación.

 


Capa de infraestructura
En la capa que se encarga de proveer de datos a la aplicación, o de almacenar los que sean necesarios (Gateway, BD, SharedPreferences, Caché…). Cada uno de estos canales de comunicación se implementará con un patrón repository, los cuales serán inyectados en el servicio donde se necesiten.

 

¿Qué ganamos?
Es gracias a tener la dependencia servicio => vista invertida, y no únicamente la dependencia vista => servicio, que podemos hacer cosas tales como:

  • Añadir/quitar en runtime más listeners a la vista
  • Usar una misma vista con más de un “listener” a la vez. Por ejemplo cuando, en el click de un mismo botón, tenemos que comunicarnos con el servidor y lanzar un evento de tracking.
  • Al debuggar nuestra aplicación, podremos ver en un único sitio, el/los presenter(s), todos los flujos de eventos.
  • Poder aplicar el diseño “presenter first“, es decir, definir, antes que los componentes de Vista y Servicio, definir las interfaces de los dos, como métodos y eventos

 

Testing
A la hora de escribir tests para nuestra aplicación es importante que podamos testear unidades aisladas de código, que no tengan efectos colaterales en otras partes de la aplicación, y cuyas dependencias puedan ser mockeadas (esto es, que dichas unidades de código no dependan de lo que hagan otras partes de la aplicación). Gracias a que en esta arquitectura, nuestros componentes son cajas negras que reciben eventos y emiten información, es fácil comprobar que, al recibir un evento X, se emite una información Y.
La capa de infraestructura (data) también es fácilmente testeable ya que todo lo que tenemos que hacer es mockear las respuestas del servidor, y para ello usaremos MockWebServer de OkHttp.

 

¿Hacia dónde vamos?
Actualmente existen arquitecturas como Redux y Redux-saga que trabajan bajo el principio de inversión de dependencias, en el sentido de que son totalmente Event-Driven. Observando la evolución de las arquitecturas en frontend no es descabellado pensar que en Android nos estamos acercando a una arquitectura parecida a Redux.

 

Mejoras
Este no deja de ser un pequeño proyecto desarrollado con el único propósito de mostrar los temas que se tocan en este artículo, por lo que hay infinidad de aspectos en los que se puede mejorar, siendo algunos de estos:

  1. ViewModel: Se puede añadir una capa adicional entre la Vista y el Presenter que se encargue de guardar el estado de la vista. Además, este ViewModel se podría inyectar con DataBinding directamente en el XML y, usando LiveData, hacer el binding de los componentes visuales a los Observable de LiveData. Además investigar de qué forma se podría realizar este binding cuando tenemos que lidiar con un Adapter es un reto en el que estamos trabajando ahora mismo.
  2. Autenticación: Existe una limitación por parte de la API de Github donde una misma IP no puede realizar más de un número determinado de llamadas (https://api.github.com/rate_limit) sin autenticar un usuario; por lo que añadir una autenticación básica con OAuth2 a través de la web de Github sería un buen punto para empezar a mejorar esta aplicación
  3. Paginación: Actualmente no se ha implementado ningún mecanismo de paginación por lo que la aplicación no muestra más de un número determinado de resultados para una búsqueda, por lo que implementar un mecanismo para paginar podría ser un reto interesante dentro de esta arquitectura.

 

 

Suscríbete a nuestro newsletter para estar al día de Model-View-Presenter puro y desarrollo movil en general !

Si este artículo sobre Model-View-Presenter puro te gustó, te puede interesar:

 

Tendencias en aplicaciónes móviles

Patrón MVP en iOS

Debugging con Charles Proxy en Android emulator

Por qué Kotlin?   

Integración Continua en iOS usando Fastlane y Jenkins  

Meetups de arquitectura de software