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
Únete a nuestro servidor de Discord para soporte: https://discord.com/invite/Nn6GkYjzW
Instalación
Añade get_it a tu pubspec.yaml:
dependencies:
get_it: ^8.3.0 # Consulta pub.dev para la última versiónEjemplo Rápido
Paso 1: Crea una instancia global de GetIt (típicamente en un archivo separado):
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():
configureDependencies(); // Register all services FIRST
runApp(MyApp());Paso 3: Accede a tus servicios desde cualquier lugar:
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 Registro | Cuándo se Crea | Tiempo de Vida | Úsalo Cuando |
|---|---|---|---|
| registerSingleton | Inmediatamente | Permanente | El servicio se necesita al inicio, rápido de crear |
| registerLazySingleton | Primer acceso | Permanente | El servicio no siempre se necesita, costoso de crear |
| registerFactory | Cada llamada a get() | Temporal | Necesitas una nueva instancia cada vez (diálogos, objetos temporales) |
Ejemplos:
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>():
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:
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:
// 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
// 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 PaymentProcessorCambiando Implementaciones
Un patrón común es cambiar entre implementaciones reales y simuladas usando registro condicional:
// 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:
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:
- Registros Múltiples - Sistemas de plugins, observadores, middleware
- Patrones Avanzados - Instancias con nombre, conteo de referencias, utilidades
Ayuda:
¿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:
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.instanceoGetIt.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.