Registro de Objetos
get_it ofrece diferentes tipos de registro que controlan cuándo se crean los objetos y cuánto tiempo viven. Elige el tipo correcto según tus necesidades.
Referencia Rápida
| Tipo | Cuándo se Crea | Cuántas Instancias | Tiempo de Vida | Mejor Para |
|---|---|---|---|---|
| Singleton | Inmediatamente | Una | Permanente | Rápido de crear, necesario al inicio |
| LazySingleton | Primer acceso | Una | Permanente | Costoso de crear, no siempre necesario |
| Factory | Cada get() | Muchas | Por solicitud | Objetos temporales, nuevo estado cada vez |
| Cached Factory | Primer acceso + después del GC | Reutilizado mientras está en memoria | Hasta que es recolectado por el GC | Optimización de rendimiento |
Singleton
T registerSingleton<T extends Object>(
T instance, {
String? instanceName,
bool? signalsReady,
DisposingFunc<T>? dispose,
});Pasas una instancia de T que siempre será devuelta en las llamadas a get<T>(). La instancia se crea inmediatamente cuando la registras.
Parámetros:
instance- La instancia a registrarinstanceName- Nombre opcional para registrar múltiples instancias del mismo tiposignalsReady- Si es true, esta instancia debe señalizar cuándo está lista (usado con inicialización asíncrona)dispose- Función de limpieza opcional llamada al desregistrar o resetear
Ejemplo:
void configureDependencies() {
// Simple registration
getIt.registerSingleton<Logger>(Logger());
// With disposal
getIt.registerSingleton<Database>(
Database(),
dispose: (db) => db.close(),
);
}Cuándo usar Singleton:
- ✅ Servicio necesario al inicio de la app
- ✅ Rápido de crear (sin inicialización costosa)
- ❌️ Evita para inicialización lenta (usa LazySingleton en su lugar)
LazySingleton
void registerLazySingleton<T>(
FactoryFunc<T> factoryFunc, {
String? instanceName,
DisposingFunc<T>? dispose,
void Function(T instance)? onCreated,
bool useWeakReference = false,
}) {}Pasas una función factory que devuelve una instancia de T. La función solo se llama en el primer acceso a get<T>(). Después de eso, siempre se devuelve la misma instancia.
Parámetros:
factoryFunc- Función que crea la instanciainstanceName- Nombre opcional para registrar múltiples instancias del mismo tipodispose- Función de limpieza opcional llamada al desregistrar o resetearonCreated- Callback opcional invocado después de que la instancia es creadauseWeakReference- Si es true, usa referencia débil (permite recolección de basura si no se usa)
Ejemplo:
void configureDependencies() {
// Simple registration
getIt.registerLazySingleton<ApiClient>(() => ApiClient());
// With disposal and onCreated callback
getIt.registerLazySingleton<Database>(
() => Database(),
dispose: (db) => db.close(),
onCreated: (db) => print('Database initialized'),
);
}
// First access - factory function runs NOW
final api = getIt<ApiClient>(); // ApiClient() constructor called
// Subsequent calls - returns existing instance
final sameApi = getIt<ApiClient>(); // Same instance, no constructor callCuándo usar LazySingleton:
- ✅ Servicios costosos de crear (base de datos, cliente HTTP, etc.)
- ✅ Servicios no siempre necesarios para cada usuario
- ✅ Cuando necesites retrasar la inicialización
Tipos Concretos vs Interfaces
Puedes registrar clases concretas o interfaces abstractas. Registra clases concretas directamente a menos que esperes múltiples implementaciones (ej., producción vs prueba, diferentes proveedores). Esto mantiene tu código más simple y la navegación del IDE más fácil.
Factory
void registerFactory<T>(
FactoryFunc<T> factoryFunc, {
String? instanceName,
}) {}Pasas una función factory que devuelve una NUEVA instancia de T cada vez que llamas get<T>(). A diferencia de los singletons, obtienes un objeto diferente cada vez.
Parámetros:
factoryFunc- Función que crea nuevas instanciasinstanceName- Nombre opcional para registrar múltiples factories del mismo tipo
Ejemplo:
void configureDependencies() {
getIt.registerFactory<ShoppingCart>(() => ShoppingCart());
}Cuándo usar Factory:
- ✅ Objetos temporales (diálogos, formularios, contenedores de datos temporales)
- ✅ Objetos que necesitan estado fresco cada vez
- ✅ Objetos con ciclo de vida corto
- ❌️ Evita para objetos costosos de crear usados frecuentemente (considera Cached Factory)
Pasando Parámetros a Factories
En algunos casos, necesitas pasar valores a factories al llamar get(). get_it soporta hasta dos parámetros:
void registerFactoryParam<T, P1, P2>(
FactoryFuncParam<T, P1, P2> factoryFunc, {
String? instanceName,
}) {}
void registerFactoryParamAsync<T, P1, P2>(
FactoryFuncParamAsync<T, P1, P2> factoryFunc, {
String? instanceName,
}) {}Ejemplo con dos parámetros:
const userId = 'user-123';
const age = 25;
// Register factory accepting two parameters
getIt.registerFactoryParam<UserViewModel, String, int>(
(userId, age) => UserViewModel(userId, age: age),
);
// Access with parameters
final vm = getIt<UserViewModel>(param1: userId, param2: age);Ejemplo con un parámetro:
Si solo necesitas un parámetro, pasa void como el segundo tipo:
// Register with one parameter (second type is void)
getIt.registerFactoryParam<ReportGenerator, String, void>(
(reportType, _) => ReportGenerator(reportType),
);
// Access with one parameter
final report = getIt<ReportGenerator>(param1: 'sales');¿Por qué dos parámetros?
Dos parámetros cubren escenarios comunes como widgets de Flutter que necesitan tanto BuildContext como un objeto de datos, o servicios que necesitan tanto configuración como valores en tiempo de ejecución.
Seguridad de Tipos
Los parámetros se pasan como dynamic pero se verifican en tiempo de ejecución para coincidir con los tipos registrados (P1, P2). Las discordancias de tipo lanzarán un error.
Cached Factories
Las cached factories son una optimización de rendimiento que se sitúa entre las factories regulares y los singletons. Crean una nueva instancia en la primera llamada pero la cachean con una referencia débil, devolviendo la instancia cacheada mientras todavía esté en memoria (lo que significa que alguna parte de tu app todavía mantiene una referencia a ella).
void registerCachedFactory<T>(
FactoryFunc<T> factoryFunc, {
String? instanceName,
});
void registerCachedFactoryParam<T, P1, P2>(
FactoryFuncParam<T, P1, P2> factoryFunc, {
String? instanceName,
});
void registerCachedFactoryAsync<T>(
FactoryFuncAsync<T> factoryFunc, {
String? instanceName,
});
void registerCachedFactoryParamAsync<T, P1, P2>(
FactoryFuncParamAsync<T, P1, P2> factoryFunc, {
String? instanceName,
});Cómo funciona:
- Primera llamada: Crea nueva instancia (como factory)
- Llamadas subsecuentes: Devuelve instancia cacheada si todavía está en memoria (como singleton)
- Si es recolectada por el GC (sin referencias mantenidas por tu app): Crea nueva instancia otra vez (como factory)
- Para versiones con parámetros: También verifica si los parámetros coinciden antes de reutilizar
Ejemplo:
// Register a cached factory
getIt.registerCachedFactory<HeavyParser>(() => HeavyParser());
// First call - creates instance
final parser1 = getIt<HeavyParser>();
print('parser1: $parser1'); // New instance created
// Second call - reuses if not garbage collected
final parser2 = getIt<HeavyParser>();
print('parser2: $parser2'); // Same instance (if still in memory)
// After garbage collection (no references held)
final parser3 = getIt<HeavyParser>();
print('parser3: $parser3'); // New instance createdCon parámetros:
getIt.registerCachedFactoryParam<ImageProcessor, int, int>(
(width, height) => ImageProcessor(width, height),
);
// Creates new instance
final processor1 = getIt<ImageProcessor>(param1: 1920, param2: 1080);
print('processor1: $processor1');
// Reuses same instance (same parameters)
final processor2 = getIt<ImageProcessor>(param1: 1920, param2: 1080);
print('processor2: $processor2');
// Creates NEW instance (different parameters)
final processor3 = getIt<ImageProcessor>(param1: 3840, param2: 2160);
print('processor3: $processor3');Cuándo usar cached factories:
✅ Buenos casos de uso:
- Objetos pesados recreados frecuentemente: Parsers, formateadores, calculadoras
- Escenarios sensibles a la memoria: Quieres limpieza automática pero prefieres reutilización
- Objetos con inicialización costosa: Conexiones a base de datos, lectores de archivos
- Objetos de tiempo de vida corto a medio: Activos por un tiempo pero no para siempre
❌️ No uses cuando:
- El objeto siempre debería ser nuevo (usa factory regular)
- El objeto debería vivir para siempre (usa singleton/lazy singleton)
- El objeto mantiene estado crítico que no debe reutilizarse
Características de rendimiento:
| Tipo | Costo de Creación | Memoria | Reutilización |
|---|---|---|---|
| Factory | Cada llamada | Baja (GC inmediato) | Nunca |
| Cached Factory | Primera llamada + después del GC | Media (ref. débil) | Mientras está en memoria |
| Lazy Singleton | Solo primera llamada | Alta (permanente) | Siempre |
Ejemplo de comparación:
// Factory - always new, immediate cleanup
getIt.registerFactory<JsonParser>(() => JsonParser());
final p1 = getIt<JsonParser>();
print('p1: $p1'); // Creates instance 1
final p2 = getIt<JsonParser>();
print('p2: $p2'); // Creates instance 2 (different)
// Cached Factory - reuses if possible
getIt.registerCachedFactory<JsonParser>(() => JsonParser());
final p3 = getIt<JsonParser>();
print('p3: $p3'); // Creates instance 3
final p4 = getIt<JsonParser>();
print('p4: $p4'); // Returns instance 3 (if not GC'd)
// Lazy Singleton - reuses forever
getIt.registerLazySingleton<JsonParser>(() => JsonParser());
final p5 = getIt<JsonParser>();
print('p5: $p5'); // Creates instance 4
final p6 = getIt<JsonParser>();
print('p6: $p6'); // Returns instance 4 (always)Gestión de Memoria
Las cached factories usan referencias débiles, lo que significa que la instancia cacheada puede ser recolectada por el garbage collector cuando ninguna otra parte de tu código mantiene una referencia a ella. Esto proporciona gestión automática de memoria mientras te beneficias de la reutilización.
Registrando Múltiples Implementaciones
get_it soporta múltiples formas de registrar más de una instancia del mismo tipo. Esto es útil para sistemas de plugins, manejadores de eventos y arquitecturas modulares donde necesitas recuperar todas las implementaciones de un tipo particular.
Aprende Más
Mira el capítulo Registros Múltiples para documentación comprehensiva que cubre:
- Diferentes enfoques para registrar múltiples instancias
- Por qué se requiere habilitación explícita para registros sin nombre
- Cómo se comportan
get<T>()vsgetAll<T>()de forma diferente - Registros con nombre vs sin nombre
- Comportamiento de scope con
fromAllScopes - Patrones del mundo real (plugins, observadores, middleware)
Gestionando Registros
Verificando si un Tipo está Registrado
Puedes probar si un tipo o instancia ya está registrada:
bool isRegistered<T extends Object>({
Object? instance,
String? instanceName,
Type? type,
});Ejemplo:
// Check if type is registered
if (getIt.isRegistered<ApiClient>()) {
print('ApiClient is already registered');
}
// Check by instance name
if (getIt.isRegistered<Database>(instanceName: 'test-db')) {
print('Test database is registered');
}
// Check if specific instance is registered
final myLogger = Logger();
if (getIt.isRegistered<Logger>(instance: myLogger)) {
print('This specific logger instance is registered');
}Desregistrando Servicios
Puedes remover un tipo registrado de get_it, opcionalmente llamando a una función de disposal:
FutureOr unregister<T extends Object>({
Object? instance,
String? instanceName,
FutureOr Function(T)? disposingFunction,
bool ignoreReferenceCount = false,
});Ejemplo:
// Unregister by type with cleanup
getIt.unregister<Database>(
disposingFunction: (db) => db.close(),
);
// Unregister by instance name
getIt.unregister<ApiClient>(instanceName: 'legacy-api');
// Unregister specific instance
final myService = getIt<MyService>();
print('myService: $myService');
getIt.unregister<MyService>(
instance: myService,
disposingFunction: (s) => s.dispose(),
);TIP
La función de disposal sobrescribe cualquier función de disposal que hayas proporcionado durante el registro.
Reseteando Lazy Singletons
A veces quieres resetear un lazy singleton (forzar recreación en el siguiente acceso) sin desregistrarlo:
FutureOr resetLazySingleton<T extends Object>({
T? instance,
String? instanceName,
FutureOr Function(T)? disposingFunction,
});Ejemplo:
// Reset so it recreates on next get()
getIt.resetLazySingleton<UserCache>();
// Next access will call the factory function again
final cache = getIt<UserCache>();
print('cache: $cache'); // New instance createdCuándo usar:
- ✅ Refrescar datos cacheados (después de login/logout)
- ✅ Testing - resetear estado entre pruebas
- ✅ Desarrollo - recargar configuración
Resetear Todos los Lazy Singletons
Si necesitas resetear todos los lazy singletons a la vez (en lugar de uno a la vez), usa resetLazySingletons() que soporta control de scope y operaciones en lote. Mira la documentación de resetLazySingletons() para detalles.
Reseteando Todos los Registros
Limpia todos los tipos registrados (útil para pruebas o cierre de la app):
Future<void> reset({bool dispose = true});Ejemplo:
// Reset everything and call disposal functions
await getIt.reset();
// Reset without calling disposals
await getIt.reset(dispose: false);Importante
- Los registros se limpian en orden inverso (último registrado, primero dispuesto)
- Esto es async - siempre haz
await - Las funciones de disposal registradas durante la configuración serán llamadas (a menos que
dispose: false)
Casos de uso:
- Entre pruebas unitarias (
tearDownotearDownAll) - Antes del cierre de la app
- Cambiando entornos completamente
Sobrescribiendo Registros
Por defecto, get_it previene registrar el mismo tipo dos veces (atrapa bugs). Para permitir sobrescritura:
getIt.allowReassignment = true;
// Now you can re-register
getIt.registerSingleton<Logger>(ConsoleLogger());
getIt.registerSingleton<Logger>(FileLogger()); // Overwrites previousUsa con Moderación
Permitir reasignación hace que los bugs sean más difíciles de atrapar. Prefiere usar scopes en su lugar para sobrescrituras temporales (especialmente en pruebas).
Saltar Registro Doble (Solo Testing)
En pruebas, ignora silenciosamente el registro doble en lugar de lanzar un error:
getIt.skipDoubleRegistration = true;
// If already registered, this does nothing instead of throwing
getIt.registerSingleton<Logger>(Logger());