Answer
Overview
Dependency Injection (DI) is a design pattern where objects receive their dependencies from external sources rather than creating them internally. GetIt is the most popular service locator package for DI in Flutter.
Why Dependency Injection?
Without DI (Bad)
dartclass UserProfile extends StatelessWidget { Widget build(BuildContext context) { // Tight coupling - creates dependency directly final apiService = ApiService(); final authService = AuthService(); return Text('User: ${authService.currentUser}'); } }
Problems:
- Hard to test (can't mock dependencies)
- Tight coupling between classes
- Difficult to swap implementations
- Creates new instances everywhere (memory waste)
With DI (Good)
dartclass UserProfile extends StatelessWidget { Widget build(BuildContext context) { // Loose coupling - receives dependencies final apiService = GetIt.instance<ApiService>(); final authService = GetIt.instance<AuthService>(); return Text('User: ${authService.currentUser}'); } }
Benefits:
- Easy to test (inject mocks)
- Loose coupling
- Centralized dependency management
- Singleton/lazy loading support
GetIt Package
Installation
yaml# pubspec.yaml dependencies: get_it: ^7.6.0
Basic Setup
1. Create Service Locator
dart// lib/service_locator.dart import 'package:get_it/get_it.dart'; // Global instance final getIt = GetIt.instance; void setupServiceLocator() { // Register services getIt.registerSingleton<ApiService>(ApiService()); getIt.registerLazySingleton<AuthService>(() => AuthService()); getIt.registerFactory<UserRepository>(() => UserRepository()); }
2. Initialize in main()
dart// lib/main.dart void main() { setupServiceLocator(); // Initialize DI runApp(MyApp()); }
3. Use in Widgets
dartclass HomePage extends StatelessWidget { Widget build(BuildContext context) { // Get registered service final authService = getIt<AuthService>(); return Text('User: ${authService.currentUser}'); } }
Registration Types
Singleton (Eager)
Creates instance immediately when registered.
dartgetIt.registerSingleton<DatabaseService>(DatabaseService()); // Instance created NOW
Lazy Singleton
Creates instance only when first accessed.
dartgetIt.registerLazySingleton<ApiService>(() => ApiService()); // Instance created on FIRST getIt<ApiService>() call
Factory
Creates new instance every time.
dartgetIt.registerFactory<UserModel>(() => UserModel()); // NEW instance on EVERY getIt<UserModel>() call
Factory with Parameters
dartgetIt.registerFactoryParam<UserRepository, String, void>( (userId, _) => UserRepository(userId), ); // Usage final repo = getIt<UserRepository>(param1: 'user123');
Real-World Example
Services
dart// lib/services/api_service.dart class ApiService { final String baseUrl = 'https://api.example.com Future<List<User>> fetchUsers() async { final response = await http.get(Uri.parse('$baseUrl/users')); return (jsonDecode(response.body) as List) .map((json) => User.fromJson(json)) .toList(); } } // lib/services/auth_service.dart class AuthService { String? _token; bool get isAuthenticated => _token != null; Future<void> login(String email, String password) async { // Login logic _token = 'some_token'; } void logout() { _token = null; } } // lib/repositories/user_repository.dart class UserRepository { final ApiService _apiService; // Dependency injected via constructor UserRepository(this._apiService); Future<List<User>> getUsers() => _apiService.fetchUsers(); }
Setup
dart// lib/service_locator.dart import 'package:get_it/get_it.dart'; final getIt = GetIt.instance; void setupServiceLocator() { // Core services (Singleton) getIt.registerLazySingleton<ApiService>(() => ApiService()); getIt.registerLazySingleton<AuthService>(() => AuthService()); // Repositories (Factory - new instance each time) getIt.registerFactory<UserRepository>( () => UserRepository(getIt<ApiService>()), ); // BLoCs/Controllers (Factory) getIt.registerFactory<UserBloc>( () => UserBloc(getIt<UserRepository>()), ); }
Usage in Widget
dartclass UserListPage extends StatefulWidget { _UserListPageState createState() => _UserListPageState(); } class _UserListPageState extends State<UserListPage> { late final UserBloc _bloc; void initState() { super.initState(); // Get dependency from GetIt _bloc = getIt<UserBloc>(); _bloc.fetchUsers(); } void dispose() { _bloc.dispose(); super.dispose(); } Widget build(BuildContext context) { return StreamBuilder<List<User>>( stream: _bloc.usersStream, builder: (context, snapshot) { if (!snapshot.hasData) return CircularProgressIndicator(); return ListView.builder( itemCount: snapshot.data!.length, itemBuilder: (context, index) { return Text(snapshot.data![index].name); }, ); }, ); } }
Testing with GetIt
dart// test/user_repository_test.dart import 'package:flutter_test/flutter_test.dart'; import 'package:get_it/get_it.dart'; import 'package:mockito/mockito.dart'; class MockApiService extends Mock implements ApiService {} void main() { final getIt = GetIt.instance; setUp(() { // Register mock for testing getIt.registerSingleton<ApiService>(MockApiService()); }); tearDown(() { getIt.reset(); // Clear all registrations }); test('UserRepository fetches users', () async { final mockApi = getIt<ApiService>(); when(mockApi.fetchUsers()).thenAnswer((_) async => [User(name: 'Test')]); final repo = UserRepository(mockApi); final users = await repo.getUsers(); expect(users.length, 1); expect(users[0].name, 'Test'); }); }
Best Practices
| Practice | Recommendation |
|---|---|
| Registration location | Create text |
| Initialization | Call text text |
| Service naming | Use interfaces for better testability |
| Singleton vs Factory | Singleton for stateful, Factory for stateless |
| Testing | Always call text text |
GetIt vs Other DI Solutions
| Feature | GetIt | Provider | Riverpod | Injectable |
|---|---|---|---|---|
| Syntax | Simple | Widget-based | Simple | Code generation |
| Boilerplate | Low | Medium | Low | Very Low |
| Learning curve | Easy | Easy | Moderate | Easy |
| Widget tree dependency | No | Yes | No | No |
| Code generation | No | No | No | Yes |
| Best for | Any architecture | Widget DI | Modern apps | Large projects |