Android sin DI. Enfoque basado en la arquitectura de actuación Bob Dahlberg | Octubre de 2020

Reactivo

Primero debemos reemplazar los marcos DI con otra forma de comunicación entre nuestros ViewModels y nuestros actores. Mi solución siempre ha aterrizado en alguna corriente reactiva. Fuerza la separación y hace de la arquitectura un corazón reactivo. He sido un gran usuario de Rx en el pasado y todavía lo considero una buena solución. Dado que uso mucho Kotlin y coututins, tiene sentido usar los conductos para esta corriente caliente. Si no es por el simple hecho de tener una biblioteca externa menos. Pero si es un gran usuario de Rx, le recomiendo que comience con eso. Y si está atrapado en Java, no veo tantas alternativas.

Esta es nuestra AppStream. El corazón es BroadcastChannel, por el que pasarán todas nuestras noticias. La función de envío inicia el proceso de cada mensaje recibido y lo envía al canal. Decidí hacer esto para que la función de envío de AppStreams no necesitara pausar la palabra clave. Esto lo hace disponible para que cualquier parte de su aplicación envíe un mensaje sin la necesidad de un contexto de rutina. AppStream también pone a disposición Flow, que abre una suscripción de canal cada vez que se llama. Al envolverlo en Flow, también se cerrará cuando se cancele el flujo.

Modelo de actor

Con el objeto AppStream, ahora podemos enviar mensajes desde nuestros ViewModels a nuestros actores. Entonces ahora necesitamos algunos actores. Pero, ¿cuál es el modelo del actor? Comencemos con esta definición de brianstorti.com/the-actor-model/

«Un actor es una unidad informática primitiva».
Está cosa quien recibe el mensaje y lo hace
algún cálculo basado en él. «

Lo que me atrae es que los actores tienen su propio estado cambiante privado. Y el único que puede mutar esta situación es el propio actor. También tiene una forma definida de comunicarse a través de mensajes procesados ​​uno tras otro en el orden en que se reciben, lo que trae música segura, separadora y reactiva a mis oídos.

Sin embargo, también viene con sus desafíos, especialmente en términos de arquitectura. Realmente tienes que comprometerte con tu arquitectura y tomar decisiones bien pensadas cuando algo es un actor y cuando es otra cosa, como un instrumento.

También debo enfatizar que esta arquitectura está inspirada en actores, no son actores puros y violé el modelo de actuación aquí y allá. Simplemente le robé oro y ajusté el resto para que se ajustara a la aplicación de Android, como puedo ver mejor.

Esto significa que otro bloque de construcción en nuestra arquitectura es un actor abstracto, podemos iniciarlo y detenerlo desde afuera. Al inicio, crea un código de canal interno (el creador del actor de Kotlin en la línea seis), luego itera a través de su canal y llama a la función act para cada mensaje recibido. En este caso, el canal es un buzón que representa el modelo activo.

Lo último que sucede en la función inicial es recopilar todos los mensajes del AppStream y pasarlos a los actores en la línea 11.

Implementación

Mostraré dos implementaciones ejemplares de un actor abstracto. Primero, el UserActor, que se ha simplificado para este ejemplo. Tiene un estado de variable privada, específicamente usuarios. Luego recibe mensajes y se ocupa solo del tipo LoginMessage.

Después de recibir LoginMessage, intentará cumplir el propósito de este mensaje llamando a la función de inicio de sesión privado con la entrada del mensaje. Si el usuario inicia sesión correctamente, muta su estado y lo deja como está. Lo último que hace un UserActor es enviar un UserState que contiene el estado actual y un posible error de la operación.

Esta forma de actuar, donde siempre se envía un mensaje con el estado actual, puede considerarse como un contrato para los actores que si alguien intenta influir en el estado, siempre recibirán un mensaje de estado sabiendo que la operación está completa.

Otra implementación que siempre uso es un actor de registro. Recibe todos los mensajes y los graba. En este ejemplo, pondré la etiqueta «>>» como saliente, «>>» para todos los mensajes que no sean de tipo, y marcaré todos los estados como entrantes «<<" para mostrar la buena narración de mi aplicación en los registros.

También suelo registrar esta historia en Firebase Crashlytics, por lo que cuando ocurre un bloqueo inesperado, también tengo los registros más recientes de lo que sucedió en mi aplicación. Incluso puedo usar la historia para controlar mi aplicación durante las pruebas. Al iniciar sesión en la fábrica, tenga cuidado de no registrar datos confidenciales. Más sobre eso en la tercera parte de esta serie.

Involucrando todo junto

Ahora conocemos todos los componentes. En el lado de la interfaz de usuario, tenemos View y ViewModel, que se comunica a través de LiveData. ViewModel envía y recibe mensajes de AppStream, y los actores reciben y envían mensajes y estados a través de AppStream. Ahora tenemos que combinar todas las partes y un aspecto, reemplazar el marco DI habitual configurado con el nuestro.

Gracias a que usamos un enfoque reactivo y tenemos un código muy separado, donde cada actor se conoce y solo se preocupa por sí mismo, tenemos una configuración simple.

Es tan simple que nuestros actores están conectados y listos para recibir mensajes. Y todos los desarrolladores perspicaces pueden notar que estoy insertando una dependencia en mi UserActor, es decir, la API. Pude decidir que NetworkActor también manejaría todas las llamadas HTTP de reenvío. Pero decidí que no por dos razones. Primero, porque es un código demasiado redundante para realizar una tarea estandarizada. Y segundo, porque los actores tienen un buzón y los mensajes que se le envían se hacen uno a uno. Para realizar múltiples llamadas a la API en paralelo, tendría que crear un actor de orquestación para todas estas solicitudes, creando un nuevo actor para cada solicitud, y esto es demasiado complicado para la mayoría de las aplicaciones que he creado.

Otro enfoque que he utilizado es que el actor de la red envíe al cliente un mensaje a todos los actores que lo utilizan. Pero también puede causar algunos problemas de sincronización, pero el enfoque es bueno.

Y luego está el problema de los datos persistentes, y pienso que escribir en el almacenamiento persistente es básicamente una operación sincrónica. Normalmente tengo PrefsActor y DBActor para ellos. Pero depende mucho de lo exigente que sea la base de datos. Si es solo un actor que puede editar la tabla o tener su propia base de datos.

Continuemos conectando nuestro ViewModel con nuestro AppStream para que nuestro lado más estándar de la interfaz de usuario pueda comunicarse con nuestra lógica empresarial y responder a los cambios de estado.

Primero, creo una secuencia interna para filtrar solo los mensajes de UserState. Luego usaré esta secuencia para crear el LiveData registrado y el mensaje de error LiveMata.

Dado que AppStream es un objeto y Kotlin tiene un objeto Singleton, puedo conectarme a los estados de flujo desde él. Lo mismo ocurre con la función de envío desde AppStream que se está importando. Me permite enviar mensajes desde cualquier lugar y, como tampoco está en pausa, las personas que llaman no tienen que tener una gama de Coroutines, lo que simplifica mucho.

Finalmente, echemos un vistazo a Ver para obtener una imagen completa. Eso no debería venir como una sorpresa. Insertamos un ViewModel y monitoreamos sus objetos LiveData para actualizar nuestra interfaz de usuario.

Pensamientos finales

Esta sección cubrió la mayoría de los conceptos básicos y mis pensamientos sobre el enfoque de la arquitectura. También guardé algunos consejos y trucos para el próximo artículo, donde usamos Kotlin como lenguaje para simplificar nuestro código.

Me gustaría que extrajera de este artículo que cuando utiliza marcos de integración de dependencias, existe un gran riesgo de que nosotros, como desarrolladores, desatemos la estructura y la separación, porque eso no es un problema cuando se trata de manejar dependencias. Pero esto puede ser un problema cuando todo está firmemente conectado a todo en su aplicación y necesita refactorizar una parte. Entonces, incluso los mejores marcos no te ayudarán.

Esta arquitectura reactiva y de acción me ayuda a estar al tanto del departamento, los efectos secundarios y la fuga de datos no intencional. La parte descriptiva me ayuda a resolver problemas con mucha facilidad, entenderlos e incluso probarlos.

Lleva un tiempo acostumbrarse y algo tan simple como leer un valor se vuelve más complicado cuando tienes que enviar y recibir mensajes. Pero gracias a las zanahorias, podemos crear algunos atajos para que se vea exactamente como leer un valor. En el próximo artículo te mostraré cómo hacerlo.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *