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 configureDependencies() async => getIt.init();
@module
abstract class RegisterModule {
@preResolve
Future 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 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 {
final GetDarkModeUsecase _getDarkModeUsecase;
final SetDarkModeUsecase _setDarkModeUsecase;
SettingCubit(
this._getDarkModeUsecase,
this._setDarkModeUsecase,
) : super(SettingState(
darkMode: _getDarkModeUsecase(),
));
bool getDarkMode() => _getDarkModeUsecase();
Future 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();
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 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 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 configureDependencies() async => getIt.init();
@module
abstract class RegisterModule {
@preResolve
Future 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