This project uses construction-based dependency injection. This means that all the classes will receive their dependencies as parameters of their constructor.
class GetUserByIdUseCase {
final UserRepository userRepository;
GetUserByIdUseCase(this.userRepository); // Dependencies injected by constructor
@override
Stream<User> call(String userId) {
return userRepository.getUserById(userId);
}
}
To satisfy these dependencies each package with concrete implementations will expose a file called dependency_configurator.dart
with an implementation of an interface called DependencyConfigurator. This interface defines a method that must be implemented and it receives a GetIt
instance that can be used to register all the dependencies that are provided by the package. GetIt is simple but powerful service locator, but if you want you can use any other tool with this architecture. DependencyConfigurator
also receives a DependencyConfiguratorContext
instance with extra configuration info.
// dependency_configuration_context.dart
class DependencyConfigurationContext {
final String apiBaseUrl;
ConfigurationContext({
required this.apiBaseUrl,
});
}
// dependency_configurator.dart
abstract class DependencyConfigurator {
void configureDependencies(
DependencyConfiguratorContext context,
GetIt getIt,
);
}
Then these dependency_configurator.dart
can be used from a main module to register all the different dependencies. For instance from main method we can call the following configureDependencies()
method:
// dependencies.dart
final configurators = [
ThemesDependencyConfigurator(),
DomainDependencyConfigurator(),
];
Future configureDependencies(DependencyConfigurationContext context) async{
for (var configurator in configurators) {
await configurator.configureDependencies(context, GetIt.instance);
}
}
Dependency injection is necessary if you're not coding spaghetti 🍝 and you want to keep nice layers of separation in your Flutter app's codebase. The problem is that all of the libraries out there, such as get_it or kiwi, are just service locators with no support or a limited support for automating the registration of dependencies.
Dagger solves it elegantly for native Android and Angular is also known for its powerful dependency injection framework. Now, we Flutter developers can finally use something similar - the injectable package which is a code generator for get_it.
GetIt is a service locator that allows you to create interfaces and their implementations, and access those implementations globally, anywhere in your app. Injectable generates code that we would have otherwise written by using annotations. This allows us to worry more about logic and less about how we are going to access it.
dependencies:
dependency_injection:
path: ../dependency-injection/
dev_dependencies:
# add the generator to your dev_dependencies
injectable_generator:
# add build runner if not already added
build_runner:
- Define a top-level function (lets call it
_configureModuleDependencies
) then annotate it with@injectableInit
. - Call the Generated func $initGetIt(), or your custom initilizer name inside your configure func and pass in the getIt instance.
//global private function
@InjectableInit(initializerName: r'$initDatabaseGetIt')
_configureModuleDependencies(GetIt getIt) => $initDatabaseGetIt(getIt);
//module dependency configurator
class DatabaseDependencyConfigurator implements DependencyConfigurator {
@override
Future configureDependencies(
DependencyConfigurationContext context, GetIt getIt) {
return Future.value(_configureModuleDependencies(getIt)); // wrap in future value
}
}
final getIt = GetIt.instance;
@InjectableInit(
initializerName: r'$initGetIt', // default
preferRelativeImports: true, // default
asExtension: false, // default
)
void configureDependencies() => $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'])
void configureDependencies() => $initGetIt(getIt);
We make sure we call configureDependencies() in your main func before running the App.
More details for understanding Injectable package can be found here,