Aplica Clean Code en Android Kotlin con inyección de dependencia usando arquitectura MVVM

Aplica Clean Code en Android Kotlin con inyección de dependencia usando arquitectura MVVM

¡Hola! ¿Cómo estás? Clean Code y la arquitectura MVVM son requisitos muy solicitados en las vacantes de desarrollo de Android, por lo que es fundamental conocer no solo los conceptos, sino también cómo aplicarlos, ya que muchas vacantes de desarrollo de Android requieren una etapa de pruebas técnicas, donde el candidato debe mostrar las mejores prácticas con las mejores herramientas.

¡Así que no temas! Este artículo te muestra cómo aplicar el concepto de Código Limpio en Android usando Kotlin, a través de la arquitectura MVVM e inyección de dependencias con Dagger Hilt 2, una biblioteca recomendada por Jetpack de Google (paquete de bibliotecas recomendadas https://developer.android.com/jetpack).


¿Qué es Clean Code?

Clean Code es un estilo de escritura de código que enfatiza la legibilidad, la mantenibilidad y la simplicidad. Código limpio que es fácil de entender y modificar porque está bien organizado, utiliza nombres descriptivos para variables y funciones, sigue convenciones de estilo consistentes y está estructurado de manera lógica y clara.

El objetivo del código limpio es hacer que el proceso de desarrollo de software sea más eficiente y menos propenso a errores. Al escribir código limpio, los desarrolladores pueden reducir la complejidad del sistema, mejorar la legibilidad y comprensión del código y facilitar su mantenimiento y actualización con el tiempo.

Algunas prácticas comunes de código limpio incluyen:

- Utilice nombres significativos para variables, funciones y clases;

- Escribir funciones pequeñas y cohesivas;

- Evite la duplicación de código;

- Comentar sólo cuando sea necesario y de manera clara y objetiva;

- Escribir pruebas automatizadas para validar el código;

- Utilice espacios consistentes y sangrías apropiadas para que el código sea más fácil de leer.

¿Y qué tiene que ver Clean Code con MVVM?

¡Todo! La arquitectura MVVM (Model-View-ViewModel) es un patrón de arquitectura de software que corresponde a los conceptos principales de Clean Code.

- Separación de responsabilidades: MVVM divide la aplicación en tres componentes principales: el modelo, la interfaz de usuario (vista) y el modelo de vista. Cada componente tiene una responsabilidad claramente definida. El modelo es responsable de la lógica empresarial y la persistencia de los datos, la interfaz de usuario es responsable de la visualización de datos y la interacción del usuario, y ViewModel es responsable de mediar entre el modelo y la interfaz de usuario. Esta separación de responsabilidades ayuda a mantener el código organizado, legible y fácil de mantener.

- Testabilidad: MVVM hace que la aplicación sea más comprobable ya que ViewModel está diseñado para ser independiente de la interfaz de usuario y se puede probar fácilmente sin necesidad de una interfaz de usuario. Además, el modelo también se puede probar por separado del ViewModel y la UI. La capacidad de probar diferentes componentes de la aplicación por separado ayuda a garantizar que cada componente funcione correctamente y evita problemas de regresión.

- Facilidad de mantenimiento: Finalmente, MVVM también ayuda con el mantenimiento de la aplicación, ya que es fácil agregar nuevas funciones o cambiar las existentes sin afectar el resto de la aplicación. ViewModel está diseñado para ser independiente de la interfaz de usuario, lo que significa que los cambios en la interfaz de usuario no afectan a ViewModel. Además, la separación de responsabilidades facilita que los desarrolladores comprendan cómo funciona la aplicación y realicen cambios sin afectar a otros componentes. Esto ayuda a mantener el código limpio y organizado.

Consulte la imagen a continuación para conocer el concepto de MVVM organizado en un diagrama en capas:

Fuente: Google (https://developer.android.com/training/dependency-injection/manual)‌ ‌


Quizás te preguntes, ¿qué es este "Repositorio" en el medio del diagrama?

El Repository es responsable de proporcionar datos a la capa ViewModel. Representa la capa de abstracción de varias fuentes de datos, llamada capa de Fuente de datos, que puede ser una base de datos local o un servicio web remoto, y luego el Repositorio proporciona una interfaz unificada para la capa ViewModel.

Desde una perspectiva de Código Limpio, el Repositorio promueve el principio de responsabilidad única, ya que tiene una sola responsabilidad, que es proporcionar datos a la capa ViewModel. Además, la capa de Repositorio facilita la implementación de pruebas, ya que a través de ella se pueden simular datos, sin necesidad de utilizar datos reales en las pruebas.

¿Cómo vamos a aplicar todos esos conceptos juntos?

Aplicar Código Limpio con MVVM requerirá crear una serie de clases, objetos e interfaces, por lo que se recomienda utilizar inyección de dependencias, ya que de esta manera podemos inyectar las clases donde necesitamos usarlas y así podemos aplicar las capas de MVVM. ¡Vamos a implementar!

Aquí se mostrará cómo implementar un dado simple de N caras aplicando los conceptos que vimos anteriormente, la aplicación devolverá el resultado de tirar el dado al hacer clic en un botón, este resultado se calculará aleatoriamente. Atención, no utilizaremos ninguna fuente de datos local o remota, ya que el enfoque es Código Limpio con MVVM.

Etapa 1. Incluir dependencias

Para construir nuestro proyecto con Dagger Hilt necesitamos incluir esta biblioteca en las dependencias del proyecto de Android. Primero, vayamos al build.gradle del proyecto e incluyamos:

// Top-level build file where you can add configuration options common to all sub-projects/modules.

plugins {

   ...

   id 'com.google.dagger.hilt.android' version '2.41' apply false

}



Ahora, en build.gradle (:app) agrega "kotlin-kapt" y las dependencias necesarias:

plugins {

   ...

   id 'kotlin-kapt'

   id 'com.google.dagger.hilt.android'

}


dependencies {


   ...


   // Dagger Hilt

   implementation "com.google.dagger:hilt-android:2.41"

   kapt "com.google.dagger:hilt-android-compiler:2.41"


   // Activity Extensions (to use 'by viewModels()'

   implementation "androidx.activity:activity-ktx:1.7.1"

}


La dependencia "androidx.activity:activity-ktx:version" servirá para facilitar la inyección de ViewModel, se verá en la práctica cuando implementemos el View.  

Etapa 2. Crear estructura de directorios y clases abajo


Esta estructura se encuentra típicamente en proyectos reales en el mercado. Creamos paquetes para cada capa para facilitar su comprensión y también para permitirles escalar de manera ordenada, siempre puedes agregar más repositorios, fuentes de datos, modelos y UIs según la necesidad.


Etapa 3. Inicializar Dagger Hilt en la clase App

Dagger Hilt debe iniciarse a través de una Custom Application con la notación @HiltAndroidApp. Esta y muchas otras notaciones son esenciales para construir una aplicación con Dagger Hilt, que se basa en las notaciones para construir el árbol de inyección de dependencia.

@HiltAndroidApp

class App: Application()


Necesitamos actualizar AndroidManifest.xml, indicando que usaremos la clase App como una aplicación personalizada.

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

   xmlns:tools="http://schemas.android.com/tools">


   <application

       android:name=".App"

       ...>

       ...

   </application>


</manifest>



Etapa 4. Crea el Model del dato con la clase Dice

Model es solo una clase del tipo de datos que sirve para definir cómo es el objeto dado y cuáles son sus atributos, en nuestro caso el dado solo tiene el atributo faces, que determina cuántas caras tiene el dado.

data class Dice(

   var faces: Int

)


Etapa 5. Implementar DataSource

Ahora implementemos el Data Source, responsable de comunicarse con la fuente de datos (que puede ser local o remota), que en este ejemplo será solo una función para obtener un valor aleatorio entre 1 y el número total de caras del dado.

Aquí la notación @Inject es importante en el constructor para que la clase pueda ser vista mediante la inyección de dependencia de Dagger Hilt, sin esta notación no podemos inyectar nada en DiceDataSource, ni podemos inyectar DiceDataSource en otra clase.

class DiceDataSource @Inject constructor() {


   fun rollDice(dice: Dice) = (1..dice.faces).random()

}


Etapa 6. Implementar el Repository

Finalmente, vamos a implementar el Repository, clase responsable por comunicarse con el Data Source. Esta capa abstraerá solo el resultado de la fuente de datos al ViewModel, por lo que no es necesario saber de dónde ni cómo proviene.

Nuevamente la notación @Inject, pero ahora en el constructor tendremos DiceDataSource como parámetro. Solo hacer un uso correcto de esta notación es suficiente para no preocuparnos por la inyección, ¡ya que Dagger Hilt se encarga del resto!

class DiceRepository @Inject constructor(

   private val dataSource: DiceDataSource

) {


   fun rollDice(dice: Dice) = dataSource.rollDice(dice)

}


Etapa 7. Implementar el ViewModel

A continuación tenemos la implementación de ViewModel, que inyectará la capa de Repositorio y expondrá el resultado del desplazamiento de datos a través de LiveData diceResult, y permitirá que la acción de desplazamiento de datos active la función rollDice de DiceRepository a través de una función del mismo nombre rollDice.

Tenga en cuenta que para vincular correctamente una clase ViewModel, Dagger Hilt necesita ver la notación @HiltViewModel.

@HiltViewModel

class MainViewModel @Inject constructor(

   private val diceRepository: DiceRepository

): ViewModel() {


   private var _diceResult = MutableLiveData<Int?>(null)

   var diceResult: LiveData<Int?> = _diceResult


   fun rollDice(dice: Dice) {

       _diceResult.value = diceRepository.rollDice(dice)

   }

}

Haremos con el resultado de diceRepository.rollDice(dice) que se actualice el valor de MutableLiveData _diceResult, que a su vez actualiza el valor del LiveData diceResult. Hacemos esto así para que la View no sea capaz de alterar el valor del resultado, pues la View debe escuchar el valor y reaccionar ante él.

Etapa 8. Implementar la View

Concluyendo nuestra implementación por capas, ahora vamos a ver la View, que será una Activity com una TextView y un Button. Para que Dagger Hilt inyecte todo correctamente (necesario en una View), es preciso usar la notación @AndroidEntryPoint.

Para inyectar el ViewModel en la View, usaremos el artifício "by viewModels()", por ser elegante, sucinto y diferente del uso del ​​ViewModelProvider. Dado lo anterior, no es necesaria la creación de una Factory por hacerse automáticamente.

@AndroidEntryPoint

class MainActivity : AppCompatActivity() {


   private val mainViewModel: MainViewModel by viewModels()


   private lateinit var diceResultText: TextView

   private lateinit var rollDiceButton: Button


   override fun onCreate(savedInstanceState: Bundle?) {

       super.onCreate(savedInstanceState)

       setContentView(R.layout.activity_main)


       diceResultText = findViewById(R.id.diceResultText)

       rollDiceButton = findViewById(R.id.rollDiceButton)


       rollDiceButton.setOnClickListener {

           mainViewModel.rollDice(Dice(20))

       }


       mainViewModel.diceResult.observe(this) {

           it?.let { diceResultText.text = it.toString() }

       }

   }

}

Percibe que añadimos el botón OnClickListener para disparar la función rollDice de MainViewModel usando un dado de 20 caras, y luego registramos la observación de LiveData diceResult para que la View haga los cambios de valor en ese objeto.

Etapa 9. Lanza los datos probando la app

Demo: https://www.youtube.com/watch?v=1uVZOkRyQ3s

Observa lo sencillo que es cambiar la capa de Data Source para datos provenientes de una base de datos o de una base de datos remota a través de una API sin tocar la capa de View, ya que el Repository solo abstrae lo que la View necesita ver.

También es posible actualizar la View a un diseño más moderno y hermoso sin preocuparse por la capa de datos y sin el riesgo de dañar algo en otra parte del código que no sea la View misma.

Clean Code facilita el mantenimiento y la inclusión de nuevos elementos, porque digamos que ahora queremos que aparezcan 2 o más datos en la pantalla, podemos incluirlos a través de la View sin cambiar también la fuente de datos. Otra posibilidad sería agregar una entrada de texto para recibir la cantidad de caras que el usuario desea en el dado. Siéntete libre de explorar este código y actuar en las diferentes capas.

Conclusión

Por supuesto, el ejemplo anterior es simplista y sería mucho más sencillo simplemente usar la operación de rollo de datos aleatorio al hacer clic en el botón, este caso no justifica el uso de Código Limpio con MVVM e inyección de dependencia, fue solo un ejemplo práctico de Implementación para no complicar la demostración de conceptos.

Recuerda que Clean Code y MVVM deben usarse en casos de uso que tengan sentido, como por ejemplo:

- Lista de ítems: Un caso de uso común en las aplicaciones de Android es mostrar una lista de elementos, como mensajes en una aplicación de correo electrónico o productos en una aplicación de comercio electrónico. El uso de código limpio y MVVM puede ayudar a garantizar que el código relacionado con esta funcionalidad esté bien estructurado, con una capa de modelo de datos clara y una separación clara entre la lógica de vista y el código comercial.

- Formularios de entrada de datos: Otro caso de uso común es la entrada de datos del usuario en formularios. El uso de código limpio y MVVM puede ayudar a garantizar que la validación de datos se realice de manera clara y consistente en toda la aplicación, lo que facilita al desarrollador lidiar con posibles errores.

- Autenticación de usuarios: Un caso de uso importante en muchas aplicaciones de Android es la autenticación de usuarios. El uso de código limpio y MVVM puede ayudar a garantizar que la lógica de autenticación esté claramente separada de la lógica de visualización, lo que hace que el código sea más fácil de probar y mantener. Comunicación con API: en muchas aplicaciones de Android, es necesario realizar llamadas a API para recuperar datos de backend. El uso de código limpio y MVVM puede ayudar a garantizar que la lógica para comunicarse con la API esté claramente separada de la lógica de visualización y el código comercial.

En el mercado laboral en aplicaciones reales, seguramente encontrarás casos de uso reales que aplican MVVM con Código Limpio, y ahora, después de leer este artículo, sabrás lo que está sucediendo detrás de esta arquitectura y podrás hacer un buen trabajo.

¿Te gustó este artículo? Así que agrega esta página a tus favoritos para consultarla cuando sea necesario y continúa estudiando y practicando el uso de Clean Code, MVVM y Dagger Hilt. Conviértete en un profesional deseado por el mercado laboral para las vacantes Android.

💡
Las opiniones y comentarios emitidos en este artículo son propiedad única de su autor y no necesariamente representan el punto de vista de Listopro.

Listopro Community da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.