Kotlin: Primera toma de contacto con MVP en Android #AndroidMeetsKotlin
Francisco Manuel López Jurado Colaboraciones 15/12/2017
Hasta ahora hemos desarrollado todo nuestro código sin aplicar ningún patrón en cuanto a la arquitectura, es por esto que vamos a realizar una introducción a MVP, Kotlin y Android. Para ello seguiremos haciendo uso de Dagger2 como nuestro inyector de dependencias.
Sin más, ¡empezamos!
¿Por qué MVP?
MVP (Modelo Vista Presentador) es un patrón de arquitectura (no un arquitectura) derivado del MVC (Modelo Vista Controlador) que pretende modelar la capa de presentación en nuestros desarrollos abstrayendo la vista de lógica. En resumidas cuentas, pretende trasladar toda la lógica de presentación e interacción del usuario a una entidad llamada Presenter.
Capas de este patrón
- Modelo. Se encarga de la gestión del modelo de la app y contiene las clases que llamaríamos de lógica de negocio.
- Vista. Se encarga de mostrar los datos (Fragments, CustomViews, Activitis, Adapters, etc).
- Presentador. Está situado entre el modelo y la vista, permitiendo conectar la interfaz gráfica con los datos.
¿Vale de algo aplicarlo o es sólo humo?
En un principio nos dirán que aplicando de forma correcta este patrón conseguiremos tener nuestros Presenters totalmente desacoplados por lo que podríamos utilizarlos en otras plataformas. Pero, esto es prácticamente imposible ya que la gran intrusividad de Android y la fuerte vinculación del ciclo de vida de las vistas hacen casi imposible esta tarea. Sin embargo, una de las principales características que aporta este patrón es que desde la capa de los Presenters hacia abajo desconocen completamente el SDK de Android haciéndolos mucho más testeables y limpios.
¿Cómo lo abstraemos?
Lo que se suele hacer es definir interfaces y utilizar inyectores de dependencia (ver el post sobre Dagger2). De esta forma, migraremos la lógica a otras clases o manejadores que se encargarán de tratar estos elementos. Por ejemplo, el típico uso de SharedPreferences en nuestra app pasaría a estar en una clase denominada SharedPreferenceManager que se encargará de su gestión y proveerá de una interfaz para la comunicación con la misma. También entra en juego nuestro inyector de dependencias que se encargará de que todo nos llegue correctamente instanciado y listo para usar.
¿Algún obstáculo? Sí, el ciclo de vida…
Es importante que nuestro Presenter sepa en todo momento en qué estado está la vista que está manejando puesto que si intentamos presentar datos en estados erróneos acabaríamos provocando un crash de la app. Para solventar este pequeño inconveniente, vamos a utilizar un flag de control para la vista:
- isInForeground. Indicará si la vista está en primer plano o no. De este modo nos evitaremos errores a la hora de mostrar diálogos, toasts, etc.
Empieza lo bueno
Ya tenemos los conocimientos básicos en Kotlin, en un inyector de dependencia (en este caso, Dagger2) y en MVP, por lo que vamos a empezar a montar nuestra primera aproximación a este patrón paso a paso.
1.- Definimos las interfaces de comunicación
1.1.- BaseView
Interfaz base que debe implementar todas nuestras vistas. Tiene dos métodos:
- fun showError(error: String)
- fun setPresenter(presenter: BasePresenter<*>)
1.2.- PresenterContract
Se trata de la interfaz base que debe implementar todos nuestros Presenters. Tenemos que destacar el uso de “in”, se especifica cuando el tipo T definido sólo es consumido (se usa como parámetro) y nunca como producido (lo retorna un parámetro). En el caso en que sólo sea producido y nunca consumido usaremos “out”. Métodos que define:
- fun attachView(view: T)
- fun detachView()
- fun onResume()
- fun onPause()
2.- Creamos las clases base
La creación de objetos base agilizará nuestra forma de programar a la par que reducirá en gran medida el código.
2.1.- BasePresenter: PresenterContract
Se trata de una clase abstracta y hace uso de un tipado genérico que extiende BaseView. Todos los presenters que tengamos en el proyecto deben extender de él ya que aporta los elementos básicos que deben tener todos.
En cuanto a las variables tenemos:
- isInForeground: Boolean. Inicializado a false, se encargará del control del estado de la vista (si está en primer plano o no).
- weakReference: WeakReference. Mejorar el uso de la memoria ya que será la encargada de albergar nuestra variable view y eliminarla si ya no la necesitamos.
- view: T. Se trata de la vista asociada a nuestro Presenter.
Los métodos importantes son:
- fun attachView(view: T). Se trata del encargado de establecer el presenter a nuestra vista y de la inicialización de nuestra variable weakReference. Este método lo llamaremos cuando la vista sea inyectada.
- fun onResume(). Indicará a nuestro presenter que la vista está en primer plano.
- fun onPause(). Indicará a nuestro presenter que la vista no está en primer plano.
- fun detachView(). Lo usaremos para limpiar la variable weakReference y por lo tanto, liberar memoria.
- fun isPresenterInForeground(). Nos indicará si nuestra vista está en primer plano.
Donde los cuatro primeros se tratan de la implementación de la interfaz PresenterContract.
2.2.- BaseFragment : Fragment(), BaseView
Clase abstracta que proporciona la implementación básica en todos nuestros Fragments e implementa la interfaz BaseView. Como se puede ver, todo va calzando en cuanto a interfaces, de esta forma conseguimos que tanto la vista como su presentador se comuniquen de forma correcta aislandolo del SDK de Android.
Variables:
- var presenter: BasePresenter<*>. Tendrá el presenter inyectado a esa vista.
- abstract val layoutResource: Int. Será donde configuraremos el id de nuestro layout. Además, esta variable está declarada como abstracta para obligar que sea sobrescrita.
Métodos:
- abstract fun onFragmentInject(). Se trata del método donde se configura la inyección del fragment.
- abstract fun initializeView(). Método donde configuraremos nuestra vista.
- fun getAppComponent(): AppComponent. Retorna el componente de la aplicación, el cual usaremos a menudo a la hora de inyectar los fragments.
Hay que señalar que también sobreescribimos los métodos onResume, onPause, onDetach y onCreateView. Todos para informar del estado al Presenter y establecer la vista al mismo.
Por el momento, paramos
Actualmente, tenemos definido nuestras interfaces y clases base de las que extenderán nuestros futuros componentes.
Para no alargar más este post y hacerlo muy pesado paramos hasta la próxima publicación. En el siguiente post lo retomaremos desde este punto y añadiremos un presenter que extienda de BasePresenter y una activity que contendrá un fragment que extienda de BaseFragment. Una vez los tengamos creados, incluiremos una llamada a servicio, parseo de los datos y representación (ya lo hicimos en este post) pero aplicando MVP.
Espero que os haya gustado.