Menú Navegación Páginas

El blog de Antonio Leiva sobre diseño y desarrollo de interfaces en Android

MVP en Android: cómo organizar la capa de presentación

MVP en Android: cómo organizar la capa de presentación
  • Twitter
  • Facebook
  • Google Plus
  • LinkedIn

El MVP (Model View Presenter) es un patrón derivado del conocido MVC (Model View Controller), que de un tiempo a esta parte está cobrando gran importancia en el desarrollo de aplicaciones Android. Cada vez hay más gente hablando del tema, pero sin embargo muy poca información fiable y estructurada. Es por eso que quería aprovechar este blog para incitar el debate y que todos aportemos nuestros conocimientos para aplicarlo de la mejor manera posible a nuestros proyectos.

¿Qué es el MVP?

El patrón MVP permite separar la capa de presentación de la lógica de la misma, de tal forma que todo lo relacionado con cómo funciona la interfaz queda separado del cómo representarlo en pantalla. Idealmente el patrón MVP permitiría conseguir que una misma lógica pudiera tener vistas totalmente diferentes e intercambiables.

Lo primero a tener claro es que el MVP no es un patrón de arquitectura de aplicaciones, sólo se encarga de la capa de presentación. En cualquier caso siempre es mejor usarlo para la arquitectura que no usarlo en absoluto.

¿Por qué usar MVP?

En Android tenemos un problema derivado del hecho de que las actividades en Android están íntimamente acopladas tanto con la interfaz como con las mecánicas de acceso a datos. Hasta tal punto que existen clases como el CursorAdapter, que mezclan los adaptadores, que son parte de la vista, con los cursores, algo que debería estar relegado a lo más profundo de la capa de acceso a datos.

Para que una aplicación sea fácilmente extensible y mantenible necesita tener bien separadas sus capas. ¿Qué hacemos si mañana en vez de tirar de base de datos necesitamos hacerlo de un servicio en la red? Tendríamos que rehacer toda la vista.

El MVP independiza la vista de la forma de conseguir esos datos. Nos divide la aplicación en, al menos, tres capas distintas, pudiendo además testear cada una de ellas de forma independiente.

¿Cómo implementar MVP en Android?

Pues aquí es donde todo comienza a ser más difuso. Hay muchas variaciones de MVP y cada uno puede ajustar la idea del patrón a sus necesidades y a la forma en que se sienta más cómodo. El patrón varía sobre todo en función de la cantidad de responsabilidades que deleguemos en el presentador.

¿Es la vista la que tiene que activar o desactivar una barra de progreso, o se debe encargar el presentador? ¿Y quién decide qué botones deben pintarse en la Action Bar? Ahí es donde comienzan las decisiones difíciles. Yo os mostraré cómo suelo trabajar, pero quiero que este artículo sea más un foro para el debate que unas guías estrictas de cómo aplica el MVP, porque a día de hoy no existe una forma estandarizada de implementarlo.

El presentador

El presenter se encarga de actuar de intermediario entre la vista y el modelo. Recupera los datos del modelo y se los devuelve a la vista formateados. Pero a diferencia del MVC típico, también decide qué ocurre cuando se interactúa con la vista.

La vista

La vista, habitualmente implementada por una Activity (aunque puede ser un fragment, una View… según como se estructure la App), contendrá una referencia al presenter. Lo ideal sería inyectárselo a la actividad mediante Dagger, pero en el caso de no usarlo, será la vista la encargada de crear el objeto presentador. Lo único que hará la vista será llamar a un método del presenter cada vez que se realice una acción sobre la interfaz, normalmente el pulsado de un botón, un elemento de una lista, etc.

El modelo

En una aplicación con una buena arquitectura por capas, este modelo no sería más que la puerta de enlace a la capa de dominio o de lógica de negocio. Si estuviéramos utilizando la clean architecture de Uncle Bob, seguramente el modelo sería un interactor que implemente algún caso de uso. Pero este es otro tema que me gustaría tratar en futuros artículos. Por ahora es suficiente con verlo como el proveedor de los datos que queremos mostrar en la vista.

Un ejemplo

Como es un poco extenso de explicar, he creado un Github con un ejemplo consistente en una pantalla de login que valida los datos y que permite acceder a una home con un listado de datos que se recuperan desde el modelo. En este artículo no explicaré nada de código porque es bastante simple, pero si veis que no queda claro puedo crear otro artículo explicándolo en detalle.

Ir al ejemplo de MVP en Github

Conclusion

Independizar la interfaz de la lógica en Android no es tarea sencilla, pero con el patrón Model-View-Presenter se hace un poco más fácil evitar que nuestras actividades acaben siendo clases muy acopladas de cientos o incluso miles de líneas. En aplicaciones grandes se vuelve imprescindible organizar bien nuestro código si queremos que su mantenimiento y ampliación no se vuelva imposible.

¿Te ha gustado? Compártelo

31 Comentarios

  1. Muy buen post Antonio.
    Es un asunto complicado el de aplicar MVC en Android, pero la opción del “Presenter” me parece que ayuda mucho.
    El ejemplo me ha sido de mucha utilidad pero me queda la duda de como sería usando Dagger para una desacoplamiento mayor. ¿Tienes pensado subir algún ejemplo con Dagger?

    • Hola! Sí, lo uso a diario con Dagger. Básicamente cada actividad tiene su propio módulo con el que inyecta los componentes que necesita, entre ellos el presenter. De momento no tengo ningún ejemplo preparado, pero si encuentro tiempo puedo intentar montar ese mismo ejemplo con Dagger, aunque va a quedar muy simple.

  2. Me encanta el artículo y en el GDG Mallorca y GDG Barcelona llevamos discutieno esto una semanas. Personalmente creo que el Presentador no debería tener dependencia del SDK de Android.

    Que te parece esté?

    https://github.com/GDGMallorca/Juguete

  3. Buenas, me a gustado mucho la entrada. Pero al ver el ejemplo me surgen algunas dudas, por ejemplo, ¿Servicios, Broadcast Receiver, etc en que capa encajarían?,
    según mi concepción del patrón el presentador y el modelo deben ser independientes del SDK de android, pero este tipo de clases tampoco encajan en la vista.
    Saludos y de verdad muchas gracias por la entrada, esta genial.

    • Depende mucho del caso, en general no forman parte de la capa de presentación. Yo lo que hago es acceder a ellos a través de los interactors si necesitan comunicarse con las vistas, o si no en otras capas como procesos independientes. Este ejemplo sólo afecta a la capa de presentación, pero una buena arquitectura separada por capas requiere algunas más. Ya depende de la complejidad del proyecto y de su futura evolución el complicarse más o menos.

      • Entiendo, Sin embargo quiero tener una estructura basada en MVP de la cual pueda partir a la hora de desarrollar un app. Algo como esto:

        -storage(preferencias y BD en SQLite)
        -service(servicios y Receivers)
        -request(clases base para el manejo de solicitudes http)
        -model
        -presenter
        -view
        -util

        ¿Qué te parece?

        • Tiene buena pinta, quedan bastante claras las distintas capas.

  4. ¿ Qué prefieres usar Dagger o Butter Knife ?

    • Yo uso las dos, cada una sirve para una cosa diferente. La primera trata sobre inyección de dependencias y la segunda para facilitar la carga de los controles de una vista.

  5. Hola, Gracias a este MVP he conseguido despejar y ordenar mis códigos. Pero me surge una duda. En mi caso, tengo 2 Fragments, uno con datos y el otro muestra un resumen del anterior. El caso es que ahora tengo tan separadas las capas, que un cambio en los datos de Fragment 1 no es reflejado en el Fragmet 2. ¿Cuál es la forma de comunicar esos cambios? Antes usaba una clase de apoyo que iba pasando entre Fragments… Gracias

    • Aún sigues teniendo la activity como nexo entre ambos fragments, por lo que en principio puedes seguir haciéndolo igual, o incluso inyectar el presenter en los fragments y utilizarlo como intermediario.

      • A veces, es uno tan ciego!!! Gracias. Estaba tan cabezón, con no tener nada más que los textvies y demás en los Fragments, que no pensé en mover el objeto como hacía y haré ! gracias de nuevo!

  6. Hola, he oido hablar sobre el modelo MVP y me parece una forma bastante buena de separar la lógica en android, he estado trabajando en un proyecto y tengo una duda.
    Tengo un LinearLayout a la que le quiero agregar dinámicamente varios TextViews, desde el presentador obtengo una lista de Strings, ahora necesito recorrer con un foreach la lista, crear un TextView para cada String y agregar cada TextView al LinearLayout, pero en que capa puedo hacer esto?, actualmente el hice que el presentador inyecte a la Activity la Lista de Strings y desde la activity hago todo lo demás, pero me quedan dudas sobre si lo estoy haciendo bien :s, no quisiera empezar a acosumbrarme a malas prácticas, muchas gracias.

  7. Y si necesitara usar una clase asíncrona?, en qué capa podría declararla?, disculpa por tantas preguntas.

  8. Que tal amigo, quisiera agradecerte por compartir tu conocimiento adquirido a lo largo de tu desarrollo profesional, es de gran ayuda todas tus aportaciones. En lo personal me agradan este tipo de post, donde se mencionen las buenas prácticas que debemos tener en cuenta para que el mantenimiento sea más sencillo. Espero y tengas tiempo de compartir más post de este tipo, te estaremos muy agradecidos.

    Gracias.
    Saludos.

    • Gracias! Actualmente este blog sólo lo tengo como histórico para no perder lo que escribí, pero ya no añado más artículos aquí. Si quieres ver lo nuevo que voy escribiendo, puedes pasarte por http://antonioleiva.com

  9. Es muy interesante el debate que abres. Yo he empezado a usar MVP y noto cierta mejoría, pero también le veo limitaciones.
    En mi caso, tengo un Fragment con un viewPager donde cada pagina es otro Fragment. Tengo Fragments con llamadas asincronas (p. ej, mensaje que llega desde el reloj, o usuario que pulsa en un componente), que hacen una llamada al presenter, para que este nuevamente llame a la vista, que maneja el evento. Equivalente al fragment “tradicional” sin MVP, pero con el presenter de por medio.
    No sé si estaré aplicando MVP de manera incorrecta, pero mi código es identico a las implementaciones que veo en los repos de programadores destacados, solo que ellos hacen “un ejemplito” y queda genial, y mi app tiene bastante envergadura y MVP eleva la complejidad de mi código.
    Saludos!

    [Update] Después de ver tu implementación de ejemplo de MVP, efectivamente aplicamos el patrón de la misma manera. El MainPresenter define las distintas interacciones con MainActivity (onItemClicked, onXYZButtonClicked, etc), y la MainView define como MainActivity (que sería la impl. de la vista) reacciona ante estas interacciones (showProgress, hideProgress, showMessage…)

    • Sí, la verdad es que las dudas reales surgen al usarlo en implementaciones más complejas. Un consejo que me dieron que me parece muy interesante es que cada presenter debe dedicarse a una única cosa, por lo que no es descabellado tener varios presentes en la misma actividad. O si tienes un viewPager que muestra informaciones muy distintas, que cada fragment dentro del viewpager use su propio presenter. El mayor cuello de botella de MVP son los presenters, como te descuides se llenan de dependencias, así que hay que pensar muy bien qué cantidad de cosas hacen.

      • Buen consejo. Una práctica que estoy haciendo en los casos más simples para reducir algo estas dependencias, es “saltarme” MVP, y en lugar de gestionarlo a través del presenter, (llamo a presenter, presenter llama callbacks de la vista, que resulta dificil de tracear), lo gestiono directamente en el @OnClick de butterknife, trabajando con los mTextView1, mButton1, etc.
        El codigo será menos testable porque me estoy saltando MVP, pero cuando vuelva a ese método dentro de 3 meses, me resultará más fácil de digerir

  10. Hola, hice un pequeño ejemplo de login con parse utilizando el patrón mvc, en el mismo presenter hago la petición al servidor pero normalmente tengo otra capa “request” donde realizo esas operaciones y le devuelvo el resultado al presenter, y este actualiza el view.

    https://github.com/emedinaa/android-mvp

    • Por supuesto, el modelo puede ser todo lo complejo que necesites. De hecho suele haber varias capas detrás de él.

  11. Hola. Me han hablado ya de MVVM, pero la verdad es que en este momento el binding de Android solo va en una dirección. Trabajé con MVP recientemente, y la verdad es que es bastante gratificante. Sobre todo en que toda la logica del view esta extraida en el presenter. De esa manera ha sido más fácil resolver uno que otro bug.Algo que se me ha ocurrido es al inyectar el view en el presenter, es que sea tratado como view, y solo tiene que pasar prueba de que utiliza el interface del view. Hasta el momento ha dado resultado.

  12. ¿Cuál debería ser API / LEVEL para ejecutar el proyecto???

  13. Hola, Antonio. Su ejemplo es muy bueno. Ahora te pido un sistema que tiene entradas de menú, informes y otras funciones idealmente primero definir la carpeta para cada registro, informes y funciones y dentro de cada carpeta establecer View. Para el menú principal para establecer la mayor parte de los cuales se llame cada registro, informes y otras funciones. ¿Podría usted por favor enviarme una muestra que comprende dicha situación. Gracias. e-mail: marcos.aurelio.cwb@gmail.com

  14. Hola, Antonio, gracias por tu articulo, lo he leido junto con otros de MVP, pero se agradece encontrar algo en espaniol. Ya que lo ofreces como inicio de un debate, aprovecho para decirte que en la parte donde explicas porque usar MVP, no me parece que el ejemplo de los Adapters sea la mejor explicacion. Si a un adapter le entra una coleccion de objetos, da lo mismo si vienen de DDBB o de un servicio. A priori no deberia implicarme cambio alguno en las IU (ya que si los objetos son los mismos la vista permaneceria inalterable y si no siempre podria adaptarlos). Segun lo que vengo entendiendo el verdadero interes de usar MVP pasa por facilitar las pruebas. El mismo motivo de usar inyeccion de dependencia.

  15. Hola, me ha parecido excelente la explicación que presentas en éste blog. Yo aprendí éste patrón en un curso de android en Coursera y la implementación que ofrecen fue bastante compleja de entender. Sin embargo, con ésta información he comprendido finalmente su funcionamiento y cómo aplicarlo. La idea de tener múltiples presentadores es bastante buena y no se me había ocurrido hasta ahora.
    Muchas gracias y espero que puedas explicar la inyección del presenter con Dagger.

  16. Hola que tal,

    disculpas anticipadas si mis preguntas son básicas o incorrectas.

    1.- ¿Como gestionas el ciclo de vida del fragmento o actividad? En el ejemplo de Github, tienes una actividad que muestra una lista. El presenter recibe los datos del Interactor, y se los pasa a la actividad. La actividad crea el adapter, etc.

    Supongamos que queremos persistir esa lista de items en OnSaveInstanceState, para que cuando la actividad vuelva a crearse tras una rotacion de pantalla ahorrarnos una peticion de red.

    Esto, ¿es responsabilidad de la actividad, o del presenter? Por lo que he entendido, el Presenter no deberia estar al tanto del ciclo de vida de la vista. Por lo tanto, ¿en la actividad no?

    2.- He leido por ahi que consideras un Adapter como una vista, y las tareas que realiza son tareas de vista. Ahora bien, supongamos que en la lista muestras una lista de hoteles, y en el adapter, en funcion de si el hotel tiene fotos en una tabla asociada, las muestras, y sino, muestras un placeholder. Si esa informacion no esta en el item “Hotel”, realmente habria que en el adapter hacer algun tipo de consulta, ya sea a nuestra bd o remota, para decidir si hay imagenes que visualizar o no.

    Esto seria mezclar en la vista, tareas que corresponderian al modelo… ¿Como se soluciona? Hay que garantizar que toda la información que el presenter consigue del modelo, tiene siempre todo lo necesario para que la vista lo “pinte”?

    3.- Supongamos también que en la actividad de login, quieres tener un contador que al llegar a tres logins erróneos, termine la actividad. Ese contador, ¿donde lo ponemos y donde mantenemos la lógica del mismo? ¿En la actividad?

    Bueno, resumiendo, el Presenter tal como yo lo entiendo, solo hace de puente entre la vista y el modelo. Pero la vista sigue teniendo mucha responsabilidad mas allá de pintar solamente la información (ciclo de vida, y algo de lógica de funcionamiento de la app).

    4.- Finalmente, si nuestra app recibe eventos, por ejemplo a través de EventBus, de servicios que funcionan como tarea de fondo, y debe actualizar la interfaz de usuario, ¿donde se debe recibir el evento, en la vista o en el presenter?

    Bueno, un saludo y muchas gracias.!

    • Hola! Me pillas de vacaciones, pero me lo he apuntado para contestarte con calma a mi vuelta. Gracias, y disculpa que tarde en contestar.

    • 1. Normalmente todo lo que se puede lo hace el presenter, pero bien es cierto que con el ciclo de actividad de Android algunas cosas se complican. Los temas de guardar el estado de las vistas los suelo dejar en la actividad, siempre y cuando sean datos exclusivamente de la vista. El resto de datos se pueden guardar en una chaché local, SharedPreferences, BBDD… y cuando recargues la vista los recuperas.

      2. La solución más limpia que veo, si lo he entendido bien, sería tener un modelo específico para la vista que tenga toda la información que la vista necesita. Efectivamente, si hace falta juntar información de dos peticiones, ni la vista ni el presenter deberían enterarse. El modelo las juntará y las devolverá en algo que la vista pueda renderizar.

      3. Esa información es lógica de negocio, la vista no debería conocerla y, si me apuras, el presenter tampoco. El presenter informaría al módulo de autenticación, y este decidiría si le dejar reintentar o no.

      4. En el presenter, hay que intentar que nadie se comunique con la vista salvo el presenter.

      Espero que esto resuelva tus dudas.
      Un saludo!

  17. Buenas de nuevo, sigo dandole al coco.

    ¿Las tareas asyncronas donde las pones, en el presenter o en el interactor? Por ejemplo, una tarea asincrona que realice un trabajo de red, y devuelva una informacion.

    Gracias!

    • El paso del hilo principal al secundario yo lo hago en el interactor. Toda la lógica que este ejecute se hará en un hilo secundario.

Responder a Ian Cancelar respuesta