Flutter Clean Architecture: Dependency Injection

October 29, 2024
Flutter Clean Architecture: Dependency Injection
Flutter Clean Architecture: Dependency Injection
The crucial part of implementing Clean Architecture in Flutter is implementing Dependency Injection. For Dependency Injection we use get_it, injectable, injectable_generator dependency and dev dependency. This chapter covers how we implement Dependency Injection through various layers of Clean Architecture.

Table of Contents

Setting up project

We will need to add following dependencies.

				
					dependencies:
  xxx: xxx
  get_it: ^7.7.0
  injectable: ^2.4.2
				
			

And following dev dependencies.

				
					dev_dependencies:
  xxx: xxx
  build_runner: ^2.4.11
  injectable_generator: ^2.6.1
				
			

We have added these packages in first chapter of our application Flutter Clean Architecture: Introduction & Project Setup.

After this lets create di.dart fild in lib/di directory.

				
					import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'di.config.dart';

final getIt = GetIt.instance;

@InjectableInit()
Future<GetIt> configureDependencies() async => getIt.init();

@module
abstract class RegisterModule {
  @preResolve
  Future<SharedPreferences> prefs() => SharedPreferences.getInstance();
}
				
			

Here we used @preResolve for SharedPreferences as we are going to use throughout the project. There are other different configs you can use which can be viewed in get_it.
After this we need to run build_runner’s build command.

				
					dart run build_runner build --delete-conflicting-outputs
				
			

This will generate all required files.

Before we call runApp inside our main function. we should add:

				
					Future<void> main() async {
  // Add these lines
  WidgetsFlutterBinding.ensureInitialized();
  await configureDependencies();
 // Add these lines
  runApp(const MyApp());
}
				
			

This completes our setup.

Integration

We have a simple integration method where we use annotations to tell the generator which type of injection we will be using. You can view all of these from injectable.

Here in our project we place @LazySingleton() or @lazySingleton annotations in the class where we want to inject.

Example:

				
					import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_clean_architecture/feature/setting/domain/usecase/get_dark_mode_usecase.dart';
import 'package:flutter_clean_architecture/feature/setting/domain/usecase/set_dark_mode_usecase.dart';
import 'package:injectable/injectable.dart';

part 'setting_state.dart';

@LazySingleton()
class SettingCubit extends Cubit<SettingState> {
  final GetDarkModeUsecase _getDarkModeUsecase;
  final SetDarkModeUsecase _setDarkModeUsecase;
  SettingCubit(
    this._getDarkModeUsecase,
    this._setDarkModeUsecase,
  ) : super(SettingState(
          darkMode: _getDarkModeUsecase(),
        ));

  bool getDarkMode() => _getDarkModeUsecase();

  Future<void> setDarkMode(bool darkMode) async {
    await _setDarkModeUsecase(darkMode);
    emit(state.copyWith(darkMode: darkMode));
  }
}
				
			

Here in above example we have placed @LazySingleton() on top of cubit class. Here we have GetDarkModeUsecase and SetDarkModeUsecase as properties. These properties will be injected and we don’t need to manually add these objects while creating the instance. 

For example, When we want to access SettingCubit we can use:

				
					getIt<SettingCubit>();
				
			

This will return the instance of SettingCubit.

We must keep in mind that every property of the injected class should also have its own injection as per needed.

For example here GetDarkModeUsecase is property of SettingCubit. And GetDarkModeUsecase must have its own injection and so on for every properties and child properties.

				
					import 'package:flutter_clean_architecture/feature/setting/domain/repository/setting_respository.dart';
import 'package:injectable/injectable.dart';

@LazySingleton()
class GetDarkModeUsecase {
  final SettingRepository _respository;
  const GetDarkModeUsecase(this._respository);

  bool call() => _respository.getDarkMode();
}
				
			

Again we can see that GetDarkModeUsecase has SettingRepository property.

We must also inject SettingRepository too. We know that it is a abstract class so we inject from our implementation class instead.

				
					import 'package:flutter_clean_architecture/feature/setting/data/source/setting_local_source.dart';
import 'package:flutter_clean_architecture/feature/setting/domain/repository/setting_respository.dart';
import 'package:injectable/injectable.dart';

@LazySingleton(as: SettingRepository)
class SettingRepositoryImpl implements SettingRepository {
  final SettingLocalSource _settingLocalSource;
  SettingRepositoryImpl(this._settingLocalSource);

  @override
  bool getDarkMode() {
    return  _settingLocalSource.getDarkMode();
  }

  @override
  Future<void> setDarkMode(bool darkMode) async {
    await _settingLocalSource.setDarkMode(darkMode);
  }
}
				
			

We use as property to tell the class name to inject @LazySingleton(as: SettingRepository).

Again here we have SettingLocalSource as property of SettingRepositoryImpl. So SettingLocalSource must have  its own injection.

				
					import 'package:injectable/injectable.dart';
import 'package:shared_preferences/shared_preferences.dart';

@LazySingleton()
class SettingLocalSource {
  final SharedPreferences _pref;
  SettingLocalSource(this._pref);

  Future<void> setDarkMode(bool darkMode) async {
    _pref.setBool('DARK_MODE', darkMode);
  }

  bool getDarkMode() {
    return _pref.getBool('DARK_MODE') ?? false;
  }
}
				
			

Here SettingLocalSource has property SharedPreferences but we do not need to pass its instance while creating object of SettingLocalSource as we have declared it as @preResolve in di.dart file while configuring Dependency Injection.

				
					import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'di.config.dart';

final getIt = GetIt.instance;

@InjectableInit()
Future<GetIt> configureDependencies() async => getIt.init();

@module
abstract class RegisterModule {
  @preResolve
  Future<SharedPreferences> prefs() => SharedPreferences.getInstance();
}
				
			

This completes our Dependency Injection. You can browse all other chapters to learn in detail about Clean Architecture in Flutter.

You can find all of the implementations in following GitHub link: Github Repository

Flutter Clean Architecture: Introduction & Project Setup
Flutter Clean Architecture: Domain Layer
Flutter Clean Architecture: Data Layer
Flutter Clean Architecture: Presentation Layer
Flutter Clean Architecture: Dependency Injection

Related

Annapurna Circuit Trek

10 Best Treks in Nepal

Flutter Clean Architecture: Presentation Layer

Flutter Clean Architecture: Presentation Layer

Flutter Clean Architecture: Data Layer

Flutter Clean Architecture: Data Layer

Flutter Clean Architecture: Domain Layer

Flutter Clean Architecture: Domain Layer

Flutter Clean Architecture Introduction & Project Setup

Flutter Clean Architecture: Introduction & Project Setup