Un Flutter Más Limpio Vol. 6: Modelando datos
Cómo transformar utilizar entidades en modelos
Una vez finalizada la capa de dominio y todos los comportamientos generales de nuestro proyecto mediante abstracción, ya podemos empezar a usar todas esas interfaces que hicimos para crear sus implementaciones.
Y bueno, al fin cambiamos de capa. Esta vez le toca a la capa de datos, empezando por los modelos.
Capa de datos
En la capa de dominio la base eran las entidades. Estas definían los tipos de datos propios en el proyecto y sus propiedades esenciales.
En la capa de datos pasa de la misma manera, debemos de establecer los tipos de dato que vamos a manipular en los métodos que ocupa un repositorio (ahora implementado) al traer datos.
💡 En este volumen vamos a hacer bastante referencia al volumen 3. Si no lo han leído, aquí les dejo el link.
En esta nueva capa ya nos encontramos con nuestros componentes que ya se comunican directamente con un API externo de cualquier tipo, ya sea Firebase, un REST API, etc.
La construcción de la capa de datos se basa en primero crear los modelos, luego creamos los datasources que hacen la petición de datos directamente y finalmente mandamos a llamar un datasource desde el repositorio para que nos devuelva información, ya sea en datos primitivos o modelos.
Aclaro que los repositorios son implementaciones de otros repositorios abstractos y los modelos son herederos de entidades (que son clases como tal, no interfaces), pero eso no significa que los datasources sean una sola clase y ya.
Los datasources también deben tener una clase abstracta para poder hacer lo mismo que con los repositorios: poder implementar con un paquete distinto su interfaz sin afectar alguna capa externa y poder realizar mejores pruebas en base a la interfaz.
Ya vamos a los modelos.
Modelos
Supongo que ustedes ya han definido alguna carpeta en sus proyectos llamada models
. Aquí realmente no hay mucha diferencia, con nuestras entidades queríamos dejar lo menos posible en propiedades y métodos porque nuestros modelos se iban a encargar de agregar esa funcionalidad.
Un método constructor muy famoso en Flutter y Dart es el fromJson
. Este constructor nos permite parsear un JSON decodificado a un Map y así serializar nuestros datos cuando construimos el objeto.
Ese tipo de funcionalidad es la que contiene un modelo, ya que podemos tener múltiples modelos que hereden de esa entidad y definan un comportamiento distinto basado en su implementación.
Además nos permite tener entidades que pueden utilizarse independientemente de si contacta a un backend alojado en Firebase o si es un REST API propio.
Nosotros vamos a hacer nuestros modelos en base a lo que esta entidad defina, entonces creamos primeramente un modelo orientado por si viene de un API, al que vamos a nombrar StoreItemAPIModel
.
Noten que el constructor crea los parámetros dentro de sí y no vuelve a declarar propiedades que ya definimos en la entidad, sólo tenemos que enviar estas variables en el constructor a la clase padre llamado a super
.
Igualmente añadimos un constructor extra, el famoso fromJson
. Este constructor es la funcionalidad adicional que va a contener nuestro modelo con respecto a la entidad.
Y si queremos hacer otro modelo con las mismas propiedades de nuestra entidad, nos sale muy sencillo.
Es exactamente lo mismo que hacemos con el otro pero tiene métodos distintos ya que está orientado a un servicio diferente. De igual manera, la variable isAvailable
que definimos queda solamente en nuestro modelo, por lo que debemos declararla fuera del constructor.
Otra cosa que podemos destacar de este modelo es que también hay que implementarle otro fromJson
porque no extiende de nuestra clase anterior y los constructores quedan al nivel del modelo.
Este último punto puede ser algo medio molesto y repetitivo. Para utilizar el constructor fromJson
tendríamos que heredar de StoreItemAPIModel
, pero aún así se nos complica al llamar el otro constructor. De este último párrafo quédense con el primer enunciado e ignoren el resto ya que es un completo enredo.
Mejores maneras de hacer modelos
Con la forma que vimos de hacer modelos anteriormente, nos sobra. Pero esto no significa que eso mismo no se puede automatizar. Hay dos paquetes que yo ocupo bastante para mis modelos y en general cualquier clase inmutable que tenga (un estado, por ejemplo).
Equatable + json_serializable
El primer paquete que les voy a mencionar es Equatable. Hasta ahora este es el paquete con el que yo hago entidades y, por consecuencia, modelos. Nos permite crear un clase cuyas propiedades no cambian, además que nos da un método que debemos sobre escribir que se llama props
, este nos devuelve una lista con las propiedades que queramos de la clase. Muy útil y completo.
Una vez creada nuestra entidad, les sugiero combinar Equatable con json_serializable y json_annotation para crear sus modelos con el constructor fromJson
y el método toJson
para poder serializar de cualquier API que devuelva en formato JSON.
Les recuerdo que json_serializable debe generar el código en un archivo con extensión **.g.dart
. Para esto se debe correr el comando:
Igualmente si queremos crear nuestro modelo para Firestore, solo extendemos al modelo que creamos anteriormente para utilizar su constructor fromJson
y listo.
Aunque Equatable hace lo necesario, y lo hace bien, no incluye un método importante de las clases inmutables: copyWith
. Por esto les voy a presentar la segunda alternativa.
Freezed
Para solucionar el problema de lo mucho que tarda la creación de todos esos constructores, freezed es una excelente opción. Para saber más de este paquete, pueden ver mi artículo por .
Freezed incluye en sí lo siguiente:
- Inmutabilidad (
copyWith
incluido). - Igualdad de valores.
- Uniones.
- Compatibilidad con json_serializable.
Así que podemos definir todos los modelos en una misma clase abstracta y utilizar los métodos en ellas mismas de esta manera…
Pero no todo es bonito en Freezed, ¿notan el problema?
Claro, las uniones son buenísimas para crear todos los posibles constructores sin tener que estar creando varios archivos por separado. Pero esa separación de clases y archivos es la que nos permite tener entidades y modelos por separado.
Una clase Freezed no puede ser subclase de otra e intentar extender la funcionalidad de una clase Freezed es una inversión de tiempo que no vale la pena y se pierde el tiempo que nos ahorramos generando el código.
¿Equatable o Freezed?
Realmente viene siendo una cuestión de gustos ya que el resultado es muy similar. Pero en el caso de CleanScope, preferimos usar Equatable para entidades y modelos, ya que nos permite una mejor abstracción y control sobre el código.
Equatable tiene el método de props
que en muchas ocasiones resulta bien útil. Freezed tiene integrado el copyWith
y muchos otros métodos que detallo en mi artículo que cité antes, como lo son el when
, maybeWhen
, map
, maybeMap
.
Freezed nos encanta, de hecho lo usamos para generar los estados para la lógica en la capa de presentación, pero no nos cumple para el nivel de abstracción que necesitamos en los modelos. Es una limitante con los generadores de código, te quitan control sobre tu código hasta cierto punto.
Cuando terminemos esta serie de artículos, vamos a proponer una versión minimalista de CleanScope donde sí vamos a usar Freezed para los modelos ya que vamos a tener menos capas de abstracción. Pero eso va a ser en otra ocasión.
Por el momento, nos quedamos con Equatable para nuestros modelos, en combinación con json_serializable.
Lo de siempre…
Si aprendiste algo nuevo y te fue de utilidad, podés compartir este artículo para ayudar a otro/a desarrollador(a) a seguir mejorando su productividad y calidad al escribir aplicaciones con Flutter.
También hay una versión de este mismo artículo en inglés publicado en dev.to. De nada. 🇺🇸
Además, si te gustó este contenido, podés encontrar aún más y seguir en contacto conmigo en mis redes sociales:
Originally published at http://github.com.