Dependency Injection in Flutter with Injectable Package

Dependency Injection in Flutter with Injectable Package

Utilizing Injectable package in Flutter

Dependency injection is a crucial aspect of software development that enhances modularity, testability, and maintainability of code. In Flutter, dependency injection is widely used to manage dependencies and decouple components of an application. One popular library for achieving dependency injection in Flutter is injectable, which builds upon the functionality provided by get_it. In this article, we'll explore the reasons for using injectable, delve into the role of get_it for code generation, and provide a step-by-step guide for integrating and using these libraries in a Flutter project.

Why Injectable?

Injectable simplifies the dependency injection process in Flutter by offering a declarative and convenient way to manage dependencies. It utilizes code generation to reduce boilerplate code, resulting in cleaner and more maintainable projects. By combining it with get_it, it allows for efficient service location and injection across different parts of the application.

Understanding Get_it and Code Generation

get_it is a popular service locator library in the Dart ecosystem. It allows you to register and retrieve instances of classes (dependencies) from anywhere in your application. While get_it is powerful on its own, injectable takes it a step further by providing code generation capabilities. This means that the dependency injection code can be generated automatically during the build process, reducing manual intervention and potential errors.

Sample Injection Using Get_it

Before we dive into injectable, let's take a look at how dependency injection is typically done using get_it. Here's a basic example:

import 'package:get_it/get_it.dart';

final GetIt getIt = GetIt.instance;

class UserService {
  // Implementation of UserService
}

void setupDependencies() {
  getIt.registerSingleton<UserService>(UserService());
}

void main() {
  setupDependencies();
  // Access the UserService anywhere in the application
  final userService = getIt<UserService>();
}

In this example, we register an instance of UserService as a singleton using get_it. The setupDependencies function is called during the application's initialization to register the necessary dependencies.

Introduction to Injectable

Now, let's introduce injectable and see how it simplifies the dependency injection process. Start by adding the required dependencies to your pubspec.yaml file:

dependencies:
  injectable: ^2.3.2
  get_it: ^7.6.7

dev_dependencies:
  build_runner: any
  injectable_generator: ^2.4.1

Project Example

Step 1: Create a Flutter Project

Begin by creating a new Flutter project using the following command:

flutter create my_injectable_project
cd my_injectable_project

Step 2: Configure Dependencies

Create a file named injection.config.dart and import the necessary libraries:

import 'injection.config.dart';
import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';

final GetIt getIt = GetIt.instance;

@injectableInit
void configureDependencies() => getIt.init();

Step 3: Annotate Dependencies

Now, annotate your dependencies using the @singleton and @injectable annotations provided by injectable. For example:

@lazySingleton
class UserService {
  // Implementation of UserService
}

@injectable
class UserRepository {
  // Implementation of UserRepository
}

Step 4: Generate Code

Run the following command to generate the dependency injection code:

flutter pub run build_runner watch --delete-conflicting-outputs

This command triggers the code generation process and watches for changes, regenerating the code when needed.

Step 5: Use Dependencies

You can now use the injected dependencies anywhere in your Flutter project:

void main() {
  configureDependencies(); // Initialize the dependencies
  final userService = getIt<UserService>();
  final userRepository = getIt<UserRepository>();
  // Use userService and userRepository as needed
}

Let's delve deeper into some key aspects of using injectable in Flutter.

Advanced Features of Injectable

1. Scoped Instances:

injectable supports scoping instances, allowing you to control the lifecycle of dependencies. This is particularly useful when you need different instances of a service for different parts of your application. For example:

@singleton
class GlobalService {
  // Implementation of GlobalService
}

@injectable
class FeatureScopedService {
  // Implementation of FeatureScopedService
}

In this example, GlobalService is registered as a singleton, while FeatureScopedService is scoped and will be created and destroyed based on the specified scope.

2. Named Instances:

You can register multiple instances of the same type with different names using the @Named annotation. This is useful when you have similar dependencies but with distinct configurations:

@singleton
class ApiService {
  // Implementation of ApiService
}

@injectable
@Named("dev")
class DevApiService extends ApiService {
  // Implementation of DevApiService
}

@injectable
@Named("prod")
class ProdApiService extends ApiService {
  // Implementation of ProdApiService
}

3. Factories:

injectable allows you to use factories for dynamic creation of instances. For example, if you need to create a new instance based on some runtime conditions:

@factoryMethod
AuthService createAuthService() {
  // Implementation of AuthService creation logic
}

4. Environment-specific Configurations:

You can use the @Environment annotation to provide different implementations for specific environments. For instance, you may have a different API endpoint for development and production:

@Environment('dev')
@lazySingleton
class ApiEndpoint {
  final String url = 'https://dev.api.com';
}

@Environment('prod')
@lazySingleton
class ApiEndpoint {
  final String url = 'https://prod.api.com';
}

Common Scenarios

1. Unit Testing:

One of the significant advantages of using dependency injection is the ease of unit testing. With injectable, you can easily mock dependencies by creating mock implementations. You can then register these mock instances during tests to ensure isolation and control over your test environment.

2. Modularization:

When building larger applications, modularization becomes crucial. injectable supports modular injection, enabling you to organize your dependencies based on different modules or features. This ensures cleaner and more maintainable code as your project scales.

3. Hot Reload Compatibility:

injectable is designed to work seamlessly with Flutter's hot reload feature. As you make changes to your dependency configuration, the code generation process quickly updates the injected code, allowing you to see the effects of your changes without restarting your application.

Best Practices

  1. Consistent Naming Conventions: Adopt consistent naming conventions for your dependencies. This helps in easily identifying and managing dependencies, especially in larger projects.

  2. Documentation: Document your dependencies to provide clear information on their purpose, usage, and any specific configurations. This is crucial for collaboration within a team.

  3. Regular Code Generation: Ensure that you regularly run the code generation command (flutter pub run build_runner watch --delete-conflicting-outputs) to keep your dependency injection code up-to-date.

  4. Code Organization: Organize your dependency injection code in a structured manner. This helps in maintaining a clear and readable codebase.

By following these practices and exploring the advanced features, you can master dependency injection in Flutter with injectable.

injectable enhances the dependency injection experience in Flutter by combining the simplicity of get_it with the power of code generation. By following the steps outlined in this article, you can seamlessly integrate injectable into your Flutter project, resulting in cleaner, more modular, and easily maintainable code.
Where to go from here: You can find out more about injectable by reading through its official docs using https://pub.dev/packages/injectable or the Github Repo for more examples using: https://github.com/Milad-Akarie/injectable

Did you find this article valuable?

Support Flutter Aware by becoming a sponsor. Any amount is appreciated!