Dependency injection
Dependency injection in one sentence: an object does not create its dependencies, it receives them.
``` // no: the class wires itself to the network class OrderRepo { final api = ApiClient(); }
// yes: the dependency is supplied class OrderRepo { OrderRepo(this.api); final ApiClient api; } ```
The difference shows up in tests: the first version cannot be tested without a real network; the second only needs a fake ApiClient.
Three approaches are common in Flutter: the constructor (simplest), `InheritedWidget`/Provider (through the tree), and a service locator (get_it — a global registry).
| Approach | Strength | Weakness |
|---|---|---|
| Constructor | Explicit — dependencies are visible in the signature | Threading it through a deep tree is tedious |
| Provider / Riverpod | Reaches down the tree, lifetime managed | Requires a `BuildContext` |
| Service locator (`get_it`) | No context needed, reachable anywhere | Dependencies are hidden — the signature does not reveal them |
Interview tip. The service-locator question can be a trap: get_it is convenient but it hides dependencies — you cannot tell from a class's signature what it needs, which surfaces later as a forgotten mock in a test. A strong answer: I use constructor injection in business classes and keep get_it for the composition root, where objects are wired together, or for places with no context.
📚 Sources and documentation
- get_it packagepub.dev
Registration kinds (singleton, lazy, factory) and scopes are explained in the README.
- Architecture guideofficialdocs.flutter.dev
- provider packageofficialpub.dev