Skip to content

Latest commit

History

134 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

MIT Licensestarspub versionBuy Me A Coffee


Installation

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: 

Setup


  1. Create a new dart file and define a global var for your GetIt instance.
  2. Define a top-level function (lets call it configureDependencies) then annotate it with @injectableInit.
  3. Call the Generated func $initGetIt(), or your custom initilizer name inside your configure func and pass in the getIt instance.
final getIt =GetIt.instance; @InjectableInit( initializerName:r'$initGetIt', // default 
preferRelativeImports:true, // default 
asExtension:false, // default 
) voidconfigureDependencies() =>$initGetIt(getIt); 

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.

@InjectableInit(generateForDir: ['test']) voidconfigureDependencies() =>$initGetIt(getIt); 
  1. Call configureDependencies() in your main func before running the App.
voidmain() { configureDependencies(); runApp(MyApp()); } 

Registering factories


All you have to do now is annotate your injectable classes with @injectable and let the generator do the work.

@injectableclassServiceA {} @injectableclassServiceB { ServiceB(ServiceA serviceA); } 

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

final getIt =GetIt.instance; void$initGetIt(GetIt getIt,{String environment,EnvironmentFilter environmentFilter}) { final gh =GetItHelper(getIt, environment); gh.factory<ServiceA>(() =>ServiceA()); gh.factory<ServiceB>(ServiceA(getIt<ServiceA>())); } 

Registering singletons


Use the @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, luckly injectable provides two simple ways to handle instance disposing.

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(); } 

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.

classApiClient { staticFuture<ApiClient> create(Deps ...) async { .... return apiClient; } } 

Now simply annotate your class with @injectable and tell injectable to use that static initializer method as a factory method using the @factoryMethod annotation

@injectable// or lazy/singleton classApiClient { @factoryMethodstaticFuture<ApiClient> create(Deps ...) async { .... return apiClient; } } 

injectable will automatically register it as an asynchronous factory because the return type is a Future.

Generated Code:
factoryAsync<ApiClient>(() =>ApiClient.create()); 

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 it's resolved value, annotate your async dependencies with @preResolve.

@moduleabstractclassRegisterModule { @preResolveFuture<SharedPreferences> get prefs =>SharedPreferences.getInstance(); } 
generated code
Future<void> $initGetIt(GetItget, {String environment, EnvironmentFilter environmentFilter}) async { final gh =GetItHelper(getIt, environment); final registerModule =_$RegisterModule(); final sharedPreferences =await registerModule.prefs; gh.factory<SharedPreferences>(() => sharedPreferences); ... } 

as you can see this will make your initGetIt 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!

@injectableclassBackendService { BackendService(@factoryParamString url); } 
generated code
factoryParam<BackendService, String, dynamic>( (url, _) =>BackendService(url), ); 

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 it's parameters as it would with a regular constructor.
The same way if you annotate an injected param with @factoryParam injectable will treat it as a factory param.

@moduleabstractclassRegisterModule { BackendServicegetService(ApiClient client, @factoryParamString url) =>BackendService(client, url); } 
generated code
factoryParam<BackendService, String, dynamic>( (url, _) => registerModule.getService(g<ApiClient>(), url)); 

Binding abstract classes to implementations


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)

@Named("impl1") @Injectable(as: Service) class ServiceImpl implements Service {} @Named("impl2") @Injectable(as: Service) class ServiceImp2 implements Service {} 

Next annotate the injected instance with @Named() right in the constructor and pass in the name of the desired implementation.

@injectableclassMyRepo { finalService service; MyRepo(@Named('impl1') this.service) } 
Generated code
factory<Service>(() =>ServiceImpl1(), instanceName:'impl1') factory<Service>(() =>ServiceImpl2(), instanceName:'impl2') factory<MyRepo>(() =>MyRepo(getIt('impl1')) 

Auto Tagging

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

@named@Injectable(as:Service) classServiceImpl1implementsService {} @injectableclassMyRepo { finalService service; MyRepo(@Named.from(ServiceImpl1) this.service) } 
Generated code
factory<Service>(() =>ServiceImpl1(), instanceName:'ServiceImpl1') factory<MyRepo>(() =>MyRepo(getIt('ServiceImpl1')) 

Register under different environments


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 $initGetIt(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

@Injectable(as:Service, env: [Environment.dev, Environment.test]) classRealServiceImplimplementsService {} 

Now passing your environment to $initGetIt 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

Using named factories and static create functions


By default, injectable will use the default constructor to build your dependencies but, you can tell injectable to use named/factory constructors or static create functions by using the @factoryMethod annotation. .

@injectableclassMyRepository { @factoryMethodMyRepository.from(Service s); } 

The constructor named "from" will be used when building MyRepository.

factory<MyRepository>(MyRepository.from(getIt<Service>())) 

or annotate static create functions or factories inside of abstract classes with @factoryMethod.

@injectableabstractclassService { @factoryMethodstaticServiceImpl2create(ApiClient client) =>ServiceImpl2(client); @factoryMethodfactoryService.from() =>ServiceImpl(); } 

Generated code.

factory<Service>(() =>Service.create(getIt<ApiClient>())) 

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 like follows:

@moduleabstractclassRegisterModule { @singletonThirdPartyTypeget thirdPartyType; @prod@Injectable(as:ThirdPartyAbstract) ThirdPartyImplget thirdPartyType; } 

Providing custom initializers

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 it's 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$"

Problems with the generation?


Make sure you always Save your files before running the generator, if that does not work you can always try to clean and rebuild.

flutter packages pub run build_runner clean 

Support the Library

  • You can support the library by staring it on Github && liking it on pub or report any bugs you encounter.
  • also, if you have a suggestion or think something can be implemented in a better way, open an issue and let's talk about it.

About

Code Generator for get_it

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages