Flutter Dependency Injection Explained With Real Project Examples
Flutter Dependency Injection Explained With Real Project Examples
As Flutter applications grow, managing dependencies becomes one of the biggest architectural challenges.
Initially, most developers directly create objects inside widgets like this:
final apiService = ApiService();
or:
final authRepository = AuthRepository();
This works perfectly in small applications.
But once projects become larger, problems start appearing:
difficult testing
tightly coupled code
duplicated instances
poor scalability
difficult maintenance
I personally experienced this while working on scalable Flutter applications with:
authentication
APIs
Firebase
local database
push notifications
repositories
multiple modules
Managing dependencies manually quickly became messy.
That’s where Dependency Injection became extremely useful.
In this article, I’ll explain:
what Dependency Injection actually is
why Flutter apps need it
how it improves architecture
singleton vs factory
service locators
get_it usage
repository injection
real-world project structure
This guide focuses on practical Flutter development rather than theoretical definitions.
What Is Dependency Injection?
Dependency Injection (DI) is simply:
providing required objects from outside instead of creating them directly inside classes.
Instead of this:
class HomeScreen {
final apiService = ApiService();
}
You inject the dependency:
class HomeScreen {
final ApiService apiService;
HomeScreen(this.apiService);
}
Now the class does not create the dependency itself.
This improves:
flexibility
testing
scalability
Why Dependency Injection Matters
Without DI:
classes become tightly coupled
replacing implementations becomes difficult
testing becomes painful
With DI:
dependencies become reusable
architecture becomes cleaner
mocking becomes easier
large projects scale better
Real-World Example
Imagine your application has:
authentication
payment gateway
API services
analytics
notifications
local database
Without DI:
objects are created repeatedly
dependency chains become messy
object lifecycle becomes difficult
Dependency Injection solves this systematically.
Understanding Tight Coupling
Example of tightly coupled code:
class ProductRepository {
final Dio dio = Dio();
}
Problems:
repository controls dependency creation
difficult mocking
difficult replacement
hard testing
Better Approach
Inject dependency externally:
class ProductRepository {
final Dio dio;
ProductRepository(this.dio);
}
Now:
dependency becomes replaceable
testing becomes easier
architecture becomes cleaner
Dependency Injection vs Service Locator
Many developers confuse these concepts.
Dependency Injection
Dependency is passed directly.
Example:
LoginBloc(authRepository)
Service Locator
A global object locator provides dependencies.
Example:
getIt<AuthRepository>()
Flutter developers often use:
get_it
which is technically:
Service Locator pattern
but commonly used as DI in Flutter architecture.
Why get_it Is Popular
Flutter does not include built-in dependency injection like:
Angular
Spring Boot
So packages like:
get_it
injectable
riverpod
became popular.
Installing get_it
Add dependency:
dependencies:
get_it: ^7.7.0
Basic Setup
Create:
injection_container.dart
Initialize get_it
final getIt = GetIt.instance;
void setupDependencies() {
getIt.registerLazySingleton<ApiService>(
() => ApiService(),
);
}
Access Dependency
final apiService = getIt<ApiService>();
This provides the same instance globally.
Understanding Singleton
Singleton means:
only one instance exists throughout the app lifecycle.
Useful for:
Dio
Firebase services
repositories
local database
shared preferences
registerSingleton
Creates immediately.
getIt.registerSingleton<ApiService>(
ApiService(),
);
registerLazySingleton
Creates only when needed.
getIt.registerLazySingleton<ApiService>(
() => ApiService(),
);
Usually preferred.
registerFactory
Creates NEW instance every time.
getIt.registerFactory<LoginCubit>(
() => LoginCubit(),
);
Useful for:
Cubits
Blocs
temporary objects
Real Project Dependency Structure
A scalable Flutter project often looks like:
core/
├── network/
├── services/
├── di/
features/
├── auth/
├── products/
├── cart/
Inside:
di/
└── injection_container.dart
All dependencies are registered centrally.
Repository Injection Example
API Service
class ApiService {
final Dio dio;
ApiService(this.dio);
}
Repository
class ProductRepository {
final ApiService apiService;
ProductRepository(this.apiService);
}
Cubit
class ProductCubit extends Cubit<ProductState> {
final ProductRepository repository;
ProductCubit(this.repository);
}
Dependency Registration
void setupDependencies() {
getIt.registerLazySingleton(
() => Dio(),
);
getIt.registerLazySingleton(
() => ApiService(getIt()),
);
getIt.registerLazySingleton(
() => ProductRepository(getIt()),
);
getIt.registerFactory(
() => ProductCubit(getIt()),
);
}
This becomes extremely scalable.
Why Dependency Injection Improves Testing
Testing becomes much easier because dependencies can be replaced with mocks.
Example:
final mockRepository = MockRepository();
final cubit =
ProductCubit(mockRepository);
Without DI:
mocking becomes difficult
With DI:
dependencies become replaceable easily
Dependency Injection + Clean Architecture
Dependency Injection works beautifully with:
Clean Architecture
Repository Pattern
BLoC/Cubit
Because:
layers remain decoupled
dependencies stay organized
testing becomes easier
Common Mistakes Developers Make
1. Registering Everything As Singleton
Not every class should be singleton.
Wrong for:
Cubits
temporary UI controllers
2. Creating Dependencies Inside Widgets
Wrong:
final repository = Repository();
inside UI classes.
Dependencies should remain centralized.
3. Circular Dependencies
Example:
ServiceA → ServiceB
ServiceB → ServiceA
Avoid this completely.
4. Overengineering Small Apps
Tiny apps may not need full DI setup.
Do not add unnecessary complexity.
Dependency Injection + BLoC
One of the most common Flutter setups today:
Cubit/BLoC
↓
Repository
↓
API Service
↓
Dio/Firebase
Dependency Injection manages all these layers cleanly.
injectable Package
Many developers combine:
get_it
injectable
injectable helps generate registration code automatically.
But initially:
learning plain get_it first is better.
My Preferred Setup
For scalable Flutter apps, I usually prefer:
Cubit + Clean Architecture + get_it
because:
architecture stays clean
dependencies stay organized
testing becomes easier
scaling becomes smoother
Is Riverpod Also Dependency Injection?
Yes, partially.
Riverpod handles:
state management
dependency injection
very efficiently.
This is one reason Riverpod became extremely popular.
Real Benefits I Personally Experienced
After implementing proper DI:
code became cleaner
debugging improved
testing became easier
dependencies stayed centralized
large modules became manageable
Especially in long-term projects.
When Should You Start Using Dependency Injection?
My recommendation:
Beginners
First understand:
Flutter basics
state management
architecture concepts
Then learn DI.
For Production Apps
Dependency Injection becomes highly recommended once apps involve:
multiple repositories
APIs
Firebase
authentication
scalable architecture
Final Thoughts
Dependency Injection is one of the most important architectural concepts in scalable Flutter development.
Initially it may feel unnecessary.
But once applications grow:
centralized dependency management
clean architecture
reusable services
proper testing
become extremely valuable.
The goal of Dependency Injection is not complexity.
The real goal is:
building maintainable and scalable applications.
FAQs
Is get_it dependency injection?
Technically it is a Service Locator.
But commonly used as DI in Flutter.
Should beginners learn Dependency Injection?
Eventually yes.
But first understand Flutter fundamentals properly.
Is Riverpod better than get_it?
Riverpod handles both:
state management
dependency injection
while get_it focuses mainly on dependency management.
Is Dependency Injection necessary for small apps?
Usually no.
But for medium-to-large apps:
highly recommended.
Conclusion
In this article, we explored:
Flutter Dependency Injection
get_it
singleton vs factory
repository injection
scalable architecture
testing benefits
Clean Architecture integration
Dependency Injection helps Flutter applications become:
cleaner
more maintainable
more scalable
easier to test
easier to manage over time.


.jpg)
Comments
Post a Comment