With the injectable package, how do annotations such as @Injectable(as:), @lazySingleton and @Environment become get_it registrations?
answer
- codegen on top of get_it
- @InjectableInit on configureDependencies
- generated .config.dart with init()
- as: binds the abstract type
- init(environment:) filters by env
basics
~20 sinjectable's generator reads annotated classes and writes a .config.dart file whose init() calls get_it's register methods: @injectable becomes a factory, @singleton and @lazySingleton their get_it counterparts, as: binds an abstract type, and @Environment limits a class to matching environments.
solid answer
~30 sinjectable removes hand-written get_it setup. You annotate a top-level `configureDependencies()` with `@InjectableInit()` and call the generated `getIt.init()` inside it; injectable_generator, a dev dependency run through build_runner, writes `<file>.config.dart`. Each annotated class becomes a registration: `@injectable` a factory, `@singleton` a `registerSingleton`, `@lazySingleton` a `registerLazySingleton`, with constructor parameters resolved from get_it. `@LazySingleton(as: PaymentService)` registers the class under the abstract type. `@Environment('test')`, or the shipped `dev`, `prod` and `test` constants, marks a class for some environments, and `init(environment: Environment.test)` registers only classes with no environment or a matching one, so a `FakePaymentService` can replace the real service in a test build.
code
dart · 32 linesimport 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';
import 'di.config.dart'; // generated by injectable_generator
final getIt = GetIt.instance;
@InjectableInit()
void configureDependencies(String env) => getIt.init(environment: env);
abstract class PaymentService {
Future<void> charge({required int cents});
}
@LazySingleton(as: PaymentService, env: [Environment.prod, Environment.dev])
class CardPaymentService implements PaymentService {
@override
Future<void> charge({required int cents}) async {/* real API */}
}
@LazySingleton(as: PaymentService, env: [Environment.test])
class FakePaymentService implements PaymentService {
final charges = <int>[];
@override
Future<void> charge({required int cents}) async => charges.add(cents);
}
@injectable
class ParkingSessionViewModel {
ParkingSessionViewModel(this._payments);
final PaymentService _payments;
}go deeper
Know that injectable generates get_it registration code from annotations, so you still call getIt<T>().
Map each annotation to its get_it call and explain how as: and @Environment let a test build register a fake.
Judge when codegen is worth its build time and stale-file failures, and how to keep environment tags from drifting.
Decide whether the app's dependency graph is large enough to justify generated wiring and how to migrate to or away from it.
## What injectable adds get_it registrations are plain code, and in a large app `configureDependencies()` grows to hundreds of lines that must be kept in step with every constructor. **injectable** generates that code from annotations. It is two packages: - `injectable`, a normal dependency, holding the annotations; - `injectable_generator`, a dev dependency, run through build_runner. The output is still get_it: the generated code calls get_it's register methods, so everything about lifetimes and `getIt<T>()` still applies. ## Setup 1. Create `final getIt = GetIt.instance;`. 2. Write a top-level function annotated `@InjectableInit()` whose body calls `getIt.init()`. 3. Import the generated `<file>.config.dart`, named after the file that holds the annotated function. 4. Call `configureDependencies()` in `main()` before `runApp`. `@InjectableInit` defaults include `initializerName: 'init'` and `asExtension: true`, which is why the call reads `getIt.init()`. ## Annotation to registration | Annotation | Generated get_it call | |---|---| | `@injectable` / `@Injectable()` | factory: a new instance per get | | `@singleton` / `@Singleton()` | `registerSingleton` | | `@lazySingleton` / `@LazySingleton()` | `registerLazySingleton` | | `as: SomeAbstractType` | registered under that type instead of the class | | `@Named('x')` | registered, or injected, under a name | | `@module` abstract class | registrations for third-party types you cannot annotate | | `@preResolve` | the future is awaited before registration | Constructor parameters are resolved from get_it in the generated code, so `ParkingSessionViewModel(PaymentService payments)` gets whatever is registered as `PaymentService`. ## What the generated file contains The generated `init` extension builds a small helper object from the injectable package, `GetItHelper`, and calls one method per annotated class: `factory`, `singleton` or `lazySingleton`, each wrapping the matching get_it registration. Constructor parameters become `gh<T>()` lookups inside the closures. Because it is ordinary Dart, you can open the `.config.dart` file to see exactly which type a class was registered under and in which environments; that is the first place to look when `getIt<PaymentService>()` throws a `StateError` in a test build. ## Environments: the test swap `@Environment('name')` restricts a class to some environments, and the package ships `dev`, `prod` and `test` constants. The `env:` field on `@Injectable`, `@Singleton` and `@LazySingleton` does the same. At startup, `getIt.init(environment: ...)` builds a filter that registers classes with **no environment, or a matching one**. In a parking app: - `CardPaymentService` is `@LazySingleton(as: PaymentService, env: [Environment.prod, Environment.dev])`; - `FakePaymentService` is `@LazySingleton(as: PaymentService, env: [Environment.test])`; - `ParkingSessionViewModel` is `@injectable` with no environment, so it exists everywhere. Calling `init(environment: Environment.test)` registers the fake as `PaymentService`; production calls it with `Environment.prod`. Custom filters such as `NoEnvOrContainsAll` exist through the `environmentFilter` parameter. ## Costs to weigh - A generated file must be regenerated after every annotation or constructor change; a stale one fails at runtime with a missing registration. - Build time grows with the number of annotated classes. - `@preResolve` makes the generated `init` asynchronous, so `configureDependencies` must return a future and `main` must await it. - Wiring errors still surface at runtime, as get_it errors, not as compile errors. For a small app, hand-written registrations or plain constructor calls are often clearer; injectable pays off when the graph is large and changes often.
- A class has no @Environment annotation. Is it registered when init is called with Environment.test?Yes. The default filter created from `init(environment: ...)` registers dependencies that have no environment, or one that matches. Only classes tagged solely with other environments are skipped.
- How do you register a third-party class you cannot annotate?Declare an abstract class annotated `@module` with getters or methods returning the type, annotated with `@singleton`, `@lazySingleton` or `@injectable`. Add `@preResolve` when the value comes from a future that must be awaited before registration.
saying these in an interview costs you the question
- injectable is a separate container that replaces get_it at runtime.
- Missing registrations are caught at compile time by injectable.
- @injectable registers a singleton by default.
- Classes without @Environment are skipped whenever an environment is passed.
- The generated file never needs regenerating after constructor changes.