Skip to content

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

TipoCuándo se CreaCuántas InstanciasTiempo de VidaMejor Para
SingletonInmediatamenteUnaPermanenteRápido de crear, necesario al inicio
LazySingletonPrimer accesoUnaPermanenteCostoso de crear, no siempre necesario
FactoryCada get()MuchasPor solicitudObjetos temporales, nuevo estado cada vez
Cached FactoryPrimer acceso + después del GCReutilizado mientras está en memoriaHasta que es recolectado por el GCOptimización de rendimiento

Singleton

dart
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 registrar
  • instanceName - Nombre opcional para registrar múltiples instancias del mismo tipo
  • signalsReady - 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:

dart
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

dart
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 instancia
  • instanceName - Nombre opcional para registrar múltiples instancias del mismo tipo
  • dispose - Función de limpieza opcional llamada al desregistrar o resetear
  • onCreated - Callback opcional invocado después de que la instancia es creada
  • useWeakReference - Si es true, usa referencia débil (permite recolección de basura si no se usa)

Ejemplo:

dart
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 call

Cuá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

dart
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 instancias
  • instanceName - Nombre opcional para registrar múltiples factories del mismo tipo

Ejemplo:

dart
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:

dart
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:

dart
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:

dart
// 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).

dart
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:

  1. Primera llamada: Crea nueva instancia (como factory)
  2. Llamadas subsecuentes: Devuelve instancia cacheada si todavía está en memoria (como singleton)
  3. Si es recolectada por el GC (sin referencias mantenidas por tu app): Crea nueva instancia otra vez (como factory)
  4. Para versiones con parámetros: También verifica si los parámetros coinciden antes de reutilizar

Ejemplo:

dart
// 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 created

Con parámetros:

dart
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:

TipoCosto de CreaciónMemoriaReutilización
FactoryCada llamadaBaja (GC inmediato)Nunca
Cached FactoryPrimera llamada + después del GCMedia (ref. débil)Mientras está en memoria
Lazy SingletonSolo primera llamadaAlta (permanente)Siempre

Ejemplo de comparación:

dart
// 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>() vs getAll<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:

dart
bool isRegistered<T extends Object>({
  Object? instance,
  String? instanceName,
  Type? type,
});

Ejemplo:

dart
// 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:

dart
FutureOr unregister<T extends Object>({
  Object? instance,
  String? instanceName,
  FutureOr Function(T)? disposingFunction,
  bool ignoreReferenceCount = false,
});

Ejemplo:

dart
// 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:

dart
FutureOr resetLazySingleton<T extends Object>({
  T? instance,
  String? instanceName,
  FutureOr Function(T)? disposingFunction,
});

Ejemplo:

dart
// 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 created

Cuá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):

dart
Future<void> reset({bool dispose = true});

Ejemplo:

dart
// 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 (tearDown o tearDownAll)
  • 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:

dart
  getIt.allowReassignment = true;

// Now you can re-register
  getIt.registerSingleton<Logger>(ConsoleLogger());
  getIt.registerSingleton<Logger>(FileLogger()); // Overwrites previous

Usa 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:

dart
  getIt.skipDoubleRegistration = true;

// If already registered, this does nothing instead of throwing
  getIt.registerSingleton<Logger>(Logger());

Publicado bajo la Licencia MIT.