Entendiendo la reactividad con Combine (para Yago un meta crack)
Veremos mas de cerca cómo usar Combine + SwiftUI y su reactividad, vamos a entender publishers, subscribers, operators y como utilzarlos y como organizar el código.
Crearemos una App que nos muestra un formulario de registro muy sencillo que permitirá a los usuarios ingresar username, nombre, apellido, contraseña y validación de la contraseña, para crear una nueva cuenta en una App, vamos a implementar MVVM, este patrón Arquitectonico da como resultado un base de código limpio y facilitará la adición de nuevas funciones a la App, vamos a definir el ViewModel, va a contener las propiedades que tomarán la entrada del usuario.
Para la ContentView() usaremos un Form con varias Section para los distintintos campos de entrada, lo que nos da una apariencia muy limpia a nivel gráfico.
Antes de implementar la lógica de validación para nuestro formulario de registro, dedicaremos un tiempo para comprender como funciona el framework Combine.
Según Apple:
El framework Combine proporciona una API Swift declarativa para procesar valores a lo largo del tiempo. Estos valores pueden representar muchos tipos de eventos asincrónicos. Combine declara que los editores exponen valores que pueden cambiar con el tiempo y que los suscriptores reciben esos valores de los editores. ( fuente )
Echemos un vistazo más de cerca a un par de conceptos clave aquí para comprender qué significa esto y cómo nos ayuda.
Publishers
Los Publishers envían valores a uno o más suscriptores. Se ajustan al protocolo Publisher y declaran el tipo de salida y cualquier error que produzcan:
Un Publisher puede enviar cualquier número de valores a lo largo del tiempo o fallar con un error. El tipo asociado Output define qué tipos de valores puede enviar un Publisher, mientras que el tipo asociado Failure define el tipo de error con el que puede fallar. Un Publisher puede declarar que nunca falla especificando el Never de tipo asociado al error.
Subscribers
Los Subscribers, por otro lado, se suscriben a una instancia de Publisher específica y reciben un flujo de valores hasta que se cancela la suscripción.
Se ajustan al protocolo Subscriber . Para suscribirse a un Publisher, los tipos Input y Failure deben conformar y ajustarse a los Publishers Output y Failure.
Operators
Los Publishers y los Subscribers son la columna vertebral de la sincronización bidireccional de SwiftUI entre la interfaz gráfica y el modelo de datos. Esta sincronización es gracias al framework Combine.
Los Publishers, sin embargo, son lo más potente de Combine. Son métodos que operan en un Publisher, realizan cálculos y producen otro Publisher a cambio, OMGG.
- Por ejemplo, podemos usar un operador filter para ignorar valores en función de determinadas condiciones.
- Si necesitamos realiza una tarea costosa (como buscar información en la red), podemos usar un operador debounce para esperar hasta que el usuario deje de escribir.
- El operador map nos permite trasnformar valores de entrada de un cierto tipo en valores de salida de un tipo diferente.
Test validación del nombre del usuario
Con esto en mente, implementemos una validación simple para asegurarnos de que el usuario ingresa un nombre que contenga al menos cinco caracteres.
Todas las propiedades de nuestro ViewModel estan empaquetadas y usan la palabra reservada @Publisher que es un contenedor de propiedades (Property wrapper), esto lo que nos quiere indicar es que cada propiedad tiene su propio publisher, al que podemos suscribirnos.
Para indicar si un nombre de usuario es válido, transformamos la entra del usuario de un “String” a un “Bool” usando un operador map.
El suscriptor “assing” consume el resultado de esta transformación y como su nombre lo indica, asigna el valor recibido a la propiedad “valid” de salida de nuestro ViewModel, gracias al enlace que hemos configurado anteriormente en la “ContentView.swift”, SwiftUI actualizará automáticamente la interfaz del usuario cada vez que cambie esta propiedad.
Pero que hace el “debounce” y el “removeDuplicate”, esto hace que Combine sea una herramineta tan potente y útil para conectar la interfaz del usuario y la logica de negocio, en todas las interfaces de usuarios, tenemos que lidiar con el hecho de que el usuario pueda escribir mas rápido de lo que podemos obtener la información que solicita. Por ejemplo, al escribir el nombre de usuario, no es necesario verificar si el nombre de usuario es válido o está disponible para cada letra que escribe el usuario. Es suficiente realizar esta verificación solo una vez qiue deje de escribir (o se lo piense un poco).
El operador“debounce” nos permite especificar que queremos esperar una pausa en la entrega de eventos, por ejemplo cuando el usuario deja de escribir.
Del mismo modo, el operador “removeDuplicates” publicará eventos solo si son diferentes de cualquier evento anterior. Por ejemplo, si el usuario escribe primero Andrés, luego Felipe y luego Andrés nuevamente, solo recibiremos Andrés una vez. esto ayuda a que nuestra interfaz de usuario funcione de manera mas eficiente.
El resultado de esta cadena de llamadas es el “Cancellable”, que podemos usar para cancelar el procesamiento si es necesario(muy útil para cadenas de ejecución más largas). Almacenaremos esto (y todos los demás que crearemos mas adelante) en una propiedad “Set<AnyCancellable>”, para que podamos limpiar “deinit”.
Vamos a ver el código, mmm es algo distinto verdad??, pues vamos a entender porque ha cambiado.
Validación de las contraseñas
Ahora cambiemos de tema y vamos a ver como podemos realizar una lógica de validación de varias pasos. Esto es necesario ya que los campos de contraseña por ejemplo debe cumplir con varios requisitos: no deben estar vacíos, deben coincidir y además debe ser los suficientemente segura no?. Además de trasnformar los valores de entrada en un “Bool” para indicar si las contraseñas cumplen con nuestros requisitos, también queremos proporcionar alguna guía para el usuario devolviendo un mensaje de advertencia apropiado.
Paso a paso vamos a implementar para validar las contraseñas que ingresa el usuario.
Verificar si la contraseña está vacía es bastante sencillo, y vemos además que es muy similar a la implementacion de name, lastname, username, etc. Sin embargo, en lugar de asignar el resultado directamente de la transformación a la propiedad de salida “isValid” devolvemos un AnyPublisher<Bool, Never>. Esto se hace para que luego podamos combinar varios Publishers en una cadena de varias capas antes de suscribirnos al resultado final (válido o no).
Para verificar si dos propiedades separadas contiene Strings iguales, usamos el operador CombineLatest. Sobre todo es para vincular el respectivo SecureField cada vez que el usuario ingresa un caracter, y queremos comparar el último valor para cada cuno de esos campos.
Para calcular la seguridad de la contraseña usaremos Vera con soporte en Swift Package Manager, basado en la librería Navajo de Matt que nos permite convertir la enumeración resultante en un Bool encadenado a otro Publisher(isPasswordStrongEnoughPublisher). Esta es la primera vez que vamos a suscribirnos a uno de nuestros propios Editors y vamos ver como podemos combinar varios Editors para producir el resultado requerido.
Véis que siempre invocamos eraseToAnyPublishers(), eso se hace para borrar el tipo y asegura que no terminemos con algunos tipos anidados en el retorno, algo asi: Publishers.map<Publishers.removeDuplicates<Publishres.debounce<Publishers<Srtring>.Publishers,RunLoop>>,Bool>(watttt??).
Sigamos, así que ahora bien, sabemos mucho mas sobre las contraseñas que ha ingresado el usuario, reduzcamo esto para saber si la contraseña es valida, ya sabemos que tenemso un operador CombineLatest, pero esta vez tenemos 3 parametros usaremos otro perador CombineLatest3(en serio!! Yago),que es capaz de tomar tres parámetros de entrada.
La razón por la que asignamos los tres valores booleanos en una sola enum es que queremos poder producir un mensaje de advertencia adecuado según el resultado de la validación. la idea es decirle al usuario porque su contraseña no es valida.
Paso final para calcular el resultado final de la validación, para ello debemos combinar el resultado de la validación de todos los campos del formulario, pero como tenemos mucho mas campos que lo que podemos operador con el Publishers.CombineLatest(), así que usaremos la opcion Latest4().
Validaremos el Name, Email, Username y la Password, y lo transformamos en un valor Bool para controlar el botón si debemos habilitarlo o no.
Posteriormente implementamos el metodo init() del ViewModel, y gestionamos el mensaje que le mostraremos al usuario y listo!, todo el codigo fuente lo encontrareis aquí.
Veamos como queda la vista luego de darle unos retoques:
Yeah!! Yago… ahora si sabes usar Combine.. ;)