Package selection guidance compiled from public package information, community data, and project usage.
Injectable is a code generation tool for Flutter that simplifies dependency injection using the get_it service locator. Inspired by Angular, Guice, and inject.dart, it enables clean, type-safe, and maintainable dependency management through annotations. Developers define dependencies with simple decorators, and the generator automatically creates the necessary registration code. This reduces boilerplate and improves code organization, especially in larger applications.
Use cases
Large-scale Flutter applications
Modular app architecture
Testing with mock dependencies
Dependency management across multiple environments
Streamlining service and repository injection
Key features
Automatic dependency registration via code generation
dependencies:# add injectable to your dependencies injectable:# add get_it get_it:dev_dependencies:# add the generator to your dev_dependencies injectable_generator:# add build runner if not already added build_runner:
LeanBuilder support (Experimental) ⚡
injectable_generator (2.12.0+) now supports LeanBuilder for super-fast incremental builds.
To use lean_builder instead of build_runner, simply add it to your dev_dependencies:
Create a new dart file and define a global var for your GetIt instance.
Define a top-level function (lets call it configureDependencies) then annotate it with @injectableInit.
Import the Generated dart file created later on in the code. This will follow the name of the file with the @InjectableInit annotated func, eg file_name.config.dart.
Call the Generated extension func getIt.init(), or your custom initializer name inside your configure func.
Note: you can tell injectable what directories to generate for using the generateForDir property inside of @injectableInit.
The following example will only process files inside of the test folder.
To register cached factories pass true to the cache property inside of the @Injectable annotation.
@Injectable(cache: true)
classServiceA{}
to read more about cached factories check the GetIt docs
Run the generator
Use the [watch] flag to watch the files' system for edits and rebuild as necessary.
flutter packages pub run build_runner watch
if you want the generator to run one time and exits use
flutter packages pub run build_runner build
Inside of the generated file
Injectable will generate the needed register functions for you.
import'package:get_it/get_it.dart'as _i1;
extensionGetItInjectableXon _i1.GetIt {
/// initializes the registration of main-scope dependencies inside of [GetIt] Future<_i1.GetIt> init({
String? environment,
_i2.EnvironmentFilter? environmentFilter,
}) async {
final gh = _i2.GetItHelper(
this,
environment,
environmentFilter,
);
gh.factory<ServiceA>(() => ServiceA());
gh.factory<ServiceB>(ServiceA(getIt<ServiceA>()));
returnthis;
}
}
Generating Accessor Methods
By default, dependencies are accessed using getIt.get<Type>(). However, you can enable the generateAccessors property in @InjectableInit to generate convenient accessor methods that provide a cleaner, more concise API.
Enabling Accessor Generation
Simply set generateAccessors: true in your @InjectableInit annotation:
This provides IDE autocomplete support and type safety while keeping your code clean and readable.
Registering singletons
Use @singleton or @lazySingleton to annotate your singleton classes.
Alternatively use the constructor version to pass signalsReady to getIt.registerSingleton(signalsReady)@Singleton(signalsReady: true) >> getIt.registerSingleton(Model(), signalsReady: true)@LazySingleton() >> getIt.registerLazySingleton(() => Model())
@singleton// or @lazySingleton classApiProvider{}
Disposing of singletons
GetIt provides a way to dispose singleton and lazySingleton instances by passing a dispose callback to the register function. Injectable works in the static realm, which means it's not possible to pass instance functions to your annotation; luckily injectable provides two simple ways to handle instance disposal.
1- Annotating an instance method inside of your singleton class with @disposeMethod.
@singleton// or lazySingleton classDataSource{
@disposeMethodvoiddispose(){
// logic to dispose instance
}
}
2- Passing a reference to a dispose function to Singleton() or LazySingleton() annotations.
@Singleton(dispose: disposeDataSource)
classDataSource{
voiddispose() {
// logic to dispose instance
}
}
/// dispose function signature must match Function(T instance) FutureOrdisposeDataSource(DataSource instance){
instance.dispose();
}
Multiple Registrations
GetIt supports registering multiple instances of the same type without names. To enable this feature, set allowMultipleRegistrations: true in @InjectableInit:
This generates a call to getIt.enableRegisteringMultipleInstancesOfOneType() at the start of the init function, allowing you to register multiple implementations as the same type:
Without allowMultipleRegistrations, the above would fail with a "type already registered" error since both classes register as Plugin.
You can then retrieve all instances using getIt.getAll<Plugin>(). Note that getIt<Plugin>() returns only the first registered instance.
Note: Once enabled, this setting applies globally and cannot be disabled. See GetIt documentation for more details.
FactoryMethod and PostConstruct Annotations
As the name suggests @FactoryMethod annotation is used to tell injectable which method to use to create the dependency, and that includes named constructors, factory constructs and static create methods.
on the other hand @PostConstruct annotation is used to initialize the constructed dependency synchronously or asynchronously and that only includes public member methods.
now both of these annotations take an optional bool flag preResolve. If the create or initialize methods return a future and preResolve is true, the future will be pre-resolved ( awaited ) before the dependency is registered inside of GetIt, otherwise it's registered as an async dependency.
Registering asynchronous injectables
Requires GetIt >= 4.0.0 if we are to make our instance creation async we're gonna need a static initializer method since constructors can not be asynchronous.
Now simply annotate your class with @injectable and tell injectable to use that static initializer method as a factory method using the @factoryMethod annotation
Using a register module (for third party dependencies)
just wrap your instance with a future, and you're good to go.
@moduleabstractclassRegisterModule{
Future<SharedPreferences> get prefs => SharedPreferences.getInstance();
}
Don't forget to call getAsync<T>() instead of get<T>() when resolving an async injectable.
Pre-Resolving futures
if you want to pre-await the future and register its resolved value, annotate your async dependencies with @preResolve.
@moduleabstractclassRegisterModule{
@preResolveFuture<SharedPreferences> get prefs => SharedPreferences.getInstance();
}
It also works with @factoryMethod and @postConstruct annotations.
@Injectable()
classAsyncService{
AsyncService(Service service);
// @preResolve -> this works as well @FactoryMethod(preResolve: true)
staticFuture<AsyncService> create(@factoryParamString? param) =>
Future.value(AsyncService(Service.from(param));
}
@Injectable()
classSomeController{
SomeController(Service service);
// @preResolve -> this works as well @PostConstruct(preResolve: true)
Future<AsyncService> init(@factoryParamString? param) =>
Future.value(SomeController(Service.from(param));
}
Generated Code:
import'package:get_it/get_it.dart'as _i1;
extensionGetItInjectableXon _i1.GetIt {
/// initializes the registration of main-scope dependencies inside of [GetIt] Future<_i1.GetIt> init({
String? environment,
_i2.EnvironmentFilter? environmentFilter,
}) async {
final gh = _i2.GetItHelper(
this,
environment,
environmentFilter,
);
final registerModule = _$RegisterModule();
final sharedPreferences = await registerModule.prefs;
gh.factory<SharedPreferences>(() => sharedPreferences);
returnthis;
}
}
as you can see this will make your init func async so be sure to await for it.
Passing Parameters to factories
Requires GetIt >= 4.0.0
If you're working with a class you own simply annotate your changing constructor param with @factoryParam, you can have up to two parameters max!
Using a register module (for third party dependencies)
if you declare a module member as a method instead of a simple accessor, injectable will treat it as a factory method, meaning it will inject its parameters as it would with a regular constructor.
This is similar to how if you annotate an injected param with @factoryParam injectable will treat it as a factory param.
--- Use the 'as' Property inside of Injectable(as:..) to pass an abstract type that's implemented by the registered dependency
@Injectable(as: Service)
classServiceImplimplementsService{}
// or @Singleton(as: Service)
classServiceImplimplementsService{}
// or @LazySingleton(as: Service)
classServiceImplimplementsService{}
Generated Code:
factory<Service>(() => ServiceImpl())
Binding an abstract class to multiple implementations
Since we can't use type binding to register more than one implementation, we have to use names (tags)
to register our instances or register under different environment. (we will get to that later)
Use the lower cased @named annotation to automatically assign the implementation class name to the instance name. Then use @Named.from(Type) annotation to extract the name from the type.
it is possible to register different dependencies for different environments by using @Environment('name') annotation. in the below example ServiceA is now only registered if we pass the environment name to $init(environment: 'dev').
@Environment("dev")
@injectableclassServiceA{}
you could also create your own environment annotations by assigning the const constructor Environment("") to a global const var.
const dev = Environment('dev');
// then just use it to annotate your classes @dev@injectableclassServiceA{}
You can assign multiple environment names to the same class.
@test@dev@injectableclassServiceA{}
Alternatively use the env property in injectable and subs to assign environment names to your dependencies.
Now passing your environment to init function will create a simple environment filter that will only validate dependencies that have no environments or one of their environments matches the given environment.
Alternatively, you can pass your own EnvironmentFilter to decide what dependencies to register based on their environment keys, or use one of the shipped ones:
NoEnvOrContainsAll
NoEnvOrContainsAny
SimpleEnvironmentFilter
Registering third party types
To Register third party types, create an abstract class and annotate it with @module then add your third party types as property accessors or methods as follows:
In some cases you'd need to register instances that are asynchronous or singleton instances or just have a custom initializer and that's a bit hard for injectable to figure out on its own, so you need to tell injectable how to initialize them:
@moduleabstractclassRegisterModule{
// You can register named preemptive types like follows @Named("BaseUrl")
Stringget baseUrl => 'My base url';
// url here will be injected @lazySingletonDiodio(@Named('BaseUrl') String url) => Dio(BaseOptions(baseUrl: url));
// same thing works for instances that's gotten asynchronous. // all you need to do is wrap your instance with a future and tell injectable how // to initialize it @preResolve// if you need to pre resolve the value Future<SharedPreferences> get prefs => SharedPreferences.getInstance();
// Also, make sure you await for your configure function before running the App.
}
if you're facing even a weirder scenario you can always register them manually in the configure function.
Auto registering
Instead of annotating every single injectable class you write, it is possible to use a Convention Based Configuration to auto register your injectable classes, especially if you follow a concise naming convention.
For example, you can tell the generator to auto-register any class that ends with Service, Repository or Bloc
using a simple regex pattern
class_name_pattern: 'Service$|Repository$|Bloc$'
To use auto-register create a file with the name build.yaml in the same directory as pubspec.yaml and add
targets:$default:builders:injectable_generator:injectable_builder:options:auto_register:true# auto registers any class with a name matches the given pattern class_name_pattern:"Service$|Repository$|Bloc$"# auto registers any class inside a file with a # name matches the given pattern file_name_pattern:"_service$|_repository$|_bloc$"
Manual order
By default injectable tries to re-order dependencies based on their dependents, meaning if A depends on B, B will be registered first.
You can manually decide the order of a specific dependency by giving it a negative number to register it before everything else or a positive number to register it after everything else.
Note All dependencies have order of 0 by default.
You specify the custom order by using annotation @Order(number) or using the property order inside of injectable and subs
// @Order(-1) this works too @Injectable(order: -1)
classService{}
Using Scopes
GetIt v5.0 introduced scopes support, which allows registration of related dependencies in a different scope, so they can be initialized only when needed and disposed of when they're not. More on that here.
To use GetIt scopes using injectable you simply annotate the dependencies that are meant to be registered in a different scope with @Scope('scope-name') or pass in the scope name to Injectable or its subs like so @Injectable(scope: 'scope-name').
dependencies tagged with a scope name will be generated inside of a separate init method than the other main-scope dependencies.
e.g.
// @Scope('auth') this works too @Injectable(scope: 'auth')
classAuthController{}
when you're ready to use the auth-scope, call the generated scope-init method or extension.
// using extensions
getIt.initAuthScope();
// using methods initAuthScope(getIt);
// scope-init method will return future if it has pre-resolved dependencies // so make sure you await it await getIt.initAuthScope();
Including microPackages and external modules
MicroPackages are sub packages that can be depended on and used by the root package, packages that's annotated as micro will generate a MicroPackageModule instead of an init-method and the initiation of those modules is done automatically by the root package's init-method.
so all you have to do is annotate the package as a microPackage by using the named constructor @InjectableInit.microPackage()
// @microPackageInit => short const @InjectableInit.microPackage()
initMicroPackage(){} // will not be called but needed for code generation
By default injectable will automatically include all MicroPackagesModules in the project directory unless the includeMicroPackages flag inside of @InjectableInit is set to false.
@InjectableInit(includeMicroPackages: false)
it's also possible to include micro local or external modules manually by passing them to the externalPackageModules property inside of @injectableInit so they're initialized with the rest of the local dependencies. Note:
Modules assigned to externalPackageModulesBefore will be initialized before the root dependencies;
Modules assigned to externalPackageModulesAfter will be initialized after the root dependencies;