Avanzado
Implementando la Interfaz Disposable
En lugar de pasar una función de disposal en el registro o al empujar un Scope, desde V7.0 el método onDispose() de tu objeto será llamado si el objeto que registras implementa la interfaz Disposable:
abstract class Disposable {
FutureOr onDispose();
}Encontrar Todas las Instancias por Tipo: findAll<T>()
Encuentra todas las instancias registradas que coincidan con un tipo dado con opciones poderosas de filtrado y coincidencia.
// ignore_for_file: missing_function_body, unused_element
List<T> findAll<T>({
bool includeSubtypes = true,
bool inAllScopes = false,
String? onlyInScope,
bool includeMatchedByRegistrationType = true,
bool includeMatchedByInstance = true,
bool instantiateLazySingletons = false,
bool callFactories = false,
}) =>
[];Nota de Rendimiento
A diferencia de las búsquedas O(1) basadas en Map de get_it, findAll() realiza una búsqueda lineal O(n) a través de todos los registros. Usa con moderación en código crítico de rendimiento. El rendimiento puede mejorarse limitando la búsqueda a un solo scope usando onlyInScope.
Parámetros:
Coincidencia de Tipo:
includeSubtypes- Si es true (por defecto), coincide con T y todos los subtipos; si es false, coincide solo con el tipo exacto T
Control de Scope:
inAllScopes- Si es true, busca en todos los scopes (por defecto: false, solo scope actual)onlyInScope- Busca solo en el scope nombrado (tiene precedencia sobreinAllScopes)
Estrategia de Coincidencia:
includeMatchedByRegistrationType- Coincidir por tipo registrado (por defecto: true)includeMatchedByInstance- Coincidir por tipo de instancia real (por defecto: true)
Efectos Secundarios:
instantiateLazySingletons- Instanciar lazy singletons que coincidan (por defecto: false)callFactories- Llamar factories que coincidan para incluir sus instancias (por defecto: false)
Ejemplo - Coincidencia básica de tipos:
abstract class IOutput {
void write(String message);
}
class FileOutput implements IOutput {
@override
void write(String message) => File('log.txt').writeAsStringSync(message);
}
class ConsoleOutput implements IOutput {
@override
void write(String message) => print(message);
}
// Register different implementation types
void main() {
getIt.registerSingleton<FileOutput>(FileOutput());
getIt.registerLazySingleton<ConsoleOutput>(() => ConsoleOutput());
// Find by interface (registration type matching)
final outputs = getIt.findAll<IOutput>();
print('outputs: $outputs');
// Returns: [FileOutput] only (ConsoleOutput not instantiated yet)
}Ejemplo - Incluir lazy singletons
void main() async {
// Instantiate lazy singletons that match
final all = getIt.findAll<IOutput>(
instantiateLazySingletons: true,
);
// Returns: [FileOutput, ConsoleOutput]
// ConsoleOutput is now created and cached
print('Found: $all');
}Ejemplo - Incluir factories
void main() async {
getIt.registerFactory<IOutput>(() => RemoteOutput('https://api.example.com'));
// Include factories by calling them
final withFactories = getIt.findAll<IOutput>(
instantiateLazySingletons: true,
callFactories: true,
);
// Returns: [FileOutput, ConsoleOutput, RemoteOutput]
// Each factory call creates a new instance
}Ejemplo - Coincidencia exacta de tipo
class BaseLogger {}
class FileLogger extends BaseLogger {}
class ConsoleLogger extends BaseLogger {}
void main() {
getIt.registerSingleton<BaseLogger>(FileLogger());
getIt.registerSingleton<BaseLogger>(ConsoleLogger());
// Find subtypes (default)
final allLoggers = getIt.findAll<BaseLogger>();
print('allLoggers: $allLoggers');
// Returns: [FileLogger, ConsoleLogger]
// Find exact type only
final exactBase = getIt.findAll<BaseLogger>(
includeSubtypes: false,
);
// Returns: [] (no exact BaseLogger instances, only subtypes)
}Ejemplo - Tipo de Instancia vs Tipo de Registro
void main() async {
// Register as FileOutput but it implements IOutput
getIt.registerSingleton<FileOutput>(FileOutput('/path/to/file.txt'));
// Match by registration type
final byRegistration = getIt.findAll<IOutput>(
includeMatchedByRegistrationType: true,
includeMatchedByInstance: false,
);
// Returns: [] (registered as FileOutput, not IOutput)
// Match by instance type
final byInstance = getIt.findAll<IOutput>(
includeMatchedByRegistrationType: false,
includeMatchedByInstance: true,
);
// Returns: [FileOutput] (instance implements IOutput)
}Ejemplo - Control de scope
void main() {
// Base scope
getIt.registerSingleton<IOutput>(FileOutput('/tmp/output.txt'));
// Push scope
getIt.pushNewScope(scopeName: 'session');
getIt.registerSingleton<IOutput>(ConsoleOutput());
// Current scope only (default)
final current = getIt.findAll<IOutput>();
print('current: $current');
// Returns: [ConsoleOutput]
// All scopes
final all = getIt.findAll<IOutput>(inAllScopes: true);
print('all: $all');
// Returns: [ConsoleOutput, FileOutput]
// Specific scope
final base = getIt.findAll<IOutput>(onlyInScope: 'baseScope');
print('base: $base');
// Returns: [FileOutput]
}Casos de uso:
- Encontrar todas las implementaciones de una interfaz de plugin
- Recopilar todos los validators/processors registrados
- Visualización de grafo de dependencias en tiempo de ejecución
- Pruebas: verificar que todos los tipos esperados están registrados
- Herramientas de migración: encontrar instancias de tipos deprecated
Reglas de validación:
includeSubtypes=falserequiereincludeMatchedByInstance=falseinstantiateLazySingletons=truerequiereincludeMatchedByRegistrationType=truecallFactories=truerequiereincludeMatchedByRegistrationType=true
Lanza:
StateErrorsionlyInScopeno existeArgumentErrorsi se violan las reglas de validación
Conteo de Referencias
El conteo de referencias ayuda a gestionar el ciclo de vida de singletons cuando múltiples consumidores podrían necesitar la misma instancia, especialmente útil para escenarios recursivos como navegación.
El Problema
Imagina una página de detalle que puede ser empujada recursivamente (ej., ver items relacionados, navegar a través de una jerarquía):
Home → DetailPage(item1) → DetailPage(item2) → DetailPage(item3)Sin conteo de referencias:
- Primera DetailPage registra
DetailService - Segunda DetailPage intenta registrar → Error o debe saltarse el registro
- Primera DetailPage hace pop, dispone el servicio → Rompe las páginas restantes
La Solución: registerSingletonIfAbsent y releaseInstance
T registerSingletonIfAbsent<T>(
T Function() factoryFunc, {
String? instanceName,
DisposingFunc<T>? dispose,
}) =>
factoryFunc();
void releaseInstance(Object instance) {}Cómo funciona:
- Primera llamada: Crea instancia, registra, establece contador de referencia a 1
- Llamadas subsecuentes: Devuelve instancia existente, incrementa contador
releaseInstance: Decrementa contador- Cuando el contador llega a 0: Desregistra y dispone
Ejemplo de Navegación Recursiva
class DetailService extends ChangeNotifier {
final String itemId;
String? data;
bool isLoading = false;
DetailService(this.itemId) {
// Trigger async loading in constructor (fire and forget)
_loadData();
}
Future<void> _loadData() async {
if (data != null) return; // Already loaded
isLoading = true;
notifyListeners();
print('Loading data for $itemId from backend...');
// Simulate backend call
await Future.delayed(Duration(seconds: 1));
data = 'Data for $itemId';
isLoading = false;
notifyListeners();
}
}
class DetailPage extends WatchingWidget {
final String itemId;
const DetailPage(this.itemId);
@override
Widget build(BuildContext context) {
// Register once when widget is created, dispose when widget is disposed
callOnce(
(context) {
// Register or get existing - increments reference count
getIt.registerSingletonIfAbsent<DetailService>(
() => DetailService(itemId),
instanceName: itemId,
);
},
dispose: () {
// Decrements reference count when widget disposes
getIt.releaseInstance(getIt<DetailService>(instanceName: itemId));
},
);
// Watch the service - rebuilds when notifyListeners() called
final service = watchIt<DetailService>(instanceName: itemId);
return Scaffold(
appBar: AppBar(title: Text('Detail $itemId')),
body: service.isLoading
? const Center(child: CircularProgressIndicator())
: Column(
children: [
Text(service.data ?? 'No data'),
ElevatedButton(
onPressed: () {
// Can push same page recursively
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => DetailPage('related-$itemId'),
),
);
},
child: const Text('View Related'),
),
],
),
);
}
}Flujo:
Push DetailPage(item1) → Crear servicio, cargar datos, refCount = 1
Push DetailPage(item2) → Crear servicio, cargar datos, refCount = 1
Push DetailPage(item1) → Obtener existente (¡NO recarga!), refCount = 2
Pop DetailPage(item1) → Release, refCount = 1 (servicio permanece)
Pop DetailPage(item2) → Release, refCount = 0 (servicio dispuesto)
Pop DetailPage(item1) → Release, refCount = 0 (servicio dispuesto)Beneficios:
- ✅ Servicio creado síncronamente (no se necesita factory async)
- ✅ Carga async disparada en constructor
- ✅ Sin carga duplicada para el mismo item (verificado antes de cargar)
- ✅ Gestión automática de memoria vía conteo de referencias
- ✅ Actualizaciones reactivas de UI vía `watch_it` (reconstruye en cambios de estado)
- ✅ ChangeNotifier automáticamente dispuesto cuando refCount llega a 0
- ✅ Cada itemId únicamente identificado vía `instanceName`
Integración Clave: Este ejemplo demuestra cómo get_it (conteo de referencias) y watch_it (UI reactiva) funcionan juntos sin problemas para patrones de navegación complejos.
Liberación Forzada: ignoreReferenceCount
En casos raros, podrías necesitar forzar el desregistro sin importar el conteo de referencias:
// Force unregister even if refCount > 0
getIt.unregister<MyService>(ignoreReferenceCount: true);Usa con Precaución
Solo usa ignoreReferenceCount: true cuando estés seguro de que ningún otro código está usando la instancia. Esto puede causar crashes si otras partes de tu app aún mantienen referencias.
Cuándo Usar Conteo de Referencias
✅ Buenos casos de uso:
- Navegación recursiva (misma página empujada múltiples veces)
- Servicios necesarios por múltiples características activas simultáneamente
- Estructuras de componentes jerárquicas complejas
❌️ No uses cuando:
- Singleton simple que vive durante el tiempo de vida de la app (usa
registerSingletonregular) - Relación uno-a-uno widget-servicio (usa scopes)
- Pruebas (usa scopes para sombrear en su lugar)
Mejores Prácticas
- Siempre empareja registro con liberación: Cada
registerSingletonIfAbsentdebe tener unreleaseInstancecorrespondiente - Almacena referencia de instancia: Mantén la instancia devuelta para que puedas liberar la correcta
- Libera en dispose/cleanup: Ata la liberación al ciclo de vida del widget/componente
- Documenta recursos compartidos: Deja claro cuándo un servicio usa conteo de referencias
Métodos Utilitarios
Recuperación Segura: maybeGet<T>()
Devuelve null en lugar de lanzar una excepción si el tipo no está registrado. Útil para dependencias opcionales y feature flags.
/// like [get] but returns null if the instance is not found
T? maybeGet<T extends Object>({
dynamic param1,
dynamic param2,
String? instanceName,
Type? type,
});Ejemplo:
// Feature flag scenario
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context) {
final logger = getIt.maybeGet<Logger>();
print('logger: $logger');
logger?.log('Building MyWidget'); // Safe even if Logger not registered
return const Text('Hello');
}
}
// Graceful degradation
Widget getUIForPremiumStatus() {
final premiumFeature = getIt.maybeGet<PremiumFeature>();
print('premiumFeature: $premiumFeature');
if (premiumFeature != null) {
return PremiumUI(feature: premiumFeature);
} else {
return const BasicUI(); // Fallback when premium not available
}
}
void main() {
final analyticsService = getIt.maybeGet<AnalyticsService>();
print('analyticsService: $analyticsService');
if (analyticsService != null) {
analyticsService.trackEvent('user_action');
}
final ui = getUIForPremiumStatus();
print('UI: $ui');
}Cuándo usar:
- ✅ Características opcionales que pueden o no estar registradas
- ✅ Feature flags (servicio registrado solo cuando la característica está habilitada)
- ✅ Servicios específicos de plataforma (podrían no existir en todas las plataformas)
- ✅ Escenarios de degradación elegante
No uses cuando:
- ❌️ La dependencia es requerida - usa
get<T>()para fallar rápido - ❌️ El registro faltante indica un bug - la excepción es útil
Renombrado de Instancia: changeTypeInstanceName()
Renombra una instancia registrada sin desregistrar y re-registrar (evita disparar funciones de disposal).
// ignore_for_file: missing_function_body, unused_element
void changeTypeInstanceName<T>({
String? instanceName,
required String newInstanceName,
T? instance,
}) {}Ejemplo:
class UserModel extends ChangeNotifier {
String username;
String email;
UserModel(this.username, this.email);
Future<void> updateUsername(String newUsername) async {
// Update on backend via API (stubbed)
final oldUsername = username;
username = newUsername;
// Rename the instance in GetIt to match new username
getIt.changeTypeInstanceName<UserModel>(
instanceName: oldUsername,
newInstanceName: newUsername,
);
notifyListeners();
}
}Casos de uso:
- Actualizaciones de perfil de usuario donde el nombre de usuario es el identificador de instancia
- Nombres de entidades dinámicas que pueden cambiar en tiempo de ejecución
- Evitar efectos secundarios de disposal del ciclo unregister/register
- Mantener estado de instancia mientras se actualiza su identificador
Evita Dispose
A diferencia de unregister() + register(), esto no dispara funciones de disposal, preservando el estado de la instancia.
Introspección de Lazy Singleton: checkLazySingletonInstanceExists()
Verifica si un lazy singleton ha sido instanciado aún (sin disparar su creación).
// ignore_for_file: missing_function_body, unused_element
import 'package:get_it/get_it.dart';
bool checkLazySingletonInstanceExists<T>({
String? instanceName,
}) =>
false;Ejemplo:
// Register lazy singleton
getIt.registerLazySingleton<HeavyService>(() => HeavyService());
// Check if it's been created yet
if (getIt.checkLazySingletonInstanceExists<HeavyService>()) {
print('HeavyService already created');
} else {
print('HeavyService not created yet - will be lazy loaded');
}
// Access triggers creation
final service = getIt<HeavyService>();
print('service: $service');
// Now it exists
assert(getIt.checkLazySingletonInstanceExists<HeavyService>() == true);Casos de uso:
- Monitoreo de rendimiento (rastrear qué servicios han sido inicializados)
- Inicialización condicional (pre-calentar servicios si no se crearon)
- Probar comportamiento de carga lazy
- Depurar problemas de orden de inicialización
Ejemplo - Pre-calentamiento:
void preWarmCriticalServices() {
// Only initialize if not already created
if (!getIt.checkLazySingletonInstanceExists<DatabaseService>()) {
getIt<DatabaseService>(); // Trigger creation
}
if (!getIt.checkLazySingletonInstanceExists<CacheService>()) {
getIt<CacheService>(); // Trigger creation
}
}Resetear Todos los Lazy Singletons: resetLazySingletons()
Resetea todos los lazy singletons instanciados de una vez. Esto limpia sus instancias para que sean recreadas en el siguiente acceso.
Future<void> resetLazySingletons({
bool dispose = true,
bool inAllScopes = false,
String? onlyInScope,
}) async {}Parámetros:
dispose- Si es true (por defecto), llama funciones de disposal antes de resetearinAllScopes- Si es true, resetea lazy singletons a través de todos los scopesonlyInScope- Resetea solo en el scope nombrado (tiene precedencia sobreinAllScopes)
Ejemplo - Uso básico:
void main() async {
// Register lazy singletons
getIt.registerLazySingleton<CacheService>(() => CacheService());
getIt.registerLazySingleton<UserPreferences>(() => UserPreferences());
// Access them (creates instances)
final cache = getIt<CacheService>();
print('cache: $cache');
final prefs = getIt<UserPreferences>();
print('prefs: $prefs');
// Reset all lazy singletons in current scope
await getIt.resetLazySingletons();
// Next access creates fresh instances
final newCache = getIt<CacheService>();
print('newCache: $newCache'); // New instance
}Ejemplo - Con scopes:
void main() async {
// Base scope lazy singletons
getIt.registerLazySingleton<GlobalCache>(() => GlobalCache());
// Push scope and register more
getIt.pushNewScope(scopeName: 'session');
getIt.registerLazySingleton<SessionCache>(() => SessionCache());
getIt.registerLazySingleton<UserState>(() => UserState());
// Access them
final globalCache = getIt<GlobalCache>();
print('globalCache: $globalCache');
final sessionCache = getIt<SessionCache>();
print('sessionCache: $sessionCache');
// Reset only current scope ('session')
await getIt.resetLazySingletons();
// GlobalCache NOT reset, SessionCache and UserState ARE reset
// Reset all scopes
await getIt.resetLazySingletons(inAllScopes: true);
// Both GlobalCache and SessionCache are reset
// Reset only specific scope
await getIt.resetLazySingletons(onlyInScope: 'baseScope');
// Only GlobalCache is reset
}Casos de uso:
- Reset de estado entre pruebas
- Logout de usuario (limpiar lazy singletons específicos de sesión)
- Optimización de memoria (resetear cachés que pueden ser recreados)
- Limpieza específica de scope sin hacer pop del scope
Comportamiento:
- Solo resetea lazy singletons que han sido instanciados
- Los lazy singletons no instanciados no se afectan
- Los singletons regulares y factories no se afectan
- Soporta funciones de disposal tanto sync como async
Introspección Avanzada: findFirstObjectRegistration<T>()
Obtiene metadatos sobre un registro sin recuperar la instancia.
/// find the first registration that matches the type [T]/[instanceName] or the [instance]
ObjectRegistration? findFirstObjectRegistration<T extends Object>({
Object? instance,
String? instanceName,
});Ejemplo:
final registration = getIt.findFirstObjectRegistration<MyService>();
print('registration: $registration');
if (registration != null) {
print(
'Type: ${registration.registrationType}'); // factory, singleton, lazy, etc.
print('Instance name: ${registration.instanceName}');
print('Is async: ${registration.isAsync}');
print('Is ready: ${registration.isReady}');
}Casos de uso:
- Construir herramientas/utilidades de depuración sobre GetIt
- Visualización de grafo de dependencias en tiempo de ejecución
- Gestión avanzada del ciclo de vida
- Depuración de problemas de registro
Accediendo a un objeto dentro de GetIt por un tipo de tiempo de ejecución
En ocasiones raras podrías enfrentarte con el problema de que no conoces el tipo que quieres recuperar de GetIt en tiempo de compilación, lo que significa que no puedes pasarlo como parámetro genérico. Para esto las funciones get tienen un parámetro type opcional
getIt.registerSingleton(TestClass());
final instance1 = getIt.get(type: TestClass);
print('instance1: $instance1');
expect(instance1 is TestClass, true);Ten cuidado de que la variable receptora tenga el tipo correcto y no pases type y un parámetro genérico.
Más de una instancia de GetIt
Aunque no se recomienda, puedes crear tu propia instancia independiente de GetIt si no quieres compartir tu locator con algún otro paquete o porque la física de tu planeta lo demanda 😃
/// To make sure you really know what you are doing
/// you have to first enable this feature:
GetIt myOwnInstance = GetIt.asNewInstance();Esta nueva instancia no comparte ningún registro con la instancia singleton.