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.
// 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.
// 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
// 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:
// 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:
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:
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:
// 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:
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>():
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 registrationsAlternativa: 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:
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:
// 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:
// 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:
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():
// 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
// 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
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
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
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 - usagetAll<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ística | Registro con Nombre | Registro Múltiple Sin Nombre |
|---|---|---|
| Habilitar requerido | No | Sí (enableRegisteringMultipleInstancesOfOneType()) |
| Patrón de acceso | Individual por nombre: get<T>(instanceName: 'name') | Todas de una vez: getAll<T>() devuelve todas |
| Obtener una | get<T>(instanceName: 'name') | get<T>() devuelve la primera |
| Caso de uso | Diferentes configuraciones, feature flags | Sistemas de plugins, observers, middleware |
| Independencia de módulos | Debe conocer nombres de antemano | Los módulos pueden añadir implementaciones sin conocer las otras |
| Método de acceso | Nombres basados en String | Recuperació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:
// 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:
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 listaregistrationsgetIt.registerSingleton<T>(instance, instanceName: 'name')→ añade al mapanamedRegistrations
Por Qué get<T>() Devuelve Solo la Primera
El método get<T>() recupera instancias usando esta lógica:
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:
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étodo | Descripción |
|---|---|
enableRegisteringMultipleInstancesOfOneType() | Habilita registros múltiples sin nombre del mismo tipo |
Recuperar
| Método | Descripció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ámetro | Tipo | Por Defecto | Descripción |
|---|---|---|---|
fromAllScopes | bool | false | Si es true, busca en todos los scopes en lugar de solo el actual |
onlyInScope | String? | null | Si 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ística | getAll<T>() | findAll<T>() |
|---|---|---|
| Propósito | Recuperar múltiples registros explícitos | Encontrar instancias por coincidencia de tipo |
| Requiere | enableRegisteringMultipleInstancesOfOneType() | Sin configuración especial |
| Coincide | Tipo exacto T (con nombres opcionales) | T y subtipos (configurable) |
| Rendimiento | Búsqueda O(1) en map | Búsqueda lineal O(n) |
| Caso de uso | Sistemas de plugins, registros múltiples conocidos | Encontrar implementaciones, pruebas, introspección |
Ejemplo de comparación:
// 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
- Scopes - Gestión jerárquica del ciclo de vida y registros específicos de scope
- Registro de Objetos - Diferentes tipos de registro (factories, singletons, etc.)
- Objetos Asíncronos - Usando
getAllAsync()con registros async - Avanzado - findAll() - Descubrimiento de instancias basado en tipo