How I Do Dependency Injection In Dart
2020-11-14Dependency injection is a common design patteren (you can check What Dependency Injection Is? And Why? for more about it.). For my dart projects, I use two packages, get_it and injectable. They serve different purposes, and together they provide smooth experiences for dependency injection.
Short Intro Of get_it
get_it is a simple service locator. Object instances and object factories can be register and access in one single place.
How to register an instance
final getIt = GetIt.instance;
void setup() {
getIt.registerSingleton<AppModel>(AppModel());
// Alternatively you could write it if you don't like global variables
GetIt.I.registerSingleton<AppModel>(AppModel());
}
How to reference the regstered instance
MaterialButton(
child: Text("Update"),
onPressed: getIt<AppModel>().update // given that your AppModel has a method update
),
Short Intro Of injectable
Manually managing dependencies are tedious since it results many boilerplates.
This hurts especially when dependency graph is complex.
Source: http://kospiotr.github.io/wiki/spring-core-presentation/
Managing such dependency by hand? I prefer not. 😅
injectable generates get_it services register code with dependencies for you so that you don't have to worry about that anymore.
What you write
@injectable
class ServiceA {}
@injectable
class ServiceB {
ServiceB(ServiceA serviceA);
}
What injectable generates
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>()));
}
How To Use
1. Include dependencies in pubspec.yaml
First, add these two packages and their dependencies in proper sections of pubspec.yaml, and then run the command flutter pub get to fetch them to development machine.
dependencies:
# add injectable to your dependencies
injectable:
# add get_it
get_It:
dev_dependencies:
# add the generator to your dev_dependencies
injectable_generator:
# of course build_runner is needed to run the generator
build_runner:
2. Configure various dependencies with annotations
Annotate classes you need to inject dependencies or use later
@Singleton()
class ServiceA {}
@Injectable()
class ServiceB {
ServiceB(ServiceA serviceA);
}
Class annotated with @Singleton() will result a singleton in get_it, while @Injectable() will register a factory that will return a new instance everytime when called via get_it.
Personally I don't use shortcuts
@injectableand@singletonas official example onpub.devpage since they cannot pass parameters to customize the annotation.
3. Define a top level function to initialize dependencies
Create a new dart file and add the following code to tell injectable where to place generated code.
final getIt = GetIt.instance;
@InjectableInit(
initializerName: r'$initGetIt', // default
preferRelativeImports: true, // default
asExtension: false, // default
)
void configureDependencies() => $initGetIt(getIt);
4. Generate code
Run the command flutter pub build_runner build and let injectable to do its job.
5. Summon any services anywhere at your will
In anywhere of your code, you can simplly use get_it to access to all instances and factories you have annotated before.
As normal variables
var serviceA = GetIt.I<ServiceA>();
var serviceB = GetIt.I<ServiceB>();
As a change provider
ChangeNotifierProvider.value(
value: getIt<AppConfigViewModel>(),
child: ...
)
As a view model
MaterialButton(
child: Text("Update"),
onPressed: getIt<AppModel>().update // given that your AppModel has a method update
),
Closing
Here, I have shared one way to manage dependencies in dart projects. While my dart projects are much more organized following this path, I hope this will also help you and your dart projects.
If you like this article, remember to show your support by buy me a book.