Improve Flutter Developer Skills “Injectable Package”
“Dependency Injection” is a 25-dollar term for a 5-cent concept.” James Shore
Peace be upon you
In software development, dependency injection (DI) is a design pattern that fosters loose coupling and enhances code maintainability, modularity, and testability. It revolves around providing a class with the objects (dependencies) it needs to function, rather than having the class create them itself. This separation of concerns leads to several advantages:
- Improved Testability: You can easily inject mock or test doubles for dependencies during unit testing, isolating the class under test and verifying its behavior effectively.
- Enhanced Maintainability: By making dependencies explicit, changes in their implementation become more manageable. You can swap out concrete implementations without affecting the dependent class.
- Increased Flexibility: DI allows you to provide different implementations of dependencies based on environments (e.g., development, staging, production) or use cases.
GetItGetIt is a simple Service Locator package that you can use in your Flutter and Dart projects. Instead of InheritedWidget or Provider, thanks to this package, you can access the objects you have registered in a central registry system from your application’s UI.
injectableInjectable is a code generation package written for the get_it package. Using the annotations from this package, we can have injectable_generator create all the codes we need via build_runner.
Here’s a breakdown of common dependency injection (DI) techniques in Flutter, along with their advantages and considerations:
1. Constructor Injection:
Description: Dependencies are explicitly passed as arguments to a class’s constructor.
Advantages:
1.Clear and explicit about dependencies.
2.Encourages loose coupling.
3.Well-suited for simple to moderately complex applications.
Considerations:
.Can lead to lengthy constructors for classes with many dependencies.
May require additional boilerplate code for testing purposes.
2. Setter Injection:
- Description: Dependencies are injected through setter methods in a class.
- Advantages:
- Offers more flexibility for dynamically changing dependencies.
- Considerations:
- Less explicit about dependencies compared to constructor injection.
- Potential for runtime errors if dependencies are not set correctly.
3. Service Locator Pattern: “using GetIt”
- Description: A global registry that stores and provides instances of dependencies.
- Advantages:
- Convenient for accessing dependencies from anywhere in the application.
- Can be useful for singletons or shared dependencies across the entire app.
- Considerations:
- Introduces a tight coupling to the service locator itself.
- Overuse can make code less maintainable and harder to test.
4. Provider Package:
- Description: A popular third-party package that offers a provider tree for dependency management.
- Advantages:
- Simplifies dependency injection throughout the widget tree.
- Promotes a clear and declarative approach to managing dependencies.
- Considerations:
- Might require additional setup compared to other techniques.
Now we can add it to our application :
flutter pub add get_it
flutter pub add injectable
flutter pub add injectable_generator --dev
flutter pub add build_runner --dev
- Create a new dart file and define a global var for your GetIt instance.
import 'package:get_it/get_it.dart';
///this must be used in all modules to get the injection
GetIt getIt = 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
@InjectableInitannotated func, eg app_injection_container.config.dart. - Call the Generated extension func getIt.init(), or your custom initializer name inside your configure func.
import 'package:injectable/injectable.dart';
@InjectableInit(
initializerName: 'configureDependencies', // Customize if needed
preferRelativeImports: true, // Improve code readability
asExtension: true, // Convenience: Use `GetIt.I.get<T>()`
)
void configureDependencies(final $Env env) => $env.initFromEnvironment();
Registering Dependencies (Injectable Classes):
- Annotate your service or provider classes with
@injectableto indicate they can be injected by the generator:
// api_service.dart
@injectable
class ApiService {
Future<String> fetchData() async {
// ... (your API logic)
}
}
// local_storage_service.dart
@injectable
class LocalStorageService {
Future<String> getLocalData() async {
// ... (your local storage logic)
}
}
Running the Generator:
- Execute the build command to generate the necessary files (usually
injection.g.dart):
flutter pub run build_runner watch # For continuous generation as you make changes
# or
flutter pub run build_runner build # For one-time generation
Utilizing Generated Code:
- Import the generated file (e.g.,
injection.g.dart) in yourmain.dart:
import 'package:injectable/injectable.dart';
import 'injection.g.dart'; // Generated file
@injectableInit
void configureDependencies() => $configureDependencies();
void main() async {
configureDependencies();
runApp(MyApp());
}
Initialize the dependencies using configureDependencies() before running the app. This function sets up the injectable environment and registers your dependencies.
Conclusion
Although Flutter does not have a built-in DI framework, injection is a popular and well-known option for its ease of use, code generation capabilities, and support for different DI styles. It can be a valuable asset for managing dependencies in Flutter applications, especially as your project grows in size and complexity.
References
https://www.youtube.com/watch?v=KNcP8z0hWqs
https://pub.dev/packages/injectable#setup
Originally published on Medium on July 7, 2024.

Top comments (0)