Skip to content

Registros Múltiples

get_it proporciona dos enfoques diferentes para registrar múltiples instancias del mismo tipo, cada uno adecuado para diferentes casos de uso.

Resumen de Dos Enfoques

Enfoque 1: Registro con Nombre (Siempre Disponible)

Registra múltiples instancias del mismo tipo dándole a cada una un nombre único. Esto está siempre disponible sin ninguna configuración.

dart
// Register multiple REST services with different configurations
getIt.registerSingleton<ApiClient>(
  ApiClient('https://api.example.com'),
  instanceName: 'mainApi',
);

getIt.registerSingleton<ApiClient>(
  ApiClient('https://analytics.example.com'),
  instanceName: 'analyticsApi',
);

// Access individually by name
final mainApi = getIt<ApiClient>(instanceName: 'mainApi');
final analyticsApi = getIt<ApiClient>(instanceName: 'analyticsApi');

Mejor para:

  • ✅ Diferentes configuraciones del mismo tipo (endpoints dev/prod)
  • ✅ Conjunto conocido de instancias accedidas individualmente
  • ✅ Feature flags (implementación antigua/nueva)

Enfoque 2: Registros Múltiples Sin Nombre (Requiere Opt-In)

Registra múltiples instancias sin nombres y recupéralas todas de una vez con getAll<T>(). Requiere opt-in explícito.

dart
// Enable feature first
getIt.enableRegisteringMultipleInstancesOfOneType();

// Register multiple plugins without names
getIt.registerSingleton<Plugin>(CorePlugin());
getIt.registerSingleton<Plugin>(LoggingPlugin());
getIt.registerSingleton<Plugin>(AnalyticsPlugin());

// Get all at once
final Iterable<Plugin> allPlugins = getIt.getAll<Plugin>();

Mejor para:

  • ✅ Sistemas de plugins (los módulos pueden añadir implementaciones)
  • ✅ Patrones de observer/event handler
  • ✅ Cadenas de middleware
  • ✅ Cuando no necesitas acceder a instancias individualmente

Puedes Combinar Ambos Enfoques

Los registros con nombre y sin nombre pueden coexistir. getAll<T>() devuelve ambos, instancias con nombre y sin nombre.


Registro con Nombre

Todas las funciones de registro aceptan un parámetro instanceName opcional. Cada nombre debe ser único por tipo.

Uso Básico

dart
// Register multiple REST services with different base URLs
getIt.registerSingleton<RestService>(
  RestServiceImpl('https://api.example.com'),
  instanceName: 'mainApi',
);

getIt.registerSingleton<RestService>(
  RestServiceImpl('https://analytics.example.com'),
  instanceName: 'analyticsApi',
);

Funciona con Todos los Tipos de Registro

El registro con nombre funciona con cada método de registro:

dart
// Singleton
getIt.registerSingleton<Logger>(
  FileLogger(),
  instanceName: 'fileLogger',
);

// Lazy Singleton
getIt.registerLazySingleton<Cache>(
  () => MemoryCache(),
  instanceName: 'memory',
);

// Factory
getIt.registerFactory<Report>(
  () => DailyReport(),
  instanceName: 'daily',
);

// Async Singleton
getIt.registerSingletonAsync<Database>(
  () async => Database.connect('prod'),
  instanceName: 'production',
);

Casos de Uso de Registro con Nombre

Múltiples conexiones a base de datos:

dart
getIt.registerSingletonAsync<Database>(
  () async => Database.connect('postgres://main-db'),
  instanceName: 'mainDb',
);

getIt.registerSingletonAsync<Database>(
  () async => Database.connect('postgres://analytics-db'),
  instanceName: 'analyticsDb',
);

getIt.registerSingletonAsync<Database>(
  () async => Database.connect('postgres://cache-db'),
  instanceName: 'cacheDb',
);

Registros Múltiples Sin Nombre

Para sistemas de plugins, observers y middleware donde quieres recuperar todas las instancias de una vez sin conocer sus nombres.

Habilitando Registros Múltiples

Por defecto, get_it previene registrar el mismo tipo múltiples veces (sin nombres de instancia diferentes) para detectar registros duplicados accidentales, que usualmente son bugs.

Para habilitar registros múltiples del mismo tipo, debes hacer opt-in explícitamente:

dart
getIt.enableRegisteringMultipleInstancesOfOneType();

¿Por qué opt-in explícito?

  • Previene bugs: Registrar accidentalmente el mismo tipo dos veces usualmente es un error
  • Protección contra cambios breaking: El código existente no se romperá por cambios de comportamiento no intencionados
  • Intención clara: Hace obvio que estás usando el patrón de registro múltiple
  • Tipado seguro: Te fuerza a ser consciente de que el comportamiento de get<T>() cambia

Importante

Una vez habilitada, esta configuración aplica globalmente a toda la instancia de get_it. No puedes habilitarla solo para tipos específicos.

Esta característica no puede deshabilitarse una vez habilitada. Incluso llamar a getIt.reset() limpiará todos los registros pero mantendrá esta característica habilitada. Esto es intencional para prevenir cambios breaking accidentales en tu aplicación.


Registrando Múltiples Implementaciones

Después de llamar a enableRegisteringMultipleInstancesOfOneType(), puedes registrar el mismo tipo múltiples veces:

dart
// First unnamed registration
getIt.registerSingleton<Plugin>(CorePlugin());

// Second unnamed registration (now allowed!)
getIt.registerSingleton<Plugin>(LoggingPlugin());

// Named registrations (always allowed - even without enabling)
getIt.registerSingleton<Plugin>(FeaturePlugin(), instanceName: 'feature');

Sin Nombre + Con Nombre Juntos

Todos los registros coexisten - tanto sin nombre como con nombre. getAll<T>() los devuelve todos.


Recuperando Instancias

Usando get<T>() - Devuelve Solo la Primera

Cuando existen múltiples registros sin nombre, get<T>() devuelve solo la primera instancia registrada:

dart
getIt.enableRegisteringMultipleInstancesOfOneType();

getIt.registerSingleton<Plugin>(CorePlugin());
getIt.registerSingleton<Plugin>(LoggingPlugin());
getIt.registerSingleton<Plugin>(AnalyticsPlugin());

final plugin = getIt<Plugin>();
// Returns: CorePlugin (the first one only!)

Cuándo usar get()

Usa get<T>() cuando quieras la implementación "por defecto" o "primaria". ¡Regístrala primero!

Usando getAll<T>() - Devuelve Todas

Para recuperar todas las instancias registradas (tanto sin nombre como con nombre), usa getAll<T>():

dart
  getIt.enableRegisteringMultipleInstancesOfOneType();

  getIt.registerSingleton<Plugin>(CorePlugin()); // unnamed
  getIt.registerSingleton<Plugin>(LoggingPlugin()); // unnamed
  getIt.registerSingleton<Plugin>(AnalyticsPlugin(),
      instanceName: 'analytics'); // named

  final Iterable<Plugin> allPlugins = getIt.getAll<Plugin>();
// Returns: [CorePlugin, LoggingPlugin, AnalyticsPlugin]
//          ALL unnamed + ALL named registrations

Alternativa: findAll() para Descubrimiento Basado en Tipos

Mientras que getAll<T>() recupera instancias que has registrado explícitamente múltiples veces, findAll<T>() encuentra instancias por coincidencia de tipo - no se necesita configuración de registro múltiple. Mira Relacionado: Encontrar Instancias por Tipo abajo para cuándo usar cada enfoque.


Comportamiento de Scope

getAll<T>() proporciona tres opciones de control de scope:

Solo Scope Actual (Por Defecto)

Por defecto, busca solo en el scope actual:

dart
getIt.enableRegisteringMultipleInstancesOfOneType();

// Base scope
getIt.registerSingleton<Plugin>(CorePlugin());
getIt.registerSingleton<Plugin>(LoggingPlugin());

// Push new scope
getIt.pushNewScope(scopeName: 'feature');
getIt.registerSingleton<Plugin>(FeatureAPlugin());
getIt.registerSingleton<Plugin>(FeatureBPlugin());

// Current scope only (default)
final featurePlugins = getIt.getAll<Plugin>();
// Returns: [FeatureAPlugin, FeatureBPlugin]
Todos los Scopes

Para recuperar de todos los scopes, usa fromAllScopes: true:

dart
// All scopes
final allPlugins = getIt.getAll<Plugin>(fromAllScopes: true);
// Returns: [FeatureAPlugin, FeatureBPlugin, CorePlugin, LoggingPlugin]
Scope Nombrado Específico

Para buscar solo en un scope nombrado específico, usa onlyInScope:

dart
// Only search the base scope
final basePlugins = getIt.getAll<Plugin>(onlyInScope: 'baseScope');
// Returns: [CorePlugin, LoggingPlugin]

// Only search the 'feature' scope
final featurePlugins = getIt.getAll<Plugin>(onlyInScope: 'feature');
// Returns: [FeatureAPlugin, FeatureBPlugin]

Precedencia de Parámetros

Si se proporcionan tanto onlyInScope como fromAllScopes, onlyInScope tiene precedencia.

Mira la documentación de Scopes para más detalles sobre el comportamiento de scope.


Versión Async

Si tienes registros async, usa getAllAsync<T>() que espera a que todos los registros se completen:

dart
getIt.enableRegisteringMultipleInstancesOfOneType();

getIt.registerSingletonAsync<Plugin>(() async => await CorePlugin.create());
getIt
    .registerSingletonAsync<Plugin>(() async => await LoggingPlugin.create());

// Wait for all plugins to be ready
await getIt.allReady();

// Retrieve all async instances
final Iterable<Plugin> plugins = await getIt.getAllAsync<Plugin>();
Con control de scope

getAllAsync() soporta los mismos parámetros de scope que getAll():

dart
// All scopes
  final Iterable<Plugin> allPlugins = await getIt.getAllAsync<Plugin>(
    fromAllScopes: true,
  );

// Specific named scope
  final Iterable<Plugin> basePlugins = await getIt.getAllAsync<Plugin>(
    onlyInScope: 'baseScope',
  );

Patrones Comunes

Sistema de Plugins

dart
// Enable multiple registrations at app startup
void configureDependencies() {
  getIt.enableRegisteringMultipleInstancesOfOneType();

  // Core plugins (unnamed - always loaded)
  getIt.registerSingleton<AppPlugin>(CorePlugin());
  getIt.registerSingleton<AppPlugin>(LoggingPlugin());
  getIt.registerSingleton<AppPlugin>(AnalyticsPlugin());
}

// Feature module registers additional plugins
void enableShoppingFeature() {
  getIt.pushNewScope(scopeName: 'shopping');
  getIt.registerSingleton<AppPlugin>(ShoppingCartPlugin());
  getIt.registerSingleton<AppPlugin>(PaymentPlugin());
}

// App initialization
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // Initialize all plugins
    final allPlugins = getIt.getAll<AppPlugin>(fromAllScopes: true);
    print('allPlugins: $allPlugins');
    for (final plugin in allPlugins) {
      plugin.initialize();
    }

    return const MaterialApp(
      home: Scaffold(body: Center(child: Text('Plugins Initialized'))),
    );
  }
}
Event Handlers / Observers
dart
abstract class AppLifecycleObserver {
  void onAppStarted();
  void onAppPaused();
  void onAppResumed();
}

class AnalyticsObserver implements AppLifecycleObserver {
  @override
  void onAppStarted() {}
  @override
  void onAppPaused() {}
  @override
  void onAppResumed() {}
}

class LoggingObserver implements AppLifecycleObserver {
  @override
  void onAppStarted() {}
  @override
  void onAppPaused() {}
  @override
  void onAppResumed() {}
}

class CacheObserver implements AppLifecycleObserver {
  @override
  void onAppStarted() {}
  @override
  void onAppPaused() {}
  @override
  void onAppResumed() {}
}

class AppLifecycleManager {
  void notifyAppStarted() {
    final observers = getIt.getAll<AppLifecycleObserver>();
    print('observers: $observers');
    for (final observer in observers) {
      observer.onAppStarted();
    }
  }

  void notifyAppPaused() {
    final observers = getIt.getAll<AppLifecycleObserver>();
    print('observers: $observers');
    for (final observer in observers) {
      observer.onAppPaused();
    }
  }
}
Cadenas de Middleware / Validator
dart
abstract class RequestMiddleware {
  Future<bool> handle(Request request);
}

class AuthMiddleware implements RequestMiddleware {
  @override
  Future<bool> handle(Request request) async => true;
}

class RateLimitMiddleware implements RequestMiddleware {
  @override
  Future<bool> handle(Request request) async => true;
}

class LoggingMiddleware implements RequestMiddleware {
  @override
  Future<bool> handle(Request request) async => true;
}

class ApiClient {
  Future<Response> send(Request request) async {
    // Execute all middleware in registration order
    final middlewares = getIt.getAll<RequestMiddleware>();
    print('middlewares: $middlewares');
    for (final middleware in middlewares) {
      final canProceed = await middleware.handle(request);
      if (!canProceed) {
        return Response.forbidden();
      }
    }

    return _executeRequest(request);
  }

  Future<Response> _executeRequest(Request request) async {
    return Response(200, '');
  }
}
Combinando Registros Sin Nombre y Con Nombre
dart
abstract class ThemeProvider {
  ThemeData getTheme();
}

class LightThemeProvider implements ThemeProvider {
  @override
  ThemeData getTheme() => ThemeData.light();
}

class DarkThemeProvider implements ThemeProvider {
  @override
  ThemeData getTheme() => ThemeData.dark();
}

class HighContrastThemeProvider implements ThemeProvider {
  @override
  ThemeData getTheme() => ThemeData(brightness: Brightness.light);
}

class ThemePickerDialog extends StatelessWidget {
  const ThemePickerDialog({super.key});

  @override
  Widget build(BuildContext context) {
    final allThemes = getIt.getAll<ThemeProvider>();
    print('allThemes: $allThemes');
    // Returns: [LightThemeProvider, DarkThemeProvider, HighContrastThemeProvider]

    return ListView(
      children: allThemes.map((themeProvider) {
        return ListTile(
          title: Text(themeProvider.getTheme().toString()),
          onTap: () {
            // Apply theme
          },
        );
      }).toList(),
    );
  }
}

Mejores Prácticas

✅ Haz

  • Habilita al inicio de la app antes de cualquier registro
  • Registra la implementación más importante/por defecto primero (para get<T>())
  • Usa clases base abstractas como tipos de registro
  • Documenta dependencias de orden si el orden de middleware/observer importa
  • Usa registros con nombre para implementaciones de propósito especial que también necesitan acceso individual

❌️ No Hagas

  • No habilites a mitad de aplicación - hazlo durante la inicialización
  • No confíes en get<T>() para recuperar todas las implementaciones - usa getAll<T>()
  • No asumas orden de registro a menos que lo controles
  • No mezcles este patrón con allowReassignment - sirven para propósitos diferentes

Eligiendo el Enfoque Correcto

CaracterísticaRegistro con NombreRegistro Múltiple Sin Nombre
Habilitar requeridoNoSí (enableRegisteringMultipleInstancesOfOneType())
Patrón de accesoIndividual por nombre: get<T>(instanceName: 'name')Todas de una vez: getAll<T>() devuelve todas
Obtener unaget<T>(instanceName: 'name')get<T>() devuelve la primera
Caso de usoDiferentes configuraciones, feature flagsSistemas de plugins, observers, middleware
Independencia de módulosDebe conocer nombres de antemanoLos módulos pueden añadir implementaciones sin conocer las otras
Método de accesoNombres basados en StringRecuperación basada en tipo

Cuándo usar registro con nombre:

  • ✅ Diferentes configuraciones (endpoints de API dev/prod)
  • ✅ Feature flags (implementación antigua/nueva)
  • ✅ Conjunto conocido de instancias accedidas individualmente
  • ✅ Múltiples conexiones a base de datos

Cuándo usar registro múltiple sin nombre:

  • ✅ Arquitectura modular de plugins
  • ✅ Patrón de observer/event handler
  • ✅ Cadenas de middleware
  • ✅ Pipeline de validators/processors

Combinando ambos enfoques:

Los registros con nombre y sin nombre funcionan juntos sin problemas:

dart
// Enable multiple unnamed registrations
getIt.enableRegisteringMultipleInstancesOfOneType();

// Core plugins (unnamed)
getIt.registerSingleton<Plugin>(CorePlugin());
getIt.registerSingleton<Plugin>(LoggingPlugin());

// Special plugins (named for individual access + included in getAll())
getIt.registerSingleton<Plugin>(DebugPlugin(), instanceName: 'debug');

// Get all including named
final all =
    getIt.getAll<Plugin>(); // [CorePlugin, LoggingPlugin, DebugPlugin]

// Get specific named one
final debug = getIt<Plugin>(instanceName: 'debug');

Cómo Funciona

Esta sección explica los detalles internos de implementación. Entender esto es opcional para usar la característica.

Estructura de Datos

get_it mantiene dos listas separadas para cada tipo:

dart
class _ObjectRegistration {}

class _TypeRegistration<T> {
  final registrations = <_ObjectRegistration>[]; // Unnamed registrations
  final namedRegistrations =
      LinkedHashMap<String, _ObjectRegistration>(); // Named registrations
}

Cuando llamas:

  • getIt.registerSingleton<T>(instance) → añade a la lista registrations
  • getIt.registerSingleton<T>(instance, instanceName: 'name') → añade al mapa namedRegistrations

Por Qué get<T>() Devuelve Solo la Primera

El método get<T>() recupera instancias usando esta lógica:

dart
class _ObjectRegistration {}

Por eso get<T>() solo devuelve el primer registro sin nombre, no todos.

Por Qué getAll<T>() Devuelve Todas

El método getAll<T>() combina ambas listas:

dart
final typeRegistration = TypeRegistration();
final registrations = [
  ...typeRegistration.registrations, // ALL unnamed
  ...typeRegistration.namedRegistrations.values, // ALL named
];

Esto devuelve cada instancia registrada, sin importar si tiene nombre o no.

Preservación de Orden

  • Registros sin nombre: Preservados en orden de registro (List)
  • Registros con nombre: Preservados en orden de registro (LinkedHashMap)
  • Orden de getAll(): Primero sin nombre (en orden), luego con nombre (en orden)

Esto es importante para patrones de middleware/observer donde el orden de ejecución importa.


Referencia de API

Habilitar

MétodoDescripción
enableRegisteringMultipleInstancesOfOneType()Habilita registros múltiples sin nombre del mismo tipo

Recuperar

MétodoDescripción
get<T>()Devuelve primer registro sin nombre
getAll<T>({fromAllScopes})Devuelve todos los registros (sin nombre + con nombre)
getAllAsync<T>({fromAllScopes})Versión async, espera a registros async

Parámetros

ParámetroTipoPor DefectoDescripción
fromAllScopesboolfalseSi es true, busca en todos los scopes en lugar de solo el actual
onlyInScopeString?nullSi se proporciona, busca solo en el scope nombrado (tiene precedencia sobre fromAllScopes)

Relacionado: Encontrar Instancias por Tipo

Mientras que getAll<T>() recupera instancias que has registrado explícitamente múltiples veces, findAll<T>() ofrece un enfoque diferente: encontrar instancias por criterios de coincidencia de tipo.

Diferencias clave:

CaracterísticagetAll<T>()findAll<T>()
PropósitoRecuperar múltiples registros explícitosEncontrar instancias por coincidencia de tipo
RequiereenableRegisteringMultipleInstancesOfOneType()Sin configuración especial
CoincideTipo exacto T (con nombres opcionales)T y subtipos (configurable)
RendimientoBúsqueda O(1) en mapBúsqueda lineal O(n)
Caso de usoSistemas de plugins, registros múltiples conocidosEncontrar implementaciones, pruebas, introspección

Ejemplo de comparación:

dart
// Approach 1: Multiple registrations with getAll()
getIt.enableRegisteringMultipleInstancesOfOneType();
getIt.registerSingleton<ILogger>(FileLogger());
getIt.registerSingleton<ILogger>(ConsoleLogger());

final loggers1 = getIt.getAll<ILogger>();
// Returns: [FileLogger, ConsoleLogger]

// Approach 2: Different registration types with findAll()
getIt.registerSingleton<FileLogger>(FileLogger());
getIt.registerSingleton<ConsoleLogger>(ConsoleLogger());

final loggers2 = getIt.findAll<ILogger>();
// Returns: [FileLogger, ConsoleLogger] (matched by type)

Cuándo Usar Cada Uno

  • Usa getAll() cuando explícitamente quieras múltiples instancias del mismo tipo y las recuperarás todas juntas
  • Usa findAll() cuando quieras descubrir instancias por relación de tipo, especialmente para pruebas o depuración

Mira la documentación de findAll() para detalles comprehensivos sobre coincidencia de tipos, control de scope y opciones avanzadas de filtrado.


Ver También

Publicado bajo la Licencia MIT.