GetIt Service Locator
The universal toolbox for your app's dependencies
Open interactive version (quiz + challenge)Real-world analogy
What is it?
GetIt is a service locator package for Dart that acts as a central registry for all your app's dependencies. You register services (like Dio, repositories, use cases, and BLoCs) at app startup, and then retrieve them anywhere in your code without needing BuildContext. It supports singletons (one shared instance), lazy singletons (created on first use), and factories (new instance each time). In team_mvp_kit, the DependencyManager class organizes all registrations by layer: network, data sources, repositories, use cases, and BLoCs.
Real-world relevance
In team_mvp_kit, GetIt is the backbone that wires everything together. The DependencyManager is called in main() before runApp. It registers Dio as a singleton (one HTTP client for the whole app), repositories as lazy singletons (created when first needed), use cases as factories (stateless, so new instances are fine), and BLoCs as factories (each screen gets its own BLoC instance). During testing, the team resets GetIt and registers mocks, making it trivial to test any layer in isolation. Without GetIt, you would need to pass dependencies through constructors all the way from main() down to every widget.
Key points
- What Is Dependency Injection? — Dependency Injection (DI) means giving an object its dependencies from the outside instead of creating them internally. This makes code testable, flexible, and loosely coupled.
- Why GetIt? — GetIt is a simple, fast service locator for Dart. Unlike Provider or Riverpod, it works outside the widget tree, meaning you can access services in BLoCs, use cases, and data sources without BuildContext.
- Setting Up GetIt — Create a global GetIt instance and register your dependencies. This is typically done in a setup function called from main() before runApp.
- Singletons vs Factories — Singletons create one instance shared across the entire app (like Dio, repositories). Factories create a new instance every time (like BLoCs that should not be shared between screens).
- LazySingleton vs Singleton — registerSingleton creates the instance immediately at registration time. registerLazySingleton creates it on first access. Lazy is preferred because it speeds up app startup.
- DependencyManager Pattern from team_mvp_kit — team_mvp_kit organizes registration in a DependencyManager class with separate methods for each concern: network, data sources, repositories, use cases, and BLoCs.
- Using GetIt with BlocProvider — BlocProvider's create callback uses getIt to obtain a fully-wired BLoC instance. The BLoC's dependencies are resolved automatically through the registration chain.
- Resetting for Testing — GetIt can be reset between tests to ensure a clean state. You can also unregister specific dependencies and re-register mocks.
- Async Registration — Some dependencies need async initialization (like Hive databases or SharedPreferences). GetIt supports async registration with registerSingletonAsync.
Code example
import 'package:get_it/get_it.dart';
import 'package:dio/dio.dart';
final getIt = GetIt.instance;
// ---- DependencyManager (team_mvp_kit pattern) ----
class DependencyManager {
static Future<void> init() async {
_registerNetwork();
_registerDataSources();
_registerRepositories();
_registerUseCases();
_registerBlocs();
}
static void _registerNetwork() {
getIt.registerLazySingleton<Dio>(() {
final dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
headers: {'Content-Type': 'application/json'},
));
dio.interceptors.add(
LogInterceptor(
requestBody: true,
responseBody: true,
),
);
return dio;
});
}
static void _registerDataSources() {
getIt.registerLazySingleton<TaskRemoteDataSource>(
() => TaskRemoteDataSourceImpl(getIt<Dio>()),
);
}
static void _registerRepositories() {
getIt.registerLazySingleton<TaskRepository>(
() => TaskRepositoryImpl(
getIt<TaskRemoteDataSource>(),
),
);
}
static void _registerUseCases() {
getIt.registerFactory<GetTasksUseCase>(
() => GetTasksUseCase(getIt<TaskRepository>()),
);
getIt.registerFactory<CreateTaskUseCase>(
() => CreateTaskUseCase(getIt<TaskRepository>()),
);
getIt.registerFactory<UpdateTaskStatusUseCase>(
() => UpdateTaskStatusUseCase(
getIt<TaskRepository>()),
);
}
static void _registerBlocs() {
getIt.registerFactory<TaskListBloc>(
() => TaskListBloc(
getTasks: getIt<GetTasksUseCase>(),
createTask: getIt<CreateTaskUseCase>(),
),
);
}
}
// ---- main.dart ----
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize all dependencies
await DependencyManager.init();
// Set up BLoC observer for debugging
Bloc.observer = AppBlocObserver();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
// Global BLoCs provided at app level
BlocProvider(
create: (_) => getIt<AuthBloc>()
..add(CheckAuthStatus()),
),
],
child: MaterialApp.router(
title: 'Task Manager',
routerConfig: appRouter,
),
);
}
}
// ---- Usage in Screens ----
class TaskListScreen extends StatelessWidget {
const TaskListScreen({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
// GetIt resolves the full dependency chain
create: (_) => getIt<TaskListBloc>()
..add(LoadTasks()),
child: const TaskListView(),
);
}
}
// ---- Testing with GetIt ----
void main() {
late MockTaskRepository mockRepo;
late TaskListBloc bloc;
setUp(() {
getIt.reset();
mockRepo = MockTaskRepository();
getIt.registerLazySingleton<TaskRepository>(
() => mockRepo,
);
getIt.registerFactory<GetTasksUseCase>(
() => GetTasksUseCase(getIt()),
);
getIt.registerFactory<CreateTaskUseCase>(
() => CreateTaskUseCase(getIt()),
);
bloc = TaskListBloc(
getTasks: getIt<GetTasksUseCase>(),
createTask: getIt<CreateTaskUseCase>(),
);
});
tearDown(() {
bloc.close();
getIt.reset();
});
// Now you can test with mocked dependencies
}Line-by-line walkthrough
- 1. Create a global GetIt instance accessible throughout the app.
- 2. DependencyManager class organizes all registrations with a static init method.
- 3. _registerNetwork creates a Dio singleton with base URL, timeouts, and logging interceptor.
- 4. LazySingleton means Dio is only created when first requested, not at startup.
- 5. _registerDataSources registers the remote data source, passing Dio via getIt().
- 6. getIt() retrieves the previously registered Dio singleton automatically.
- 7. _registerRepositories registers TaskRepository implementation with its data source dependency.
- 8. _registerUseCases registers use cases as factories since they are stateless operations.
- 9. Each use case receives its repository dependency through getIt().
- 10. _registerBlocs registers BLoCs as factories so each screen gets a fresh instance.
- 11. TaskListBloc receives its use case dependencies through named parameters.
- 12. In main.dart, WidgetsFlutterBinding.ensureInitialized() is called before async init.
- 13. DependencyManager.init() sets up the entire dependency graph before the app runs.
- 14. BlocObserver is set up for development debugging of all BLoC events and transitions.
- 15. MyApp uses MultiBlocProvider for app-level BLoCs like AuthBloc.
- 16. getIt() retrieves a fresh AuthBloc with all dependencies resolved.
- 17. TaskListScreen uses BlocProvider with getIt to create a screen-level BLoC.
- 18. The cascade operator (..) immediately dispatches LoadTasks after creation.
- 19. In tests, getIt.reset() clears all registrations for a clean slate.
- 20. Mock implementations are registered in place of real ones for isolated testing.
- 21. tearDown resets GetIt to prevent test pollution between test cases.
Spot the bug
class DependencyManager {
static void init() {
getIt.registerLazySingleton<Dio>(
() => Dio(),
);
getIt.registerLazySingleton<AuthBloc>(
() => AuthBloc(getIt()),
);
getIt.registerFactory<HomeBloc>(
() => HomeBloc(authBloc: getIt()),
);
}
}
// In Screen A
BlocProvider(
create: (_) => getIt<AuthBloc>(),
child: const LoginScreen(),
)
// In Screen B
BlocProvider(
create: (_) => getIt<AuthBloc>(),
child: const ProfileScreen(),
)Need a hint?
Show answer
Explain like I'm 5
Fun fact
Hands-on challenge
More resources
- GetIt Package (pub.dev)
- Injectable (GetIt code generator) (pub.dev)
- Flutter Dependency Injection (flutter.dev)