Understanding Dependency Injection

What is Dependency Injection?

Dependency Injection (DI) is a design pattern that implements Inversion of Control (IoC) for managing dependencies between different parts of your application. Instead of having your components create their dependencies directly, these dependencies are “injected” from the outside.

Think of it like this: Instead of a class making its own sandwich (creating its own dependencies), someone else prepares the sandwich and hands it to the class (injecting the dependencies).

Why Use Dependency Injection?

  1. Loose Coupling: Components become less dependent on each other, making your code more maintainable
  2. Easier Testing: You can easily mock dependencies for testing
  3. Flexibility: Change implementations without modifying the dependent code
  4. Reusability: Share instances across your app efficiently

Implementation in Our Template

In our Flutter template, we use the get_it package for dependency injection. get_it is a simple yet powerful service locator for Dart and Flutter projects.

Setting Up Dependencies

Create a file called di/injection.dart where you’ll define all your dependencies:

import 'package:get_it/get_it.dart';

final getIt = GetIt.instance;

void setupDependencies() {
  // Register your dependencies here
  
  // Singleton Registration
  // getIt.registerSingleton<ApiClient>(ApiClient());
  
  // Factory Registration
  // getIt.registerFactory<HomeViewModel>(() => HomeViewModel());
  
  // Lazy Singleton Registration
  // getIt.registerLazySingleton<DatabaseHelper>(() => DatabaseHelper());
}

Types of Registration

  1. Singleton: Creates a single instance that lives throughout the app’s lifecycle
getIt.registerSingleton<ApiClient>(ApiClient());
  1. Factory: Creates a new instance every time you request it
getIt.registerFactory<HomeViewModel>(() => HomeViewModel());
  1. Lazy Singleton: Creates a single instance only when it’s first requested
getIt.registerLazySingleton<DatabaseHelper>(() => DatabaseHelper());

Using Dependencies

After registration, you can access your dependencies anywhere in your app:

// Get a registered instance
final apiClient = getIt<ApiClient>();

Integration with main.dart

Make sure to initialize the dependency injection in your main.dart:

void main() {
  setupDependencies(); // Initialize dependencies
  runApp(MyApp());
}

Best Practices

  1. Register Early: Initialize dependencies at app startup

  2. Choose Registration Type Wisely:

    • Use Singletons for services that need to maintain state
    • Use Factories for view models or disposable objects
    • Use Lazy Singletons for heavy objects that aren’t needed immediately
  3. Keep it Organized: Group related dependencies together in your registration function

Example Implementation

Here’s a practical example:

// di/injection.dart
import 'package:get_it/get_it.dart';
import 'package:your_app/services/api_client.dart';
import 'package:your_app/services/auth_service.dart';
import 'package:your_app/viewmodels/home_viewmodel.dart';

final getIt = GetIt.instance;

void setupDependencies() {
  // API & Services (Singletons)
  getIt.registerSingleton<ApiClient>(ApiClient());
  getIt.registerSingleton<AuthService>(AuthService());
  
  // ViewModels (Factories)
  getIt.registerFactory<HomeViewModel>(() => HomeViewModel(
    apiClient: getIt<ApiClient>(),
    authService: getIt<AuthService>(),
  ));
}

Troubleshooting

Common issues and their solutions:

  1. Unregistered Dependencies
// Error: Object not found
final service = getIt<MyService>();  // ❌ MyService not registered

// Solution: Register before using
getIt.registerSingleton<MyService>(MyService());
final service = getIt<MyService>();  // ✅ Works now
  1. Circular Dependencies Avoid creating circular dependencies where A depends on B and B depends on A.

Next Steps

Now that you understand dependency injection:

  1. Review your app’s architecture
  2. Identify dependencies that could benefit from DI
  3. Implement DI gradually, starting with core services
  4. Write tests using the flexibility DI provides