Question #44MediumFlutter Basics

Dependency Injection in flutter (Get-It)

#flutter

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)

dart
class 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)

dart
class 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

dart
class 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.

dart
getIt.registerSingleton<DatabaseService>(DatabaseService());
// Instance created NOW

Lazy Singleton

Creates instance only when first accessed.

dart
getIt.registerLazySingleton<ApiService>(() => ApiService());
// Instance created on FIRST getIt<ApiService>() call

Factory

Creates new instance every time.

dart
getIt.registerFactory<UserModel>(() => UserModel());
// NEW instance on EVERY getIt<UserModel>() call

Factory with Parameters

dart
getIt.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

dart
class 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

PracticeRecommendation
Registration locationCreate
text
service_locator.dart
file
InitializationCall
text
setupServiceLocator()
in
text
main()
Service namingUse interfaces for better testability
Singleton vs FactorySingleton for stateful, Factory for stateless
TestingAlways call
text
getIt.reset()
in
text
tearDown()

GetIt vs Other DI Solutions

FeatureGetItProviderRiverpodInjectable
SyntaxSimpleWidget-basedSimpleCode generation
BoilerplateLowMediumLowVery Low
Learning curveEasyEasyModerateEasy
Widget tree dependencyNoYesNoNo
Code generationNoNoNoYes
Best forAny architectureWidget DIModern appsLarge projects

Resources