Migrating from GetX: Boosting Flutter App Development Efficiency — Part 3: Dependency Injection
Exploring the Power of Dependency Injection: GetIt and Injectable
In the previous section, we discussed the benefits of using AutoRoute as a replacement for routing in Flutter applications. Building upon that, we now turn our attention to the realm of dependency injection and explore the power of GetIt and Injectable.
Dependency injection is a crucial aspect of building scalable and maintainable codebases. While GetX provides a basic form of dependency injection, GetIt and Injectable take it to the next level by offering advanced features and code generation capabilities. In this chapter, I will guide you through the process of migrating from GetX to GetIt and Injectable, showcasing their advantages and demonstrating how they can enhance our development workflow.
We will begin by installing GetIt and Injectable packages and integrating them into our project. I will walk you through the setup process and show you how to configure your dependencies in a clean and organized manner. With GetIt, we can easily register and resolve dependencies using a simple and intuitive API. We will learn how to define singletons, factories, and lazy dependencies, enabling greater control and flexibility in managing our application’s components.
Next, we will explore the code generation capabilities of Injectable. By leveraging code generation, Injectable eliminates the need for manual dependency registration, making the process more efficient and error-free. We will witness the power of code generation as we define our dependencies using annotations and generate the necessary code to wire them up automatically. This approach not only reduces boilerplate code but also improves code readability and maintainability.
Throughout this chapter, I will provide practical examples and share insights gained from my own migration process. We will examine how GetIt and Injectable enhance the modularity, scalability, and testability of our codebase. By adopting these advanced dependency injection techniques, we will experience improved code organization, reduced coupling, and increased flexibility in managing our dependencies.
Installation:
Proper dependency injection is crucial for maintaining a clean and modular architecture in our Flutter app. It helps manage the dependencies between different components, promotes code reusability, and simplifies the testing process. By leveraging GetIt and Injectable, we can take our dependency injection to the next level and enjoy additional features and flexibility.
In this section, I will walk you through the installation process of two powerful dependency injection packages for Flutter: GetIt and Injectable. These packages provide advanced features and benefits compared to the traditional dependency injection approach offered by GetX.
Whether you are starting a new project or looking to enhance your existing app’s architecture, understanding and implementing these powerful dependency injection packages will greatly benefit your development process. So, let’s dive into the installation steps and unlock the full potential of dependency injection in Flutter!
Getx
Here’s a high-level overview of the steps involved in setting up dependency injection with GetX:
- Install the GetX package: Start by adding the
get
package to your project’spubspec.yaml
file. Runflutter pub get
to fetch the package and its dependencies. - Define your dependencies: Identify the dependencies that your app requires. This could include services, repositories, controllers, or any other objects that need to be accessed throughout your app.
- Create a Service or Repository class: Implement the logic for our dependencies in separate classes. These classes should handle specific functionalities or data operations.
- Set up the dependency injection container: In our app’s entry point, typically the
main.dart
file, create an instance ofGet.put
orGet.lazyPut
for each dependency. Choose the appropriate method based on your requirements.Get.put
registers the dependency as a singleton whileGet.lazyPut
registers the dependency as a factory. You can also utilize theinitialBinding
property ofGetMaterialApp
to initialize dependencies by creating or calling an existing binding class. This ensures that the dependency becomes globally accessible throughout your app. For local dependency injection specific to a module or feature, you can usefeature_binding.dart
thebindings
directory within eachmodule/feature
to handle the local dependency injection process.
Global Injection
class BMPRApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'BMPRApp',
initialRoute: AppPages.INITIAL,
initialBinding: GlobalBindings(),
transitionDuration: const Duration(milliseconds: 500),
defaultTransition: Transition.rightToLeft,
getPages: AppPages.routes,
home: Root(),
theme: homeTheme,
);
}
}
Local Injection
/// modules/create_documents/bindings/create_document_binding.dart
import 'package:get/get.dart';
import '../controllers/create_document_controller.dart';
class CreateDocumentBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<CreateDocumentController>(
() => CreateDocumentController(),
);
Get.lazyPut<ApiProvider>(
() => ApiProvider(),
);
}
}
/// lib/app/routes/app_routes.dart
class AppPages {
AppPages._();
static const INITIAL = Routes.SPLASH;
static final routes = [
GetPage(
name: _Paths.CREATE_DOCUMENT,
page: () => CreateDocumentView(),
binding: CreateDocumentBinding(), /// Local Injection of Create Document
transition: Transition.downToUp,
),
];
}
- Access the dependencies: Now, we can access the dependencies throughout our app by using
Get.find
. GetX will handle the lifecycle management and provide us with the appropriate instances.
class CreateDocumentController extends GetxController {
late ApiProvider provider;
@override
void onInit() {
super.onInit();
provider = Get.find<ApiProvider>(); // Find the injected ApiProvider
}
}
By following these steps, we can establish a solid foundation for dependency injection using GetX. In the next section, I will introduce you to GetIt and Injectable, which offer advanced features and benefits over traditional GetX dependency injection. We’ll explore their installation process and learn how to leverage them to further enhance our app’s architecture. Let’s get started!
GetIt and Injectable
To begin our migration journey from GetX to GetIt and Injectable, we need to install the necessary packages and integrate them into our Flutter project. Follow the steps below to get started:
- Open the project’s
pubspec.yaml
file. - Add the following dependencies to the
pubspec.yaml
file:
dependencies:
get_it: ^7.2.0
injectable: ^1.5.0
dev_dependencies:
injectable_generator: ^1.5.0
build_runner: ^2.1.0
- Save the
pubspec.yaml
file and run the following command in our project's root directory to fetch the dependencies:
flutter pub get
- We will be using code generation, so let’s set up the required configuration. Create a new file called
service_locator.dart
inlib/core/di/
directory
import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';
import 'service_locator.config.dart';
final getIt = GetIt.instance;
@InjectableInit(
initializerName: r'$initGetIt',
preferRelativeImports: true,
asExtension: false,
)
Future<void> configureDependencies() async => $initGetIt(getIt);
- Run the following command in our project’s root directory to generate the code :
# For generating once
flutter pub run build_runner build -d
# If you want to watch for change and re-generate whenever there is an udpate
flutter pub run build_runner watch
- This will generate
service_locator.config.dart
file inside the same directory as ourservice_locator.dart
file. - Next, initialize the code in our
main.dart
. Make sure to initialize it before calling therunApp
method.
WidgetsFlutterBinding.ensureInitialized();
await onfigureDependencies();
runApp(BMPRApp());
That’s it! We have successfully installed the GetIt and Injectable packages and set up the initial configuration. In the next section, I’ll guide you through the setup and usage of GetIt and Injectable, sharing my insights and experience with these powerful dependency injection packages. Let’s dive in and explore their advanced features to enhance your Flutter app’s architecture and maintainability.
Registering Dependencies
To register dependencies, we can create separate module files or annotate the file we want to inject. Here’s an example of registering a dependency :
- Register using module
@module
abstract class RegisterModule {
@Named('btHttpClient')
HttpClient get btHttpClient => HttpClient.init(
HttpSetting(baseUrl: ApiEndpoint.baseUrl),
);
@Named('btStorageHttpClient')
HttpClient get btStorageHttpClient => HttpClient.init(
HttpSetting(baseUrl: ApiEndpoint.storageBaseUrl),
);
}
- Register using annotation
part 'bmpr_router.gr.dart';
@AutoRouterConfig(
replaceInRouteName: 'Page, Route',
)
@LazySingleton() // Inject as a singleton
class BMPRRouter extends _$BMPRRouter {
@override
List<AutoRoute> get routes => [
AutoRoute(
page: SplashRoute.page,
path: '/',
),
];
}
Generating Dependencies
To generate the necessary code for dependency injection, run the following command in our project directory:
flutter pub run build_runner build
This command triggers the code generation process and generates the required files (lib/core/di/service_locator.config.dart
)based on the annotations and configurations.
service_locator.config.dart
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// InjectableConfigGenerator
// **************************************************************************
// ignore_for_file: unnecessary_lambdas
// ignore_for_file: lines_longer_than_80_chars
// coverage:ignore-file
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:get_it/get_it.dart' as _i1;
import 'package:injectable/injectable.dart' as _i2;
_i1.GetIt $initGetIt(
_i1.GetIt getIt, {
String? environment,
_i2.EnvironmentFilter? environmentFilter,
}) {
final gh = _i2.GetItHelper(
getIt,
environment,
environmentFilter,
);
final registerModule = _$RegisterModule();
gh.factory<_i43.HttpClient>(
() => registerModule.btHttpClient,
instanceName: 'btHttpClient',
);
gh.factory<_i43.HttpClient>(
() => registerModule.btStorageHttpClient,
instanceName: 'btStorageHttpClient',
);
}
Utilizing Dependency Injection
With the setup complete and the dependencies registered, we can now start utilizing dependency injection in our Flutter project. Here’s an example of how to use GetIt to retrieve a registered dependency:
final router = getIt<BMPRRouter>();
router.push(LoginRoute());
In this example, we retrieve an instance of BMPRRouter
from the GetIt container using getIt<BMPRRouter>()
.
Congratulations! We have successfully set up GetIt and Injectable, registered dependencies, and utilized dependency injection in our Flutter project. The code generation feature provided by these libraries simplifies the setup process and ensures the correctness of our dependencies.
Comparison: GetX Dependency Injection vs. GetIt and Injectable
When it comes to dependency injection in Flutter, there are multiple options available, each with its own advantages and considerations. In this section, I will compare GetX’s built-in dependency injection with two popular alternatives: GetIt and Injectable.
GetX Dependency Injection
GetX provides a simple and intuitive way to handle dependency injection within your Flutter application. By using Get.put
and Get.lazyPut
methods, you can easily register dependencies and manage their lifecycle. Here are some pros and cons of GetX's dependency injection:
Pros:
- Simplicity: GetX’s dependency injection is straightforward to understand and implement, making it an excellent choice for small to medium-sized projects.
- Integration: The dependency injection system is tightly integrated with other GetX features such as state management and routing, providing a seamless development experience.
- Global and Local Injection: GetX allows for both global and local dependency injection, giving you flexibility in managing dependencies at different levels of your application.
Cons:
- Lack of Advanced Features: GetX’s built-in dependency injection may lack some advanced features offered by dedicated dependency injection libraries.
- Limited Scoping: While GetX supports local injection within modules or features, it may not offer the same level of scoping and separation of concerns as dedicated DI libraries.
GetIt and Injectable
GetIt and Injectable are popular third-party libraries for dependency injection in Flutter applications. They offer additional features and flexibility compared to GetX’s built-in solution. Here’s a comparison of using GetIt and Injectable:
Pros:
- Advanced Features: GetIt and Injectable provide advanced features like named dependencies, scoping, and asynchronous injection, allowing for more fine-grained control over your dependencies.
- Separation of Concerns: These libraries promote clear separation of concerns by providing tools to define modules and organize dependencies based on their purpose.
- Code Generation: GetIt and Injectable leverage code generation to automatically generate the necessary code for dependency injection, reducing manual configuration and potential errors.
Cons:
- Learning Curve: Compared to GetX’s built-in solution, GetIt and Injectable may have a steeper learning curve due to their additional features and concepts.
- Increased Complexity: The advanced features offered by GetIt and Injectable can add complexity to the dependency injection setup, which might not be necessary for simpler projects.
In conclusion, both GetX’s built-in dependency injection and third-party libraries like GetIt and Injectable have their own strengths and considerations. GetX is an excellent choice for its simplicity and seamless integration with other GetX features. On the other hand, GetIt and Injectable provide advanced features and more flexibility, making them suitable for larger and more complex projects.
When deciding on a dependency injection solution, it’s essential to consider the specific needs of your project and weigh the benefits against the potential trade-offs. Regardless of the chosen approach, proper dependency injection will help improve code maintainability, testability, and overall application architecture.
In the next section, we will explore another powerful process of replacing GetX controllers with BLoC for state management.
If you’re interested in learning more about GetIt and Injectable, you can find detailed information in their respective documentation on pub.dev (get_it) and pub.dev (injectable). These resources will provide you with comprehensive insights and examples to further enhance your understanding of these dependency injection libraries.
This comparison is based on my personal experience and understanding as a Flutter developer. The choice of a dependency injection library ultimately depends on individual project requirements and preferences.