Saltar al contenido principal

Comunicación entre capas

Cómo implementar la inyección de dependencias para comunicarse entre las capas de MVVM.

Además de definir responsabilidades claras para cada componente de la arquitectura, es importante considerar cómo se comunican dichos componentes. Esto se refiere tanto a las reglas que dictan la comunicación como a la implementación técnica de cómo se comunican. La arquitectura de una aplicación debería responder a las siguientes preguntas:

  • ¿Qué componentes tienen permitido comunicarse con qué otros componentes (incluyendo componentes del mismo tipo)?
  • ¿Qué exponen estos componentes como salida entre sí?
  • ¿Cómo se "conecta" una capa determinada a otra capa?

Un diagrama que muestra los componentes de la arquitectura de la aplicación.

Utilizando este diagrama como guía, las reglas de participación son las siguientes:

ComponenteReglas de participación
View
  1. Una view solo tiene conocimiento de exactamente un view model, y nunca tiene conocimiento de ninguna otra capa o componente. Cuando se crea, Flutter pasa el view model a la view como argumento, exponiendo los datos y los callbacks de comandos del view model a la view.
ViewModel
  1. Un ViewModel pertenece a exactamente una view, la cual puede ver sus datos, pero el model nunca necesita saber que existe una view.
  2. Un view model tiene conocimiento de uno o más repositorios, los cuales se pasan al constructor del view model.
Repository
  1. Un repository puede tener conocimiento de muchos servicios, los cuales se pasan como argumentos al constructor del repository.
  2. Un repository puede ser utilizado por muchos view models, pero nunca necesita tener conocimiento de ellos.
Service
  1. Un servicio puede ser utilizado por muchos repositorios, pero nunca necesita tener conocimiento de un repositorio (o cualquier otro objeto).

Inyección de dependencias

#

Esta guía ha mostrado cómo estos diferentes componentes se comunican entre sí mediante el uso de entradas y salidas. En cada caso, la comunicación entre dos capas se facilita al pasar un componente a los métodos constructores (de los componentes que consumen sus datos), como un Service a un Repository.

dart
class MyRepository {
  MyRepository({required MyService myService})
          : _myService = myService;

  late final MyService _myService;
}

Sin embargo, algo que falta es la creación de objetos. ¿En qué lugar, de una aplicación, se crea la instancia de MyService para que pueda ser pasada a MyRepository? La respuesta a esta pregunta involucra un patrón conocido como dependency injection (inyección de dependencias).

En la aplicación Compass, la dependency injection se gestiona utilizando package:provider. Basándose en su experiencia construyendo aplicaciones en Flutter, los equipos de Google recomiendan usar package:provider para implementar la inyección de dependencias.

Los servicios y repositorios se exponen en el nivel superior del árbol de Widget de la aplicación Flutter como objetos Provider.

dependencies.dart
dart
runApp(
  MultiProvider(
    providers: [
      Provider(create: (context) => AuthApiClient()),
      Provider(create: (context) => ApiClient()),
      Provider(create: (context) => SharedPreferencesService()),
      ChangeNotifierProvider(
        create: (context) => AuthRepositoryRemote(
          authApiClient: context.read(),
          apiClient: context.read(),
          sharedPreferencesService: context.read(),
        ) as AuthRepository,
      ),
      Provider(create: (context) =>
        DestinationRepositoryRemote(
          apiClient: context.read(),
        ) as DestinationRepository,
      ),
      Provider(create: (context) =>
        ContinentRepositoryRemote(
          apiClient: context.read(),
        ) as ContinentRepository,
      ),
      // In the Compass app, additional service and repository providers live here.
    ],
    child: const MainApp(),
  ),
);

Los servicios se exponen únicamente para que puedan ser inyectados inmediatamente en los repositorios a través del método BuildContext.read de provider, como se muestra en el fragmento anterior. Luego, los repositorios se exponen para que puedan ser inyectados en los view models según sea necesario.

Un poco más abajo en el árbol de Widget, los view models que corresponden a una pantalla completa se crean en la configuración de package:go_router, donde se vuelve a utilizar provider para inyectar los repositorios necesarios.

router.dart
dart
// This code was modified for demo purposes.
GoRouter router(
  AuthRepository authRepository,
) =>
    GoRouter(
      initialLocation: Routes.home,
      debugLogDiagnostics: true,
      redirect: _redirect,
      refreshListenable: authRepository,
      routes: [
        GoRoute(
          path: Routes.login,
          builder: (context, state) {
            return LoginScreen(
              viewModel: LoginViewModel(
                authRepository: context.read(),
              ),
            );
          },
        ),
        GoRoute(
          path: Routes.home,
          builder: (context, state) {
            final viewModel = HomeViewModel(
              bookingRepository: context.read(),
            );
            return HomeScreen(viewModel: viewModel);
          },
          routes: [
            // ...
          ],
        ),
      ],
    );

Dentro del view model o repositorio, el componente inyectado debe ser privado. Por ejemplo, la clase HomeViewModel se ve así:

home_viewmodel.dart
dart
class HomeViewModel extends ChangeNotifier {
  HomeViewModel({
    required BookingRepository bookingRepository,
    required UserRepository userRepository,
  })  : _bookingRepository = bookingRepository,
        _userRepository = userRepository;

  final BookingRepository _bookingRepository;
  final UserRepository _userRepository;

  // ...
}

Los métodos privados evitan que la vista, que tiene acceso al view model, llame a métodos del repositorio directamente.

Esto concluye el recorrido por el código de la aplicación Compass. Esta página solo recorrió el código relacionado con la arquitectura, pero no cuenta toda la historia. La mayor parte del código de utilidades, el código de Widget y el estilo de UI se ignoró. Explora el código en el repositorio de la aplicación Compass para obtener un ejemplo completo de una aplicación robusta en Flutter construida siguiendo estos principios.

Comentarios

#

Dado que esta sección del sitio web está evolucionando, ¡agradecemos tus comentarios!