Flutter Arquitectura Limpia [13] – Inyección de dependencia

Flutter Arquitectura Limpia [13] – Inyección de dependencia

 

Tenemos todas las piezas individuales de la arquitectura de la aplicación en su lugar. Sin embargo, antes de poder utilizarlos creando una interfaz de usuario, debemos conectarlos. Dado que cada clase se desacopla de sus dependencias al aceptarlas a través del constructor, de alguna manera tenemos que pasarlas.

Hemos estado haciendo esto todo el tiempo en pruebas con las clases simuladas con mock. Ahora, sin embargo, llega el momento de pasar en clases de producción reales utilizando un localizador de servicios.

Debo aclarar que el contenido original es de Resocoder,  lo que he hecho  es una traducción al español del contenido. Al final de este artículo está el video en inglés para que vayan ilustrándose mejor y ya tengan una claridad de lo expuesto

Curso TDD Arquitectura Limpia

001 draw

Esta publicación es solo una parte de una serie de tutoriales. ¡Todas las demás partes las puede ver aquí y aprenda a diseñar sus aplicaciones Flutter!

Inyectando Dependencias

001 healthcare and medical

Casi todas las clases que creamos en este curso hasta ahora tienen algunas dependencias. Incluso en una aplicación pequeña como la aplicación Number Trivia que estamos construyendo, hay mucho que configurar. Existen múltiples localizadores de servicios adecuados para Flutter y, como recordará de la segunda parte, estamos usando get_it.

Ahora repasaremos todas las clases desde la parte superior del “flujo de llamadas” hacia abajo. En nuestro caso, esto significa comenzar desde NumberTriviaBloc y terminar con dependencias externas como SharedPreferences.

 

Clean Architecture Flutter Diagram

Vamos a configurar todo en un nuevo archivo llamado injection_container.dart ubicado directamente dentro de la carpeta raíz de lib. En esencia, vamos a completar los constructores con argumentos apropiados. La estructura básica del archivo es la siguiente:

injection_container.dart

final sl = GetIt.instance;

void init() {
  //! Features - Number Trivia

  //! Core

  //! External

}

Si su aplicación tiene múltiples funciones, es posible que desee crear archivos de inyección_contenedores más pequeños con funciones init () por cada función solo para mantener las cosas organizadas.
Luego llamaría a estas funciones init () específicas de la función desde la principal.

La función init () se llamará inmediatamente cuando la aplicación se inicie desde main.dart. Será dentro de esa función donde todas las clases y contratos se registrarán y posteriormente también se inyectarán usando la instancia singleton de GetIt almacenada dentro de sl (que es la abreviatura de un localizador de servicios).

El paquete get_it admite la creación de singletons y fábricas de instancias. Como no tenemos ningún estado dentro de ninguna de las clases, vamos a registrar todo como un singleton, lo que significa que solo se creará una instancia de una clase durante la vida útil de la aplicación. Solo habrá una excepción a esta regla: NumberTriviaBloc que, siguiendo el “flujo de llamadas”, vamos a registrar primero.

 

Registrando una Fábrica

003 architecture and city

El proceso de registro es muy sencillo. Simplemente crea una instancia de la clase como de costumbre y pasa sl () en cada parámetro del constructor. Como puede ver, la clase GetIt tiene el método call () para facilitar la sintaxis, al igual que nuestros casos de uso también tienen un método call ().

injection_container.dart

//! Features - Number Trivia
//Bloc
sl.registerFactory(
  () => NumberTriviaBloc(
    concrete: sl(),
    random: sl(),
    inputConverter: sl(),
  ),
);

Los titulares de lógica de presentación como Bloc no deben registrarse como singletons. Están muy cerca de la interfaz de usuario y si su aplicación tiene varias páginas entre las que navega, probablemente desee realizar una limpieza (como cerrar Streams of a Bloc) del método dispose () de un StatefulWidget.

Tener un singleton para las clases con este tipo de eliminación conduciría a intentar usar un soporte lógico de presentación (como Bloc) con Streams cerrados, en lugar de crear una nueva instancia con Streams abiertos cada vez que intente obtener un objeto de ese escriba de GetIt.

Usando la inferencia de tipos, la llamada a sl () determinará qué objeto debe pasar como argumento del constructor dado. Por supuesto, esto solo es posible cuando el tipo en cuestión también está registrado. Es evidente que ahora necesitamos registrar los casos de uso GetConcreteNumberTrivia y GetRandomNumberTrivia y también el InputConverter. Estas no se registrarán como fábricas, sino que serán singletons.

 

Registrando Singletons

002 note

GetIt nos da dos opciones cuando se trata de singletons. Podemos registerSingleton o registerLazySingleton. La única diferencia entre ellos es que un singleton no perezoso siempre se registra inmediatamente después de que se inicia la aplicación, mientras que un singleton perezoso se registra solo cuando se solicita como dependencia para otra clase.

En nuestro caso, elegir entre el registro diferido y el registro regular no hace la diferencia, ya que la aplicación Number Trivia tendrá solo una página con un bloque y un “árbol de dependencia”, lo que significa que incluso los singleton perezosos se registrarán de inmediato. Vamos a optar por registrarse en LazySingleton.

 

Dependencias Bloc 

Para mantener los registros organizados, todo va bajo un comentario especial. También es posible crear funciones separadas, pero creo que eso puede empeorar la legibilidad del código si solo tiene unos pocos registros como nosotros.

injection_container.dart

//! Features - Number Trivia
...
// Use cases
sl.registerLazySingleton(() => GetConcreteNumberTrivia(sl()));
sl.registerLazySingleton(() => GetRandomNumberTrivia(sl()));

//! Core
sl.registerLazySingleton(() => InputConverter());

Repositorio del Registro

Si bien InputConverter es una clase independiente, ambos casos de uso requieren un NumberTriviaRepository. Tenga en cuenta que dependen del contrato y no de la implementación concreta. Sin embargo, no podemos instanciar un contrato (que es una clase abstracta). En cambio, tenemos que instanciar la implementación del repositorio. Esto es posible especificando un parámetro de tipo en el método registerLazySingleton.

injection_container.dart

//! Features - Number Trivia
...
// Repository
sl.registerLazySingleton<NumberTriviaRepository>(
  () => NumberTriviaRepositoryImpl(
    remoteDataSource: sl(),
    localDataSource: sl(),
    networkInfo: sl(),
  ),
);
Esto demuestra muy bien la utilidad del acoplamiento suelto. Dependiendo de las abstracciones en lugar de las implementaciones, no solo permite las pruebas (¡siempre pasamos mocks en las pruebas!), Sino que también permite intercambiar sin problemas la implementación subyacente del NumberTriviaRepository por una diferente sin ningún cambio en las clases dependientes.

Fuentes de datos e información de red

El repositorio también depende de los contratos, por lo que nuevamente vamos a especificar un parámetro de tipo manualmente.

 

injection_container.dart

//! Features - Number Trivia
...
// Data sources
sl.registerLazySingleton<NumberTriviaRemoteDataSource>(
  () => NumberTriviaRemoteDataSourceImpl(client: sl()),
);

sl.registerLazySingleton<NumberTriviaLocalDataSource>(
  () => NumberTriviaLocalDataSourceImpl(sharedPreferences: sl()),
);

//! Core
...
sl.registerLazySingleton<NetworkInfo>(() => NetworkInfoImpl(sl()));

Dependencias Externas

004 window

Nos hemos movido hasta el final de la cadena de llamadas en el ámbito de las bibliotecas de terceros. Necesitamos registrar un http.Client, DataConnectionChecker y también SharedPreferences. El último es un poco complicado.

A diferencia de todas las otras clases, SharedPreferences no puede simplemente instanciarse con una llamada de constructor regular. En cambio, tenemos que llamar a SharedPreferences.getInstance (), que es un método asincróno. Puede pensar que simplemente podemos hacer esto:

 

sl.registerLazySingleton(() async => await SharedPreferences.getInstance());

Sin embargo, la función de orden superior en este caso devolvería un Future <SharedPreferences>, que no es lo que queremos. En su lugar, queremos registrar una instancia simple de SharedPreferences.

Para eso, tenemos que hacer await a la llamada para obtenerInstance () fuera del registro. Esto requerirá que cambiemos la firma del método init ()

injection_container.dart

Future<void> init() async {
  ...
  //! External
  final sharedPreferences = await SharedPreferences.getInstance();
  sl.registerLazySingleton(() => sharedPreferences);
  sl.registerLazySingleton(() => http.Client());
  sl.registerLazySingleton(() => DataConnectionChecker());
}

Inicializando

El método init () no solo se llamará mágicamente por sí mismo. Es nuestra responsabilidad invocarlo y el mejor lugar para la inicialización de este localizador de servicios es dentro de la función main ().

main.dart

import 'injection_container.dart' as di;

void main() async {
  await di.init();
  runApp(MyApp());
}
Es importante hace await al Future a pesar de que solo contiene void. Definitivamente no queremos que la UI se construya antes de que cualquiera de las dependencias tenga la oportunidad de registrarse.

¿Qué sigue?

La inyección de dependencia era el eslabón perdido entre el código de producción y el código de prueba, en el que inyectamos las dependencias manualmente con clases simuladas con mock. Ahora que hemos implementado el localizador de servicios, nada puede impedirnos escribir widgets de Flutter que utilizarán todos los bits de código que hemos escrito hasta ahora al mostrar una UI completamente funcional al usuario.

Aquí les comparto el video

 

Previous

EDGE AI ¿Qué es?

Flutter Arquitectura Limpia [14] – Interfaz del Usuario

Next

Deja un comentario