Compile-time dependency injection is a technique for managing the dependencies of an application at compile-time rather
than at runtime. This provides several benefits, including improved performance, reduced code size, and better
compile-time error checking. In Dart and Flutter, compile-time dependency injection is implemented using the @inject
and @provides
annotations, along with the Component
and Module
classes.
To use this library in your Dart or Flutter project, you need to add it as a dependency in your pubspec.yaml
file:
// dart
$ dart pub add inject_annotation
$ dart pub add inject_generator build_runner --dev
// flutter
$ flutter pub add inject_annotation
$ flutter pub add inject_generator build_runner --dev
To generate the code, you need to run the build runner:
// dart
$ dart run build_runner build
// flutter
$ flutter pub run build_runner build
To use compile-time dependency injection in your Dart or Flutter application, you need to create a Component
class.
This is an abstract class annotated with @component
or @Component([])
if you also have modules.
Inside the Component
, you can define methods that return instances of the classes you need (
e.g., Repository get repository
in the example below).
@component
abstract class MainComponent {
static const create = g.MainComponent$Component.create;
@inject
Repository get repository;
}
To add a type to the dependency graph, you annotate its class with @inject
. For example:
@inject
class Repository {
const Repository(this.apiClient);
final FakeApiClient apiClient;
Future<String> getGreeting({required String name}) => apiClient.getGreeting(name: name);
}
Note that you cannot add the @inject
annotation to classes from 3rd party libraries.
Modules are classes annotated with @module
. There, you can define dependencies with the @provides
annotation.
Methods annotated with @provides
tell how to provide an instance of a class. Function parameters are the dependencies
of this type.
@module
class ApiModule {
@provides
@singleton
FakeApiClient apiClient() => FakeApiClient();
}
You can then include the module in your Component
:
@component([ApiModule])
abstract class MainComponent {
static const create = g.MainComponent$Component.create;
@inject
Repository get repository;
}
The @singleton
annotation is used to indicate that only one instance of the provided type should be created and shared
across the application. This can help improve performance and reduce memory usage by avoiding unnecessary object
creation.
To use @singleton
, simply add it as an annotation to the method that provides the instance:
@module
class ApiModule {
@provides
@singleton
FakeApiClient apiClient() => FakeApiClient();
}
All dependency injection is analyzed, configured, and generated at compile-time
as part of a build process, and does not rely on any runtime setup or
configuration (such as reflection with dart:mirrors
). This provides the best
experience in terms of code-size and performance (it's nearly identical to hand
written code) and allows us to provide compile-time errors and warnings instead
of relying on runtime.