MVVM + Enlace de datos + Kotlin = Código eficiente y más fácil

Encora | 22 de mayo, 2018

Como desarrolladores de software, siempre buscamos formas de mejorar la forma en que escribimos y organizamos el código. El objetivo principal de esto es asegurarnos de que nuestro código sea fácil de mantener. También queremos poder tener la misma funcionalidad con menos, o al menos la misma, cantidad de código en el futuro. El enlace de datos de Kotlin y Android trabajan juntos para construir una arquitectura MVVM que logra los objetivos anteriores.

Antes de comenzar con el código, vamos a hacer una revisión rápida de las tres arquitecturas más comunes utilizadas en Android: MVC, MVP y MVVM. A continuación, identificaremos por qué centramos este artículo en MVVM.

MVC

 

imagen1

En las tres arquitecturas, el usuario interactúa con la capa de vista. Sin embargo, en el caso de MVC, esta interacción se transfiere de la Vista al Controlador, que debe poder comprender y procesar la solicitud del usuario e interactuar con el Modelo para actualizarlo si es necesario. Luego, el modelo puede desencadenar eventos para actualizar a la vista en función de los últimos cambios. Existe una interacción entre el Modelo y la Vista, que normalmente se implementa mediante un patrón observable o manualmente, a través del Controlador.

MVP
imagen2

 

Al usar MVP, tenemos una arquitectura poco acoplada, ya que el presentador recibe las entradas del usuario a través de la vista, las procesa con la ayuda del modelo y actualiza la vista. No hay comunicación entre el modelo y la vista. Existe una relación de uno a uno entre la Vista y el Presentador, y cada vista tiene una referencia al Presentador a través de una interfaz para el procesamiento de las solicitudes de los usuarios. Además, el presentador se comunica con la vista para las actualizaciones de la interfaz de usuario mediante otra interfaz que elimina el código acoplado. Esto hace que las pruebas, el mantenimiento y las mejoras futuras de la Unidad sean más fáciles de realizar.

MVVM
imagen3

 

MVVM es muy similar a MVP en términos de cómo está organizado el código, pero con la gran diferencia de que incluye el término “Enlace de datos bidireccional”. En MVVM, la Vista tiene un estado que básicamente representa el estado actual de la información que se muestra al usuario. Esta información debe corresponder con el estado actual de los objetos en la capa Ver modelo. Para mantener esta información sincronizada, utilizamos el enlace de datos bidireccional, que es una implementación de patrón observable que nos mantiene informados sobre los cambios en las capas y nos permite reaccionar a estos cambios actualizando la vista o cambiando el modelo. Estamos hablando de Programación Reactiva para manejar la comunicación entre dos capas, sin escribir toneladas de código.

Es hora de programar

Vamos a crear una aplicación muy simple usando MVVM, enlace de datos de Android, Kotlin e incluso un componente de programación reactiva (Observer y Observable). El ejemplo es una aplicación que nos permite crear un plan nutricional y nos permite saber cuántas calorías estamos ingiriendo. ¡Empecemos!

Archivo Gradle

Para poder usar el enlace de datos, necesitamos actualizar nuestro archivo gradle de nivel de aplicación. Necesitamos agregar la dependencia de enlace de datos y habilitarla. El archivo debería verse así:

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'

android {
  ...
  dataBinding {
      enabled = true
  }
  ...
}

dependencies {
  …

  ///the compiler version must be the same than our gradle version
  kapt 'com.android.databinding:compiler:3.1.1'
}

 

Modelos

La aplicación de muestra estará compuesta por 2 clases de datos

● Comida

● Plan nutricional

 Comida

Esta clase de datos tiene 2 atributos. Gracias a Kotlin, solo necesitamos una línea de código para declarar estos dos atributos, sus métodos getters y setters, su visibilidad y el constructor de la clase.

data class Food (val name: String, val calories: Int)

 

Plan Nutricional

Tiene dos atributos, una lista de objetos Alimentos y un número entero que representa el total de calorías del plan. También tiene una función que usaremos después para agregar un nuevo alimento al Plan Nutricional.

data class NutritionalPlan (var foods: MutableList<Food> = mutableListOf(), val totalCalories: ObservableInt = ObservableInt()){

   fun addFood(food: Food){
       foods.add(food)
       totalCalories.set(totalCalories.get() + food.calories)
   }
}

 

Una cosa a tener en cuenta aquí:

●El campo totalCalories es una propiedad ObservableInt, que es un objeto Observable que tiene solo una propiedad, un int. De esta forma, cuando se modifique totalCalories, automáticamente notificará a todos los suscriptores del Plan Nutricional sobre el cambio. De lo contrario, tuvimos que usar el método notifyObservers para notificar manualmente sobre el cambio

Vista Modelo

En el caso de nuestra aplicación Nutritional Plan, solo tenemos un modelo de vista, vayamos directamente al código.

class NutritionalPlanViewModel(var nutritionalPlan : NutritionalPlan = NutritionalPlan()) : Observable() {

   fun addFood(name: String, calories: Int){
       val food = Food(name, calories)
       nutritionalPlan.addFood(food)
       setChanged()
       notifyObservers()
   }

   fun showAddFoodDialog(){
       setChanged()
       notifyObservers(SHOW_ADD_FOOD_DIALOG)
   }

   companion object {
       const val SHOW_ADD_FOOD_DIALOG = "show add food dialog"
   }
}

 

Algunas cosas que debe saber sobre este código:

●Tiene solo una propiedad, que es una instancia de la clase de Plan Nutricional que creamos antes

● Se extiende desde la clase Observable, que nos permite, mediante el uso del patrón Observable, crear una aplicación Reactiva. En este caso, nuestra clase Ver modelo notificará a todos sus observadores cuando algo cambie en ella. Esto se hace mediante el uso del método setChanged (que básicamente marca que el observable ha cambiado) y el método notifyObservers () (que notifica a todos los observadores del cambio, toma un Object como parámetro, en nuestro caso estamos enviando un evento de mostrar un diálogo al usuario)

● Como notamos, no hay referencias a ninguna vista, lo que significa que un modelo de vista se puede vincular a más de una vista y se comunican entre sí mediante la vinculación de datos y los eventos observables.

Vista

Comencemos con el código XML. Nuestro archivo de diseño se llama MainActivity.xml.

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

   <data>
       <variable
           name="viewModel"
       type="com.androidtraining.MVVMDatabindingKotlin.viewModel.NutritionalPlanViewModel"/>
   </data>


   <android.support.constraint.ConstraintLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       tools:context=".view.MainActivity">
       ...
       <TextView
           android:id="@+id/tv_total_calories"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_marginEnd="8dp"
           android:layout_marginStart="8dp"
           android:layout_marginTop="5dp"
           android:text="@{String.valueOf(viewModel.nutritionalPlan.totalCalories)}"
           android:textSize="13sp"
           app:layout_constraintEnd_toEndOf="parent"
           app:layout_constraintStart_toStartOf="parent"
           app:layout_constraintTop_toBottomOf="@+id/tv_title" />

       ...

       <android.support.design.widget.FloatingActionButton
           android:id="@+id/btn_add_food"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_gravity="bottom|end"
           android:layout_marginBottom="10dp"
           android:layout_marginEnd="10dp"
           android:elevation="10dp"
           android:onClick="@{() -> viewModel.showAddFoodDialog()}"
           android:src="@android:drawable/ic_input_add"
           app:fabSize="normal"
           app:layout_constraintBottom_toBottomOf="parent"
           app:layout_constraintEnd_toEndOf="parent" />

   </android.support.constraint.ConstraintLayout>
</layout>

 

Algunas cosas que se deben tener en cuenta en el archivo de diseño:

● Omitimos Textview y RecyclerView, ya que no son relevantes para este ejemplo.

● En la etiqueta de datos, declaramos una variable llamada viewModel, que es una instancia de la clase View Model descrita anteriormente.

● El TextView con el id tv_total_calories obtiene el texto para mostrar desde el modelo de vista, utilizando la variable viewModel mencionada anteriormente.

● El controlador de eventos onClick también usa la variable viewModel para llamar a un método para ejecutar las acciones correspondientes.

Vamos con la clase MainActivity, que es un observador que está suscrito al modelo de vista observable para reaccionar a las notificaciones emitidas por él.

class MainActivity : AppCompatActivity(), Observer {
   private val viewModel = NutritionalPlanViewModel()

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       initbinding()
   }

   override fun update(p0: Observable?, p1: Any?) {
       if(p1.toString() == NutritionalPlanViewModel.SHOW_ADD_FOOD_DIALOG){
           showDialog(viewModel)
       }
   }

   private fun initbinding(){
       val activityMainBinding =  DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

       activityMainBinding.viewModel = viewModel
       viewModel.addObserver(this)
       rv_foods.layoutManager = LinearLayoutManager(this)
       var adapter = RecyclerAdapter(viewModel.nutritionalPlan.foods, this)
       rv_foods.adapter = adapter
       btn_add_food.setOnClickListener({showDialog(viewModel)})

   }

   private fun showDialog(viewModel: NutritionalPlanViewModel){
       var dialog = AddFoodDialog.newInstance(viewModel)
       dialog.show(supportFragmentManager, "")
   }
}

 

Cosas que debe saber sobre el código:

addObserver es llamado para suscribir nuestro Observador (la Vista) al Observable (el Modelo de Vista)

● Usamos el método DataBindingUtil.setContentView en lugar del setContentView normal, porque el primero devuelve una instancia de ActivityMainBinding, que es una base de clase generada automáticamente en el archivo de diseño. Esta clase contiene todos los enlaces de las propiedades de diseño. En nuestro caso, solo tenemos una propiedad llamada viewModel; también sabe cómo asignar los valores a las expresiones vinculantes

● activityMainBinding.viewModel establece el modelo de vista de la variable viewModel declarada en el diseño

● La función de actualización recibe todos los eventos disparados por el Observable, p0 representa el objeto que emitió la señal indicando que fue modificado y p1 es un objeto enviado como información extra. Podría ser cualquier tipo de objeto

Conclusión

¡Y eso es! Con muy poca cantidad de código, tenemos una arquitectura MVVM que actualiza automáticamente la base de la vista en los cambios en el modelo de vista y también mantiene la vista sincronizada con el modelo a través de la capa de modelo de vista.

Contáctenos

Contenido

Categorías

Compartir Artículo

Artículos Destacados