Skip to content
get_it logo

Primeros Pasos

get_it es un Service Locator simple y rápido para Dart y Flutter que te permite acceder a cualquier objeto que registres desde cualquier parte de tu app sin necesitar BuildContext o árboles de widgets complejos.

Beneficios clave:

  • Extremadamente rápido - Búsqueda O(1) usando Map de Dart
  • Fácil de probar - Cambia implementaciones por mocks en pruebas
  • No necesita BuildContext - Accede desde cualquier lugar en tu app (UI, lógica de negocio, donde sea)
  • Tipado seguro - Verificación de tipos en tiempo de compilación
  • Sin generación de código - Funciona sin build_runner

Casos de uso comunes:

  • Acceder a servicios como clientes API, bases de datos o autenticación desde cualquier lugar
  • Gestionar estado global de la app (view models, managers, BLoCs)
  • Cambiar fácilmente implementaciones para pruebas

Flujo de datos get_it

Únete a nuestro servidor de Discord para soporte: https://discord.com/invite/Nn6GkYjzW


Instalación

Añade get_it a tu pubspec.yaml:

yaml
dependencies:
  get_it: ^8.3.0  # Consulta pub.dev para la última versión

Ejemplo Rápido

Paso 1: Crea una instancia global de GetIt (típicamente en un archivo separado):

dart
final getIt = GetIt.instance;

void configureDependencies() {
  // Register your services
  getIt.registerSingleton<ApiClient>(ApiClient());
  getIt.registerSingleton<Database>(Database());
  getIt.registerSingleton<AuthService>(AuthService());
}

Paso 2: Llama a tu función de configuración antes de runApp():

dart
configureDependencies(); // Register all services FIRST
runApp(MyApp());

Paso 3: Accede a tus servicios desde cualquier lugar:

dart
class LoginPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () async {
        // Access service from anywhere - no BuildContext needed!
        await getIt<AuthService>().login('user@example.com', 'password');
      },
      child: Text('Login'),
    );
  }
}

¡Eso es todo! Sin wrappers de Provider, sin InheritedWidgets, sin necesidad de BuildContext.

Seguridad con Isolates

Las instancias de GetIt no son thread-safe y no pueden compartirse entre isolates. Cada isolate obtendrá su propia instancia de GetIt. Esto significa que los objetos registrados en un isolate no pueden accederse desde otro isolate.


Cuándo Usar Cada Tipo de Registro

get_it ofrece tres tipos principales de registro:

Tipo de RegistroCuándo se CreaTiempo de VidaÚsalo Cuando
registerSingletonInmediatamentePermanenteEl servicio se necesita al inicio, rápido de crear
registerLazySingletonPrimer accesoPermanenteEl servicio no siempre se necesita, costoso de crear
registerFactoryCada llamada a get()TemporalNecesitas una nueva instancia cada vez (diálogos, objetos temporales)

Ejemplos:

dart
void configureDependencies() {
  // Singleton - created immediately, used for entire app lifetime
  getIt.registerSingleton<Logger>(Logger());

  // LazySingleton - created on first use, used for entire app lifetime
  getIt.registerLazySingleton<Database>(() => Database());
  getIt.registerLazySingleton<ApiClient>(() => ApiClient());

  // Factory - new instance every time you call getIt<ShoppingCart>()
  getIt.registerFactory<ShoppingCart>(() => ShoppingCart());
}

Mejor práctica: Usa registerSingleton() si tu objeto se va a usar de todos modos y no requiere recursos significativos para crearlo - es el enfoque más simple. Solo usa registerLazySingleton() cuando necesites retrasar una inicialización costosa o para servicios que no siempre se necesitan.


Accediendo a Servicios

El parámetro de tipo genérico que proporcionas al registrar es el que se usa cuando accedes a un objeto después de registrarlo. Si no lo proporcionas, Dart lo inferirá del tipo de implementación:

Obtén tus servicios registrados usando getIt<Tipo>():

dart
void accessServices() {
  // Shorthand syntax (recommended)
  final api = getIt<ApiClient>();

  // Full syntax (same result)
  final db = getIt.get<Database>();

  // The <Type> parameter must match what was registered
  // This works because we registered <ApiClient>:
  final client = getIt<ApiClient>(); // ✅ Works

  // This would fail if ApiClient was registered as a concrete type:
  // final client = getIt<SomeInterface>(); // ❌ Error if not registered as SomeInterface
}

Sintaxis Abreviada

getIt<Tipo>() es la abreviatura de getIt.get<Tipo>(). ¡Ambas funcionan igual - usa la que prefieras!


Registrando Clases Concretas vs Interfaces

La mayoría del tiempo, registra tus clases concretas directamente:

dart
void configureDependencies() {
  getIt.registerSingleton<ApiClient>(ApiClient());
  getIt.registerSingleton<UserRepository>(UserRepository());
}

// Access directly
final api = getIt<ApiClient>();

Esto es más simple y hace que la navegación del IDE a la implementación sea más fácil.

Solo usa interfaces abstractas cuando esperes múltiples implementaciones:

dart
// Use interface when you have multiple versions
abstract class PaymentProcessor {
  Future<void> processPayment(double amount);
}

class StripePaymentProcessor implements PaymentProcessor {
  @override
  Future<void> processPayment(double amount) async {}
}

class PayPalPaymentProcessor implements PaymentProcessor {
  @override
  Future<void> processPayment(double amount) async {}
}

// Register by interface
void configureDependencies() {
  getIt.registerSingleton<PaymentProcessor>(StripePaymentProcessor());
}

Cuándo usar interfaces:

  • ✅ Múltiples implementaciones (producción vs prueba, diferentes proveedores)
  • ✅ Implementaciones específicas de plataforma (móvil vs web)
  • ✅ Feature flags para cambiar implementaciones
  • ❌️ No uses "solo porque sí" - crea fricción de navegación en tu IDE
dart
// Sin parámetro de tipo - Dart infiere StripePaymentProcessor
getIt.registerSingleton(StripePaymentProcessor());
getIt<PaymentProcessor>(); // ❌️ Error - no registrado como PaymentProcessor

// Con parámetro de tipo - registra explícitamente como PaymentProcessor
getIt.registerSingleton<PaymentProcessor>(StripePaymentProcessor());
getIt<PaymentProcessor>(); // ✅ Funciona - registrado como PaymentProcessor

Cambiando Implementaciones

Un patrón común es cambiar entre implementaciones reales y simuladas usando registro condicional:

dart
// Interface
abstract class PaymentProcessor {
  Future<void> processPayment(double amount);
}

// Real implementation
class StripePaymentProcessor implements PaymentProcessor {
  @override
  Future<void> processPayment(double amount) async {
    // Real Stripe API call
  }
}

// Mock implementation for testing
class MockPaymentProcessor implements PaymentProcessor {
  @override
  Future<void> processPayment(double amount) async {
    // Mock - no real API call
  }
}

// Configure with conditional registration
void configureDependencies({bool isTesting = false}) {
  if (isTesting) {
    // Register mock for testing
    getIt.registerSingleton<PaymentProcessor>(MockPaymentProcessor());
  } else {
    // Register real implementation for production
    getIt.registerSingleton<PaymentProcessor>(StripePaymentProcessor());
  }
}

// Business logic - works with either implementation!
class CheckoutService {
  Future<void> processOrder(double amount) {
    // The <PaymentProcessor> type parameter is what enables the switch
    // Without it, mock and real would register as different types
    final processor = getIt<PaymentProcessor>();
    return processor.processPayment(amount);
  }
}

Debido a que ambas implementaciones están registradas como <PaymentProcessor>, el resto de tu código permanece sin cambios - siempre solicita getIt<PaymentProcessor>() independientemente de qué implementación esté registrada.


Organizando tu Código de Configuración

Para apps más grandes, divide el registro en grupos lógicos:

dart
void configureDependencies() {
  _registerCoreServices();
  _registerDataServices();
  _registerBusinessLogic();
}

void _registerCoreServices() {
  getIt.registerLazySingleton<Logger>(() => Logger());
  getIt.registerLazySingleton<Analytics>(() => Analytics());
}

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

void _registerBusinessLogic() {
  getIt.registerLazySingleton<AuthService>(() => AuthService(getIt()));
  getIt.registerLazySingleton<UserRepository>(
      () => UserRepository(getIt(), getIt()));
}

Mira ¿Dónde debería poner mi código de configuración de get_it? para más patrones.


Siguientes Pasos

Ahora que entiendes los básicos, explora estos temas:

Conceptos Principales:

  • Registro de Objetos - Todos los tipos de registro en detalle
  • Scopes - Gestiona el tiempo de vida de servicios para login/logout, características
  • Objetos Asíncronos - Maneja servicios con inicialización asíncrona
  • Pruebas - Prueba tu código que usa get_it

Características Avanzadas:

Ayuda:

  • FAQ - Preguntas comunes y solución de problemas
  • Ejemplos - Ejemplos de código del mundo real

¿Por qué get_it?

Haz clic para aprender sobre la motivación detrás de get_it

A medida que tu app crece, necesitas separar la lógica de negocio del código de UI. Esto hace que tu código sea más fácil de probar y mantener. ¿Pero cómo accedes a estos servicios desde tus widgets?

Enfoques tradicionales y sus limitaciones:

InheritedWidget / Provider:

  • ❌️ Requiere `BuildContext` (no disponible en la capa de negocio)
  • ❌️ Añade complejidad al árbol de widgets
  • ❌️ Difícil de acceder desde tareas en segundo plano, isolates

Singletons Simples:

  • ❌️ No puedes cambiar la implementación para pruebas
  • ❌️ Acoplamiento fuerte a clases concretas
  • ❌️ Sin gestión del ciclo de vida

Contenedores IoC/DI:

  • ❌️ Inicio lento (basados en reflexión)
  • ❌️ "Mágicos" - difícil de entender de dónde vienen los objetos
  • ❌️ La mayoría no funcionan con Flutter (sin reflexión)

get_it resuelve estos problemas:

  • ✅ Accede desde cualquier lugar sin BuildContext
  • ✅ Fácil de simular para pruebas (registra interfaz, cambia implementación)
  • ✅ Extremadamente rápido (sin reflexión, solo búsqueda en Map)
  • ✅ Claro y explícito (ves exactamente qué está registrado)
  • ✅ Gestión del ciclo de vida (scopes, disposal)
  • ✅ Funciona en Dart puro y Flutter

Patrón Service Locator:

get_it implementa el patrón Service Locator - desacopla la interfaz (clase abstracta) de la implementación concreta mientras permite acceso desde cualquier lugar.

Para una comprensión más profunda, lee el artículo clásico de Martin Fowler: Inversion of Control Containers and the Dependency Injection pattern


Nombrando tu Instancia de GetIt

El patrón estándar es crear una variable global:

dart
final getIt = GetIt.instance;

Nombres alternativos que podrías ver:

  • final sl = GetIt.instance; (service locator)
  • final locator = GetIt.instance;
  • final di = GetIt.instance; (dependency injection)
  • GetIt.instance o GetIt.I (usar directamente sin variable)

Recomendación: Usa getIt o di - ambos son claros y ampliamente reconocidos en la comunidad Flutter.

Usando con watch_it

Si estás usando el paquete watch_it, ya tienes disponible una instancia global di - no necesitas crear la tuya. Solo importa watch_it y usa di directamente.

Uso entre Paquetes

GetIt.instance devuelve el mismo singleton a través de todos los paquetes en tu proyecto. Crea tu variable global una vez en tu app principal e impórtala en otros lugares.

Publicado bajo la Licencia MIT.