Dependency Injection in Flutter

Table of contents

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:
UserRepository
depends onApiService
.Instead of creating
ApiService
insideUserRepository
, we inject it.Makes it easy to replace
ApiService
with 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:
ApiService
is registered at the top usingProvider
.DataRepository
is injected usingProxyProvider
because it depends onApiService
.Inside
HomeScreen
, we accessDataRepository
viaProvider.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
Provider
for scalable, reactive apps.Use
get_it
for 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 !!
Subscribe to my newsletter
Read articles from Prashant Bale directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Prashant Bale
Prashant Bale
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.