← Flutter Reference

What is Dependency Injection in Flutter?

Architecture · Intermediate

By Abdelrahman Saed — Senior Mobile Engineer. Last updated: 2026-08-10.

Dependency injection (DI) in Flutter is a design pattern where objects receive their dependencies from an external source rather than creating them internally. This decouples construction from usage, making code modular, testable, and maintainable — especially in large codebases with many services.

How it works

Without DI, a class creates its own dependencies:

// Tight coupling — hard to test, hard to swap
class UserService {
  final _api = ApiClient();  // created internally
  final _db = Database();     // created internally
}

With DI, the class declares what it needs, and an external source provides it:

// Loose coupling — easy to test, easy to swap
class UserService {
  UserService(this._api, this._db);
  final ApiClient _api;
  final Database _db;
}

In Flutter, the most common DI approaches are:

1. get_it — a service locator. Register types at startup, resolve them anywhere:

GetIt.instance.registerSingleton(ApiClient());
GetIt.instance.registerFactory<UserService>(() => UserService(
  GetIt.instance<ApiClient>(),
  GetIt.instance<Database>(),
));
final userService = GetIt.instance<UserService>();

2. injectable — a code-generation layer on top of get_it that auto-wires dependencies via annotations.

3. Provider — uses the widget tree for DI via InheritedWidget.

At iStoria, we use get_it + injectable because it works outside the widget tree (critical for background tasks, isolates, and tests) and because injectable's generated wiring prevents manual registration errors at 50+ modules.

When to use it

Use DI when:

  • Your app has more than a few services (API clients, databases, analytics, feature flags).
  • You need to swap implementations in tests (mock the API, use an in-memory database).
  • Multiple features depend on the same shared services.

Skip DI when:

  • The app is small enough that manual construction is simpler than a DI container.
  • You don't write tests (DI's primary value is testability through swappable dependencies).

Strengths

  • Decouples construction from usage — swap implementations without touching consumers
  • Makes testing trivial — inject mock implementations in tests without monkey-patching
  • Centralized registration — all dependencies are visible in one place
  • Works outside the widget tree — critical for services, isolates, and background tasks (get_it)

Tradeoffs

  • Added indirection — resolving a dependency requires a lookup, not a constructor call
  • Runtime errors — a missing registration crashes at first use, not at compile time (injectable mitigates this)
  • Learning curve — teams need to understand registration, scoping, and lifetimes

Comparisons

Related articles

Related case studies

FAQ

Should I use get_it or Provider for dependency injection?

Use get_it (with injectable) for services that need to be accessible outside the widget tree — API clients, databases, analytics. Use Provider for things that are tied to the widget tree lifecycle — theme, locale, feature flags scoped to a screen. Many apps use both.


Available for hire. Abdelrahman Saed is a Senior Mobile Engineer (Flutter) — open to full-time, fractional, contract, or advisory work. Hire me →

Book a 20-minute call · Download the CV (PDF) · See how I work