Dependency Injection (DI) in Dart is all about decoupling object creation from object usage β so that classes depend on abstractions rather than concrete implementations. It helps in writing cleaner, testable, and more maintainable code.
In one of my Flutter projects, I implemented DI to manage services like API clients, local storage, and authentication. Instead of creating instances directly inside widgets or classes, I injected them through constructors or a dependency injection framework like get_it.
For example, using constructor injection, I might define a repository class like this:
class UserRepository {
final ApiService apiService;
UserRepository(this.apiService);
Future fetchUser() => apiService.getUser();
}
Then, in the main setup, I can pass dependencies explicitly:
void main() {
final apiService = ApiService();
final userRepository = UserRepository(apiService);
runApp(MyApp(userRepository: userRepository));
}
This way, UserRepository doesnβt need to know how ApiService is created β it just uses it. That made testing much easier since I could inject a mock or fake API service during unit tests instead of calling real APIs.
For larger apps, I used the GetIt package β which is a lightweight service locator. Hereβs how I usually set it up:
final getIt = GetIt.instance;
void setup() {
getIt.registerLazySingleton(() => ApiService());
getIt.registerFactory(() => UserRepository(getIt()));
}
Then I can simply retrieve dependencies anywhere in the app:
final userRepo = getIt();
One challenge I faced was managing the lifecycle of dependencies β especially when mixing singleton and factory registrations. At times, I accidentally used a singleton where I needed a fresh instance, which led to unexpected data persistence. I solved this by clearly documenting what should be singleton (like services) and what should be factory (like view models).
The main limitation with manual DI or service locators is boilerplate β it can get repetitive in larger projects. As an alternative, frameworks like Riverpod or Provider offer declarative DI with reactive state management, which Iβve also used when building scalable Flutter apps.
