Dependency Injection (DI) in Flutter (and more generally in software development) is a design pattern used to make your code more modular, testable, and maintainable by separating object creation from object usage.
Let’s break that down 👇
🧩 What It Means
Normally, when a class depends on another class, you might write something like:
class ApiService {
void fetchData() => print('Fetching data...');
}
class UserRepository {
final ApiService apiService = ApiService(); // 👈 creating dependency here
void getUser() {
apiService.fetchData();
}
}
Here, UserRepository creates its own ApiService.
That makes it hard to test (you can’t easily mock ApiService) and hard to change (if ApiService needs parameters, every place that creates it must change).
💡 With Dependency Injection
Instead, you inject the dependency from outside:
class UserRepository {
final ApiService apiService;
UserRepository(this.apiService); // 👈 dependency is injected
void getUser() {
apiService.fetchData();
}
}
Now you can easily provide any version of ApiService you like (real, mock, etc.) when creating UserRepository:
final userRepo = UserRepository(ApiService());
Or in tests:
final userRepo = UserRepository(MockApiService());
🧰 Common Ways to Do Dependency Injection in Flutter
Flutter doesn’t have built-in DI, but there are several popular packages that make it easy:
1. provider
Simplest way — you can provide dependencies using the widget tree.
void main() {
runApp(
Provider(
create: (_) => ApiService(),
child: MyApp(),
),
);
}
Then retrieve it anywhere in the widget tree:
final api = Provider.of<ApiService>(context, listen: false);
2. get_it (Service Locator Pattern)
A lightweight and widely used DI library.
final getIt = GetIt.instance;
void setup() {
getIt.registerLazySingleton<ApiService>(() => ApiService());
getIt.registerFactory<UserRepository>(() => UserRepository(getIt<ApiService>()));
}
Then anywhere in your code:
final userRepo = getIt<UserRepository>();
This avoids passing dependencies down the widget tree.
3. riverpod
A more modern and powerful alternative to provider, also supports DI patterns:
final apiServiceProvider = Provider((ref) => ApiService());
final userRepoProvider = Provider((ref) => UserRepository(ref.read(apiServiceProvider)));
Then use it like this:
final userRepo = ref.read(userRepoProvider);
✅ Benefits of Dependency Injection
- Easier testing: You can mock dependencies easily.
- Better code reuse: Swap out implementations (e.g., fake API vs real API).
- Decoupling: Classes don’t depend on specific implementations.
- Cleaner architecture: Promotes SOLID principles.
⚠️ Common Pitfalls
- Overusing DI can make small apps more complex than necessary.
- Misusing Service Locators (
get_it) can lead to hidden dependencies. - It’s best combined with architecture patterns like MVVM, Clean Architecture, or Bloc.
2 comments
[…] What is dependency injection in flutter […]
[…] What is dependency injection in flutter […]