Dependency Injection in Flutter

With 17+ years in software development and 14+ years specializing in Android app architecture and development, I am a seasoned Lead Android Developer. My comprehensive knowledge spans all phases of mobile application development, particularly within the banking domain. I excel at transforming business needs into secure, user-friendly solutions known for their scalability and durability. As a proven leader and Mobile Architect.
Imagine you're building a toy robot. Every time you want to build one, you go hunting for its batteries, wheels, and sensors. Sounds tiring, right? But what if someone hands you a toolbox with all the parts neatly organized? Now it’s easy!
That’s Dependency Injection (DI) — instead of every class finding or creating its own parts (dependencies), they’re handed in from the outside. Clean, simple, and organized!
What is Dependency Injection?
Dependency Injection is a design pattern where a class gets the objects it depends on — called dependencies — from outside, rather than creating them internally.
Let’s say you have a LoginService class that needs ApiService to work. Without DI, it would create its own ApiService like this:
class LoginService {
final ApiService apiService = ApiService(); // tightly coupled
}
This creates a strong dependency, making testing or replacing ApiService difficult.
With DI, you pass it from outside:
class LoginService {
final ApiService apiService;
LoginService(this.apiService); // dependency injected from outside
}
Now, LoginService doesn’t care where ApiService comes from. You can swap, mock, or upgrade it easily!
Why Should You Use Dependency Injection?
Let’s break down some powerful benefits:
Testability: You can inject mock objects for testing.
Loose Coupling: Classes don’t depend on specific implementations.
Flexibility: Easier to switch or update logic later.
Readability and Structure: A clear flow of how data moves through your app.
Ways to Use Dependency Injection in Flutter
Flutter doesn’t have built-in DI, but there are amazing tools like Provider, get_it, and manual constructor injection.
Let’s walk through them one by one.
1. Constructor Injection (Simple & Manual)
Constructor injection is the simplest and most straightforward way to inject dependencies. You just pass the dependency into the constructor.
class ApiService {
void fetchUser() => print("User Fetched!");
}
class UserRepository {
final ApiService apiService;
UserRepository(this.apiService);
void loadUser() {
apiService.fetchUser();
}
}
In the main() function, we create the dependency and inject it:
void main() {
final api = ApiService();
final repo = UserRepository(api);
repo.loadUser();
}
Explanation:
UserRepositorydepends onApiService.Instead of creating
ApiServiceinsideUserRepository, we inject it.Makes it easy to replace
ApiServicewith a mock or alternative later.
2. Using Provider — Flutter’s Favorite
The provider package is a popular state management and DI tool. It uses Flutter’s InheritedWidget under the hood to inject dependencies throughout the widget tree.
Step 1: Add Dependency
dependencies:
provider: ^6.1.0
Step 2: Create Services
class ApiService {
String fetchData() => "Data from API";
}
class DataRepository {
final ApiService apiService;
DataRepository(this.apiService);
String getData() => apiService.fetchData();
}
Step 3: Inject with MultiProvider
void main() {
runApp(
MultiProvider(
providers: [
Provider<ApiService>(create: (_) => ApiService()),
ProxyProvider<ApiService, DataRepository>(
update: (_, apiService, __) => DataRepository(apiService),
),
],
child: MyApp(),
),
);
}
Step 4: Access in Your Widgets
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final repository = Provider.of<DataRepository>(context);
final data = repository.getData();
return Scaffold(
appBar: AppBar(title: Text("DI Example")),
body: Center(child: Text(data)),
);
}
}
Explanation:
ApiServiceis registered at the top usingProvider.DataRepositoryis injected usingProxyProviderbecause it depends onApiService.Inside
HomeScreen, we accessDataRepositoryviaProvider.of.
This is clean and perfect for large apps!
3. Using get_it — The Flutter Service Locator
get_it is another lightweight and elegant way to do DI. It lets you register and retrieve services from a global service locator.
Step 1: Add the Package
dependencies:
get_it: ^7.6.4
Step 2: Register Services
import 'package:get_it/get_it.dart';
final getIt = GetIt.instance;
void setup() {
getIt.registerLazySingleton<ApiService>(() => ApiService());
getIt.registerLazySingleton<DataRepository>(
() => DataRepository(getIt<ApiService>()),
);
}
Step 3: Initialize Before Running the App
void main() {
setup(); // register everything
runApp(MyApp());
}
Step 4: Access Anywhere
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final repository = getIt<DataRepository>();
final data = repository.getData();
return Scaffold(
appBar: AppBar(title: Text("GetIt Example")),
body: Center(child: Text(data)),
);
}
}
Explanation:
Services are registered once in
setup().getIt<T>()gives you the instance whenever and wherever you want.It’s quick, global, and super convenient — especially for small to medium apps.
Best Practices for Dependency Injection in Flutter
Use constructor injection when possible.
Use
Providerfor scalable, reactive apps.Use
get_itfor simpler service locator needs.Keep the setup centralized and organized.
Write unit tests with mocked services — that’s the real win of DI!
Summary
| Method | Pros | When to Use |
| Constructor | Simple, testable | Small projects, basic apps |
| Provider | Reactive, integrates with widgets | Medium to large Flutter apps |
| get_it | Easy, fast global access | Small to medium apps, quick setup |
By using DI, you’re giving your classes the superpower of independence. Whether you're building a small app or a large enterprise project, injecting dependencies keeps your code flexible, testable, and maintainable.
Happy Coding !!



