Hoy toca dar un salto al next level y es por ello que empezaremos con las arquitecturas. Una arquitectura no es mas que una forma de organizar nuestro código para poder llevar un control sobre nuestra aplicación ya que a medida que crece la app el mantenimiento es más complejo.

Existen numerosas arquitecturas en Android, posiblemente conozcas o hayas oído hablar de algunas como MVC (model-view-controller), MVP (model-view-presenter) o la que veremos hoy que será MVVM (model-view-viewmodel), aunque hay muchas más.

En este primer capítulo añadiremos una arquitectura MVVM básica para ir entendiendo todos los conceptos y durante los próximos capítulos seguiremos mejorándola con inyección de dependencias, test, corrutinas y mucho más.

Qué es MVVM

El modelo-vista-modelo de vista es una arquitectura que se ha hecho muy popular desde que Google la convirtiera en su arquitectura oficial al lanzar la guía de arquitecturas.

Con MVVM organizaremos nuestra app en tres módulos o partes distintas.

Model

Representa la parte de datos, es decir, cuando recuperamos de una base de datos o de un servicio web, toda esa información la almacenaremos en modelos de datos.

View

Es la parte de la UI, los XML, las activities y los fragments. Estos actuarán como siempre, ejecutando acciones por ejemplo al pulsar un botón pero no realizarán las acciones, se suscribirán al view model y este les dirá cuando y como pintar.

ViewModel

Este sería la conexión entre el modelo y la vista. Las vistas se suscriben a sus respectivos viewModels y estos al percatarse de que el modelo ha sido modificado lo notificarán a la vista.

Cómo funciona MVVM

Seguramente te hayas quedado más confundido con la explicación anterior que antes de entrar a este artículo, pero es ahora cuando uniremos toda la parte anterior para que lo entiendas mejor.

Esquema MVVM en español curso android kotlin 2021
Esquema MVVM.

Imagínate una app de citas célebres, cada cita tiene un texto y un autor, es decir que ese será el model de datos. Nuestra activity será la encargada de mostrar una cita y cuando hagamos clic en la pantalla se mostrará otra.

Para hacer esto con la arquitectura MVVM es muy sencillo, lo primero que haremos es que nuestra activity se suscriba a nuestro viewModel, a través de live data, que no es otra cosa que «conectarse» a través del patrón observer para que cuando haya un cambio la activity se entere. Esto es prioritario ya que la actividad solo pintará cuando dicho viewModel lo notifique. También nuestra actividad tendrá que controlar cuando se pulsa la pantalla para avisar al viewModel. Aquí termina la parte de la vista, solo pinta lo que le diga el viewModel y cuando se produce un evento en la UI lo notifica.

Nuestro viewModel acaba de recibir que ha habido un evento en la UI por lo que tendrá que modificar el modelo de la cita. Para ello llamará al modelo que este irá a Retrofit, a Room o a cualquier tipo de acceso que nos devuelva datos (en este caso una nueva cita) y se lo devolverá al viewModel que a su vez notificará a la activity el cambio del contenido para que lo refresque.

Esto es todo, parece más complicado con la teoría así que vamos con la práctica. También decir que como esto empieza a complicarse un poquito, tendrás el proyecto en GitHub para poder descargarlo de manera gratuita siempre que quieras.

Ejemplo arquitectura MVVM Android

Lo primero que haremos será crear un nuevo proyecto en Android, el mío se llamará MVVM Example.

Build Gradle

Una vez creado empezaremos en el build.gradle(app) para preparar la configuración y las dependencias necesarias.

Lo primero será dentro de android{} activar el View Binding y la compilación con Java 1.8, porque como ya sabes Kotlin corre sobre JVM.

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    
    kotlinOptions {
        jvmTarget = '1.8'
    }

    buildFeatures{
        viewBinding = true
    }

Ahora iremos a la parte de dependencias.

    // Fragment
    implementation "androidx.fragment:fragment-ktx:1.3.2"
    // Activity
    implementation "androidx.activity:activity-ktx:1.2.2"
    // ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
    // LiveData
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"

En este caso yo no usaré los fragments pero dejo la dependencia para que sepáis cuál usar. Sincronizamos el proyecto.

Diseño

Es el momento de empezar a preparar la UI de la app, será un textView en el centro con la cita y otro textView en la parte inferior de la pantalla con el nombre del autor. Así que rellenamos el activity_main.xml con el siguiente código.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/viewContainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/background"
    tools:context=".view.MainActivity">

    <TextView
        android:id="@+id/tvQuote"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="16dp"
        android:textColor="@color/white"
        android:textSize="24sp"
        android:textStyle="italic"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tvAuthor"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="16dp"
        android:textColor="@color/white"
        android:textSize="24sp"
        android:textStyle="italic"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Como datos interesantes del código anterior, fíjate que el constraintLayout, tiene una id, ya que le pondremos un listener para capturar cuándo se pulsa la pantalla.

Seguramente los colores te darán error ya que no los has añadido, para ello abre res > values > colors.xml y añade los siguientes colores.

    <color name="white">#FFFFFFFF</color>
    <color name="background">#4F1497</color>

Yo he optado por ese violeta, pero puedes poner el color que quieras.

Preparando el View BInding

Es el turno del MainActivity, en él prepararemos el View Binding para poder acceder a la vista que hemos diseñado.

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }   
}

Esta parte ya te la sabrás de memoria, pero si no es así te recomiendo leer el capítulo sobre View Binding.

MVVM – Model

La primera parte que haremos será el modelo. Como ya he dicho una cita tiene un String que es la propia cita y otro que sería el autor, que por defecto será anónimo si no lo rellenamos.

en la ruta del MainActivity, crearemos un nuevo directorio. Hacemos clic derecho en la carpeta superior New > Package y lo llamaremos model, ya que ahí dentro meteremos toda la parte del modelo de la arquitectura. Una vez creada la carpeta, haremos clic derecho New > Class para crear esta vez la clase necesaria, que la llamaremos QuoteModel.

data class QuoteModel (val quote:String, val author:String)

Se trata de una data class de lo más normal, pero si no sabes de lo que hablo revisa este capítulo.

Según la arquitectura MVVM la parte del modelo será la encargada de acceder a la base de datos o al servicio web que nos devuelva la información, en este caso las citas. Para simplificar este primer capítulo de muchos, tendremos nuestro propio proveedor de citas, es decir, una clase con varias citas con un método que aleatoriamente devolverá una u otra.

Dentro del mismo directorio model, crearemos una nueva clase llamada QuoteProvider.

class QuoteProvider {
    companion object {

        fun random(): QuoteModel {
            val position = (0..10).random()
            return quotes[position]
        }

        private val quotes = listOf(
            QuoteModel(
                quote = "It’s not a bug. It’s an undocumented feature!",
                author = "Anonymous"
            ),
            QuoteModel(
                quote = "“Software Developer” – An organism that turns caffeine into software",
                author = "Anonymous"
            ),
            QuoteModel(
                quote = "If debugging is the process of removing software bugs, then programming must be the process of putting them in",
                author = "Edsger Dijkstra"
            ),
            QuoteModel(
                quote = "A user interface is like a joke. If you have to explain it, it’s not that good.",
                author = "Anonymous"
            ),
            QuoteModel(
                quote = "I don’t care if it works on your machine! We are not shipping your machine!",
                author = "Vidiu Platon"
            ),
            QuoteModel(
                quote = "Measuring programming progress by lines of code is like measuring aircraft building progress by weight.",
                author = "Bill Gates"
            ),
            QuoteModel(
                quote = "My code DOESN’T work, I have no idea why. My code WORKS, I have no idea why.",
                author = "Anonymous"
            ),
            QuoteModel(quote = "Things aren’t always #000000 and #FFFFFF", author = "Anonymous"),
            QuoteModel(quote = "Talk is cheap. Show me the code.", author = "Linus Torvalds"),
            QuoteModel(
                quote = "Software and cathedrals are much the same — first we build them, then we pray.",
                author = "Anonymous"
            ),
            QuoteModel(quote = "¿A que esperas?, suscríbete.", author = "AristiDevs")
        )
    }
}

Analicemos el código anterior con calma. Para empezar todo está dentro de un companion object ya que es la única forma de que podamos acceder a esa información desde fuera de la clase. Luego tenemos una lista de objetos QuoteModel, que son un listado de citas que he encontrado por internet, algunas anónimas otras no. Ese listado es privado, es decir, no se puede acceder a él desde fuera de esta clase ya que tenemos una última función llamada random() que genera un número random del 0 al 10 (ambos inclusive) y con ese número devuelvo una posición del listado.

Esta clase básicamente emula a la parte de Retrofit o de proveer información para la app, aunque como ya he dicho en siguientes partes si meteremos Retrofit, Room y mucho más.

MVVM – ViewModel

Es el turno de preparar nuestro ViewModel, y para ello crearemos otro directorio como hicimos con model pero esta vez lo llamaremos viewmodel. Dentro de la carpeta es el momento de crear nuestro primer ViewModel, que será una clase llamada QuoteViewModel.

class QuoteViewModel : ViewModel() {

    val quoteModel = MutableLiveData<QuoteModel>()

    fun randomQuote() {
        val currentQuote = QuoteProvider.random()
        quoteModel.postValue(currentQuote)
    }
}

Aunque hay poco código hay mucho que explicar.

Para que nuestra clase sea un ViewModel, tenemos que hacer que la clase creada extienda de dicha clase y es por ello que ponemos : ViewModel() después del nombre de la clase.

Luego estamos implementando LiveData, que no es más que un tipo de datos al cual nuestra activity se puede conectar para saber cuando hay un cambio en dicho modelo. Por eso fíjate que quoteModel es un MutableLiveData<QuoteModel>, es decir, es live data para que la actividad se pueda conectar, pero como el valor va a ser modificado es mutable y ese modelo de datos encapsula el objeto que queremos acceder, en este caso será un QuoteModel porque iremos cambiando la cita cada vez que el usuario toque la pantalla.

Luego tenemos un método que será al que acceda nuestra vista, que primero llama a nuestro provider para que nos devuelva una nueva cita (llamaremos a este método cada vez que se pulse la pantalla) y luego se lo añadiremos a nuestro live data con postValue(). Y como nuestro objeto ha sido modificado la actividad lo sabrá al momento y pintará los cambios.

MVVM – View

Queda la última parte que sería nuestra activity. La idea es unir dicha actividad a nuestro ViewModel para poder estar suscrito a los cambios y modificar la UI cuando sea necesario.

Gracias a la librería de activity que añadimos al principio, asignar un ViewModel es mas sencillo. Solo tenemos que crear un atributo de clase definiendo nuestro ViewModel y llamar a by viewModels().

    private val quoteViewModel: QuoteViewModel by viewModels()

Con esto ya lo tenemos, pero nos queda suscribirnos a los cambios. Como dije anteriormente live data es básicamente el patrón observe por lo que definirlo es muy sencillo.

quoteViewModel.quoteModel.observe(this, Observer { currentQuote ->
            binding.tvQuote.text = currentQuote.quote
            binding.tvAuthor.text = currentQuote.author
        })

Simplemente llamamos a nuestro ViewModel y dentro de este accedemos a nuestro objeto con live data para llamar a la función observe(). Dentro le pasaremos el owner que es this y aquí lleva una función Observer{}, fíjate que al valor que retorna lo he llamado currentQuote, para ello solo tengo que ponerlo al principio y añadir -> pero podríamos borrar eso y acceder con un it.

 quoteViewModel.quoteModel.observe(this, Observer {
            binding.tvQuote.text = it.quote
            binding.tvAuthor.text = it.author
        })

Yo lo dejaré como el primer ejemplo porque me parece más legible.

Con lo que tenemos programado ya se pintarán los cambios de la UI automáticamente cada vez que nuestro objeto con live data sea modificado pero nunca lo estamos modificando ya que se cambia al llamar a la función randomQuote() del ViewModel. Para ello vamos a terminar añadiendo un setOnClickListener al constraintLayout principal.

        binding.viewContainer.setOnClickListener { quoteViewModel.randomQuote() }

Así cada vez que tocamos la pantalla actualizaremos la cita. La clase completa quedaría así.

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    private val quoteViewModel: QuoteViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        quoteViewModel.quoteModel.observe(this, Observer {
            binding.tvQuote.text = it.quote
            binding.tvAuthor.text = it.author
        })


        binding.viewContainer.setOnClickListener { quoteViewModel.randomQuote() }

    }

}

A modo opcional podéis ir a res > values > theme y en el theme de nuestra app añadir .NoActionBar al theme del parent para quitar la toolbar. Es decir dejar el estilo así.

    <style name="Theme.MVVMExample" parent="Theme.MaterialComponents.DayNight.NoActionBar">

Ahora ejecuta la app.

Hasta aquí el capítulo de hoy, recuerda que puedes descargar el proyecto entero en mi GitHub.

Este será el primero de varios capítulos donde iremos mejorando la arquitectura, añadiremos clean, corrutinas, inyección de dependencias, Room, Retrofit, etc.

Así que si quieres que continue recuerda dejarme un comentario y suscribirte al canal de Youtube dónde también explico el mismo contenido en vídeo para que te sea más cómodo.

Continúa con el curso: Capítulo 36 – Funciones de extensión en Kotlin


Te recuerdo que puedes seguirme en mis redes sociales en Aristi.Dev. Y si tienes dudas con este o cualquier otro artículo del blog únete al Discord de la comunidad y te ayudaremos.