Skip to content

Preguntas Frecuentes

¿Por qué necesitamos get_it?

Haz clic para ver la respuesta

Pregunta: No entiendo los beneficios de usar get_it o InheritedWidget.

He investigado por qué necesitamos InheritedWidget, esto resuelve el problema de pasar datos. Sin embargo para eso tenemos un sistema de gestión de estado así que no necesitamos InheritedWidget en absoluto.

He investigado get_it y por lo que entiendo si ya estamos usando un sistema de gestión de estado el único beneficio que tendríamos es la capacidad de encapsular los servicios/métodos relacionados con un grupo de widgets en un solo lugar. (dependency injection)

Por ejemplo si tenemos un mapa y un botón de localízame entonces podrían compartir el mismo servicio _locateMe. Para esto crearíamos una clase abstracta que define el método _locateMe y lo conectaríamos con dependency injection usando un locator.registerLazySingleton.

¿Pero cuál es el punto? Puedo simplemente crear un archivo methods.dart con el método locateMe sin ninguna clase, podemos solo poner el método en methods.dart lo cual es más rápido y fácil y podemos accederlo desde cualquier lugar. No estoy seguro de cómo dart funciona internamente, lo que tiene sentido para mí es que registerLazySingleton removería el método _locateMe de la memoria después de que use el método _locateMe. Y si ponemos el método locateMe dentro de un archivo .dart normal sin clases ni nada más, estará siempre en memoria por lo tanto menos performante. ¿Es cierta mi suposición? ¿Hay algo que me esté perdiendo?


Respuesta: Déjame explicarlo de esta manera, no estás completamente equivocado. Definitivamente puedes usar solo funciones globales y variables globales para hacer que el estado sea accesible a tu UI.

El verdadero poder de dependency injection viene de usar clases de interfaz abstractas al registrar los tipos. Esto te permite cambiar implementaciones en un momento sin cambiar ninguna otra parte de tu código. Esto es especialmente útil cuando se trata de escribir pruebas unitarias o pruebas de UI para que puedas fácilmente inyectar objetos mock.

Otro aspecto es el scoping de los objetos. Los inherited widgets así como get_it te permiten sobrescribir objetos registrados basándote en un scope actual. Para inherited widgets este scope está definido por tu posición actual en el árbol de widgets, en get_it puedes empujar y hacer pop de scopes de registros independiente del árbol de widgets.

Los scopes te permiten sobrescribir comportamiento existente o gestionar fácilmente el tiempo de vida y disposal de objetos.

La idea general de cualquier sistema de dependency injection es que tienes un punto definido en tu código donde tienes toda tu configuración y setup. Además GetIt te ayuda a inicializar tus objetos de negocio síncronos mientras automáticamente se encarga de las dependencias entre tales objetos.

Escribiste que ya estás usando algún tipo de solución de gestión de estado. Lo que probablemente significa que la solución ya ofrece algún tipo de localización de objetos. En este caso probablemente no necesitarás get_it. Junto con watch_it sin embargo no necesitas ninguna otra solución de gestión de estado si ya usas get_it.

Object/factory con tipo X no está registrado - ¿cómo arreglar?

Haz clic para ver la respuesta

Mensaje de error: GetIt: Object/factory with type X is not registered inside GetIt. (Did you forget to register it?)

Este error significa que estás intentando acceder a un tipo que aún no ha sido registrado. Causas comunes:

1. Olvidaste registrar el tipo

dart
// ❌ Trying to get without registering first
  final service = getIt<MyService>();
  print('service: $service'); // ERROR!

Solución: Registra antes de acceder:

dart
getIt.registerLazySingleton<MyService>(() => MyService());
runApp(MyApp());

// Now you can use it
final service = getIt<MyService>();
print('service: $service'); // ✅ Works!

2. Orden incorrecto - accediendo antes del registro

dart
final service = getIt<MyService>();
print('service: $service'); // ❌ ERROR! Not registered yet
getIt.registerLazySingleton<MyService>(() => MyService());
runApp(MyApp());

Solución: Registra primero, usa después:

dart
getIt.registerLazySingleton<MyService>(() => MyService());
final service = getIt<MyService>();
print('service: $service'); // ✅ Now it works
runApp(MyApp());

3. Usando paréntesis en GetIt.instance

dart
final GetIt getIt = GetIt.instance(); // ❌ Wrong! Creates new instance

Solución: Sin paréntesis - es un getter, no una función:

dart
final GetIt getIt = GetIt.instance; // ✅ Correct

4. Discordancia de tipo - registrado tipo concreto pero accediendo interfaz

dart
getIt.registerLazySingleton(
    () => MyServiceImpl()); // Registers as MyServiceImpl
final service = getIt<MyService>();
print('service: $service'); // ❌ Looking for MyService

Solución: Registra con el tipo de interfaz:

dart
getIt.registerLazySingleton<MyService>(
    () => MyService()); // ✅ Register as MyService
final service = getIt<MyService>();
print('service: $service'); // ✅ Works!

5. Accediendo en el scope incorrecto Si registraste en un scope del que se ha hecho pop, el servicio ya no está disponible.

Consejos de depuración:

  • Verifica getIt.isRegistered<MyService>() para verificar el registro
  • Usa getIt.allReady() si tienes registros async
  • Asegúrate de que el registro ocurre antes de runApp() en main()

Object/factory con tipo X ya está registrado - ¿cómo arreglar?

Haz clic para ver la respuesta

Este error significa que estás intentando registrar el mismo tipo dos veces. Causas comunes:

1. Llamando función de registro múltiples veces

dart
void main() {
  configureDependencies(); // First call
  configureDependencies(); // ❌ Second call - ERROR!
  runApp(MyApp());
}

Solución: Solo llama una vez:

dart
void main() {
  configureDependencies(); // Once only!
  runApp(MyApp());
}

2. Registrando dentro de métodos build (problema de hot reload) Si registras servicios dentro de build() o initState(), hot reload lo llamará otra vez.

❌️ Incorrecto:

dart
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    getIt.registerSingleton<MyService>(
        MyService()); // Called on every hot reload!
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('My App')),
        body: Container(),
      ),
    );
  }
}

Solución: Mueve el registro a main() antes de runApp():

dart
void main() {
  getIt.registerSingleton<MyService>(MyService());
  runApp(MyApp());
}

3. Pruebas re-registrando servicios Cada prueba intenta registrar, pero el setup de la prueba anterior no limpió.

Solución: Usa scopes en pruebas (mira "¿Cómo pruebo código que usa get_it?" más abajo).

4. Múltiples registros con diferentes nombres de instancia Si quieres múltiples instancias del mismo tipo:

dart
void main() {
  // Enable multiple registrations first
  getIt.enableRegisteringMultipleInstancesOfOneType();

  getIt.registerSingleton<ApiClient>(ProdApiClient(), instanceName: 'prod');
  getIt.registerSingleton<ApiClient>(DevApiClient(), instanceName: 'dev');
}

O usa registros múltiples sin nombre (mira la documentación de Registros Múltiples).

Mejor práctica:

  • Registra una vez al inicio de la app en main()
  • Usa scopes para login/logout (no unregister/register)
  • Usa scopes en pruebas (no reset/re-register)

¿Debería usar Singleton o LazySingleton?

Haz clic para ver la respuesta

registerSingleton() crea la instancia inmediatamente cuando llamas al método de registro. Usa esto cuando:

  • El objeto se necesita al inicio de la app
  • La inicialización es rápida y no bloqueante
  • Quieres fallar rápido si la construcción falla

registerLazySingleton() retrasa la creación hasta la primera llamada a get(). Usa esto cuando:

  • El objeto podría no ser necesario en cada sesión de la app
  • La inicialización es costosa (cómputo pesado, carga de datos grandes)
  • Quieres tiempo de inicio de app más rápido

Ejemplo:

dart
// Created immediately at app startup
getIt.registerSingleton<Logger>(Logger());

// Only created when first accessed (lazy)
getIt.registerLazySingleton<HeavyDatabase>(() => HeavyDatabase());

Cómo elegir: Usa registerSingleton() para servicios rápidos de crear necesarios al inicio. Usa registerLazySingleton() para servicios costosos de crear o aquellos que no siempre se necesitan. La mayoría de los servicios de app caen en una categoría u otra basándose en su costo de inicialización.

¿Cuál es la diferencia entre Factory y Singleton?

Haz clic para ver la respuesta

Factory (registerFactory()) crea una nueva instancia cada vez que llamas get<T>():

dart
getIt.registerFactory<ShoppingCart>(() => ShoppingCart());
final cart1 = getIt<ShoppingCart>();
print('cart1: $cart1'); // New instance
final cart2 = getIt<ShoppingCart>();
print('cart2: $cart2'); // Different instance

Singleton (registerSingleton() / registerLazySingleton()) devuelve la misma instancia cada vez:

dart
getIt.registerLazySingleton<ApiClient>(() => ApiClient());
final client1 = getIt<ApiClient>();
print('client1: $client1'); // Instance created
final client2 = getIt<ApiClient>();
print('client2: $client2'); // Same instance returned

Cuándo usar Factory:

  • Objetos de corta duración (view models para diálogos, calculadoras temporales)
  • Objetos con estado por llamada (manejadores de solicitud, procesadores de datos)
  • Necesitas múltiples instancias independientes

Cuándo usar Singleton:

  • Servicios a nivel de app (cliente API, base de datos, servicio de auth)
  • Objetos costosos de crear que quieres reutilizar
  • Estado compartido a través de tu app

Consejo pro: La mayoría de los servicios deberían ser Singletons. Las factories son menos comunes - úsalas solo cuando específicamente necesites múltiples instancias.

¿Cómo manejo dependencias circulares?

Haz clic para ver la respuesta

Las dependencias circulares indican un problema de diseño. Aquí están las soluciones:

1. Usa una interfaz/abstracción (Mejor)

dart
abstract class IServiceA {
  void doSomething();
}

class ServiceB {
  final IServiceA serviceA;
  ServiceB(this.serviceA);
}

class ServiceA implements IServiceA {
  late final ServiceB serviceB;

  void init() {
    serviceB = getIt<ServiceB>(); // Get after construction
  }

  @override
  void doSomething() {/* ... */}
}

// Register

2. Usa un mediador/event bus En lugar de dependencias directas, comunícate a través de eventos:

dart
class Event {}

class ServiceAEvent extends Event {}

class ServiceBEvent extends Event {}

class EventBus {
  final _controller = StreamController<Event>.broadcast();
  Stream<Event> get events => _controller.stream;
  void emit(Event event) => _controller.add(event);
}

class ServiceA {
  ServiceA(EventBus bus) {
    bus.events.where((e) => e is ServiceBEvent).listen(_handle);
  }
  void _handle(Event event) {}
}

class ServiceB {
  ServiceB(EventBus bus) {
    bus.events.where((e) => e is ServiceAEvent).listen(_handle);
  }
  void _handle(Event event) {}
}

3. Repiensa tu diseño Las dependencias circulares a menudo significan:

  • Las responsabilidades están mezcladas (dividir en más servicios)
  • Falta capa de abstracción
  • La lógica debería estar en un tercer servicio que coordina ambos

Lo que NO hacer: ❌️ Usar late sin inicialización apropiada ❌️ Usar variables globales para romper el ciclo ❌️ Pasar instancia de getIt alrededor

¿Por qué obtengo "This instance is not available in GetIt" al llamar signalReady?

Haz clic para ver la respuesta

Este error típicamente ocurre cuando intentas llamar signalReady(instance) antes de que la instancia esté realmente registrada en GetIt. Esto comúnmente ocurre cuando usas signalsReady: true con registerSingletonAsync.

Error común:

dart
class MyService {
  Future<void> initialize() async {
    await Future.delayed(Duration(milliseconds: 100));
  }
}

void configureIncorrect() {
  // ❌ This will throw: "This instance is not available in GetIt"
  getIt.registerSingletonAsync<MyService>(
    () async {
      final service = MyService();
      await service.initialize();

      // ❌ ERROR: Instance not in GetIt yet!
      // The factory hasn't returned, so GetIt doesn't know about this instance
      getIt.signalReady(service);

      return service;
    },
    signalsReady: true,
  );
}

Por qué falla: Dentro de la factory async, la instancia aún no ha sido registrada. GetIt solo la añade al registro después de que la factory se completa. Por lo tanto, signalReady(service) falla porque GetIt aún no conoce sobre service.

Solución 1 - No uses signalsReady con registerSingletonAsync (recomendado):

La factory async automáticamente señaliza ready cuando se completa. No necesitas señalización manual:

dart
void configure() {
  // ✅ Correct - no signalsReady needed
  getIt.registerSingletonAsync<MyService>(
    () async {
      final service = MyService();
      await service.initialize();
      return service; // Automatically signals ready
    },
  );
}

Solución 2 - Usa registerSingleton con signalsReady:

Si necesitas señalización manual, registra la instancia síncronamente y señaliza después de que esté en GetIt:

dart
class MyService {
  MyService() {
    _initialize();
  }

  Future<void> _initialize() async {
    await loadData();
    GetIt.instance.signalReady(this); // ✅ Now it's in GetIt
  }

  Future<void> loadData() async {
    await Future.delayed(Duration(milliseconds: 100));
  }
}

void configure() {
  // ✅ Correct - instance registered immediately
  getIt.registerSingleton<MyService>(
    MyService(),
    signalsReady: true, // Must manually signal
  );
}

Solución 3 - Implementa la interfaz WillSignalReady:

GetIt detecta automáticamente esta interfaz y espera la señalización manual:

dart
class MyService implements WillSignalReady {
  MyService() {
    _initialize();
  }

  Future<void> _initialize() async {
    await loadData();
    GetIt.instance.signalReady(this);
  }

  Future<void> loadData() async {
    await Future.delayed(Duration(milliseconds: 100));
  }
}

void configure() {
  // ✅ Correct - interface-based signaling
  getIt.registerSingleton<MyService>(MyService());
  // No signalsReady parameter needed - interface detected
}

Cuándo usar cada enfoque:

  • registerSingletonAsync - La factory maneja TODA la inicialización, devuelve instancia lista
  • registerSingleton + signalsReady - La instancia necesita inicialización async DESPUÉS del registro
  • Interfaz WillSignalReady - Alternativa más limpia al parámetro signalsReady

Mira la documentación de Objetos Asíncronos para detalles completos.

¿Cómo pruebo código que usa get_it?

Haz clic para ver la respuesta

Mira la documentación de Pruebas comprehensiva para patrones detallados de testing, incluyendo:

  • Usar scopes para sombrear servicios con mocks
  • Enfoques de testing de integración
  • Mejores prácticas para setup y teardown de pruebas

¿Dónde debería poner mi código de configuración de get_it?

Haz clic para ver la respuesta

Principio clave: Organiza todos los registros en funciones dedicadas (no dispersos a través de tu app). Esto te permite reinicializar partes de tu app usando scopes.

Enfoque simple - función única:

dart
void configureDependencies() {
  getIt.registerLazySingleton<ApiClient>(() => ApiClient());
  getIt.registerLazySingleton<AuthService>(() => AuthService(getIt()));
  getIt.registerLazySingleton<UserRepository>(() => UserRepository(getIt()));
  getIt.registerFactory<LoginViewModel>(() => LoginViewModel(getIt()));
}

void main() {
  configureDependencies();
  runApp(MyApp());
}

Mejor enfoque - dividir por característica/scope: Divide los registros en funciones separadas que encapsulan la gestión de scope:

dart
void configureCoreDependencies() {
  getIt.registerLazySingleton<ApiClient>(() => ApiClient());
  getIt.registerLazySingleton<Database>(() => Database());
}

void configureAuthDependencies() {
  getIt.pushNewScope(
    scopeName: 'authenticated',
    init: (scope) {
      scope.registerLazySingleton<AuthService>(() => AuthService(getIt()));
      scope
          .registerLazySingleton<UserRepository>(() => UserRepository(getIt()));
    },
  );
}

void configureShopDependencies() {
  getIt.pushNewScope(
    scopeName: 'shopping',
    init: (scope) {
      scope.registerLazySingleton<CartService>(() => CartService(getIt()));
      scope.registerLazySingleton<OrderRepository>(
          () => OrderRepository(getIt()));
    },
  );
}

void main() {
  configureCoreDependencies();
  runApp(MyApp());
}

// Later, when user logs in
void onLogin() {
  configureAuthDependencies(); // Pushes scope and registers services
}

// When user opens shop feature
void openShop() {
  configureShopDependencies(); // Pushes scope and registers services
}

// When user logs out
void onLogout() async {
  await getIt.popScope(); // Removes auth scope and disposes services
}

Por qué importan las funciones:

  • Reutilizable - Llama la misma función al empujar scopes para reinicializar características
  • Testeable - Llama funciones de registro específicas en setup de pruebas
  • Organizado - Separación clara de responsabilidades por característica/capa
  • Centralizado - Toda la lógica de registro en un lugar, no dispersa

No hagas: ❌️ Dispersar llamadas de registro a través de tu app ❌️ Llamar métodos de registro desde código de widget ❌️ Mezclar registro con lógica de negocio ❌️ Duplicar código de registro para diferentes scopes

Mira la documentación de Scopes para más sobre arquitectura basada en scopes.

¿Cuándo debería usar Scopes vs unregister/register?

Haz clic para ver la respuesta

Usa scopes - están diseñados exactamente para este caso de uso:

Con Scopes (Recomendado ✅):

dart
// User logs in - push new scope
void onLogin(String token) {
  getIt.pushNewScope(scopeName: 'authenticated');
  getIt.registerSingleton<User>(AuthenticatedUser(token));
  getIt.registerSingleton<ApiClient>(AuthenticatedApiClient(token));
}

// User logs out - pop scope (automatic cleanup!)
void onLogout() async {
  await getIt.popScope(); // All auth services disposed, guest services restored
}

Sin Scopes (No recomendado ❌️):

dart
void onLogin(String token) async {
  await getIt.unregister<User>();
  await getIt.unregister<ApiClient>();
  getIt.registerSingleton<User>(AuthenticatedUser(token));
  getIt.registerSingleton<ApiClient>(AuthenticatedApiClient(token));
}

void onLogout() async {
  await getIt.unregister<User>();
  await getIt.unregister<ApiClient>();
  getIt.registerSingleton<User>(GuestUser());
  getIt.registerSingleton<ApiClient>(PublicApiClient());
}

Por qué los scopes son mejores:

  • ✅ Limpieza y restauración automática
  • ✅ No puedes olvidar re-registrar servicios originales
  • ✅ Funciones dispose llamadas automáticamente
  • ✅ Código más limpio y menos propenso a errores
  • ✅ Puedes empujar múltiples scopes anidados

Usa unregister cuando:

  • Verdaderamente estás removiendo un servicio permanentemente (raro)
  • Estás reseteando durante el ciclo de vida de la app (usa reset() en su lugar)

Mira la documentación de Scopes para más patrones.

¿Puedo usar get_it con generación de código (injectable)?

Haz clic para ver la respuesta

¡Sí! El paquete injectable proporciona generación de código para registros de get_it usando anotaciones.

Sin injectable (manual):

dart
void configureDependencies() {
  getIt.registerLazySingleton<ApiClient>(() => ApiClient());
  getIt.registerLazySingleton<AuthService>(() => AuthService(getIt()));
  getIt.registerLazySingleton<UserRepository>(
      () => UserRepository(getIt(), getIt()));
  // ... 50 more registrations
}

Con injectable (generado):

dart
@injectable
class ApiClient {}

@Injectable(as: IAuthService)
class AuthService implements IAuthService {
  AuthService(ApiClient client);
}

@injectable
class UserRepository {
  UserRepository(ApiClient client, IAuthService auth);
}

// Generated code handles all registrations!
@InjectableInit()
void configureDependencies() => getIt.init();

Cuándo usar injectable:

  • ✅ Apps grandes con muchos servicios (50+)
  • ✅ Prefieres código declarativo sobre imperativo
  • ✅ Quieres que dependency injection sea más automática

Cuándo el registro manual está bien:

  • ✅ Apps pequeñas a medianas (< 50 servicios)
  • ✅ Prefieres código explícito y directo
  • ✅ Quieres evitar el paso de compilación de generación de código

Importante: injectable es opcional. ¡get_it funciona genial sin él! La documentación aquí se enfoca en registro manual, que es más simple de aprender y funciona para la mayoría de las apps.

Mira la documentación de injectable si quieres usarlo.

¿Cómo paso parámetros a factories?

Haz clic para ver la respuesta

Mira la documentación de Registro de Objetos para información detallada sobre:

  • Usar registerFactoryParam() con uno o dos parámetros
  • Ejemplos prácticos con diferentes tipos de parámetros
  • Patrones alternativos para 3+ parámetros usando objetos de configuración

get_it vs Provider - ¿cuál debería usar?

Haz clic para ver la respuesta

Importante: get_it y Provider sirven propósitos diferentes, aunque a menudo se confunden.

get_it es un Service Locator para dependency injection:

  • Gestiona instancias de servicio/repositorio
  • No específicamente para gestión de estado de UI
  • Desacopla interfaz de implementación
  • Funciona en cualquier parte de tu app (no atado al árbol de widgets)

Provider es para propagación de estado por el árbol de widgets:

  • Pasa datos/estado a widgets descendientes eficientemente
  • Alternativa a InheritedWidget
  • Atado a la posición del árbol de widgets
  • Principalmente para estado de UI

¡Puedes usar ambos juntos!

dart
// get_it manages your business objects

// Provider propagates UI state down the tree
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => ThemeNotifier(),
      child: const MaterialApp(
        home: Scaffold(body: Center(child: Text('Hello'))),
      ),
    );
  }
}

void main() {
  getIt.registerLazySingleton<AuthService>(() => AuthServiceImpl());
  getIt.registerLazySingleton<UserRepository>(() => UserRepository(getIt()));

  runApp(const MyApp());
}

O usa get_it + watch_it en su lugar:

dart
// get_it + watch_it handles BOTH DI and state management

class LoginPage extends WatchingWidget {
  const LoginPage({super.key});

  @override
  Widget build(BuildContext context) {
    final auth = watchIt<AuthService>(); // Rebuilds when auth changes
    return const Scaffold(
      body: Center(child: Text('Login Page')),
    );
  }
}

void main() {
  getIt.registerSingleton<AuthService>(AuthServiceImpl());
  runApp(const MaterialApp(home: LoginPage()));
}

Elige:

  • solo get_it: Si ya tienes gestión de estado (BLoC, Riverpod, etc.)
  • get_it + watch_it: DI todo en uno + gestión de estado reactiva
  • get_it + Provider: Si ya estás usando Provider y quieres mejor DI

Conclusión: get_it es para localización de servicios, watch_it (construido sobre get_it) maneja tanto DI como estado. Provider es ortogonal - puedes usarlo con o sin get_it.

Mira la documentación de watch_it para la solución completa.

¿Cómo re-registro un servicio después de unregister?

Haz clic para ver la respuesta

¡No uses unregister + register para logout/login! Usa scopes en su lugar (mira la FAQ arriba).

Pero si realmente necesitas desregistrar y re-registrar:

Patrón problemático:

dart
void onLogout() async {
  getIt.unregister<AuthService>(); // ❌ Not awaited!
  getIt.registerSingleton<AuthService>(GuestAuthService());
  // Error: AuthService already registered (unregister didn't complete!)
}

Solución 1: Await unregister

dart
void onLogout() async {
  await getIt.unregister<AuthService>(); // ✅ Wait for disposal
  getIt.registerSingleton<AuthService>(GuestAuthService());
}

Solución 2: Usa la función disposing de unregister

dart
await getIt.unregister<AuthService>(
  disposingFunction: (service) async {
    await service.cleanup(); // Custom cleanup logic
  },
);

Solución 3: Resetea lazy singleton en su lugar Si quieres mantener el registro pero resetear la instancia:

dart
await getIt.resetLazySingleton<AuthService>();
// Next call to getIt<AuthService>() creates new instance

Por qué importa el await:

  • Si tu objeto implementa Disposable o tiene una función dispose, unregister la llama
  • Estas funciones dispose pueden ser async
  • Si no haces await, el siguiente registro podría ocurrir antes de que el disposal se complete
  • Esto causa errores de "already registered"

Nuevamente, prefiere fuertemente scopes sobre unregister/register:

dart
// Logout - pop scope
  await getIt.popScope(); // All auth services disposed automatically
// Guest services from base scope automatically restored!

¡Mucho más limpio y menos propenso a errores!

Publicado bajo la Licencia MIT.