How I Do Dependency Injection In Dart

Dependency injection is a common design patteren (you can check What Dependency Injection Is? And Why? for more about it.). For my dart projects, I use two packages, get_it and injectable. They serve different purposes, and together they provide smooth experiences for dependency injection.

Short Intro Of get_it

get_it is a simple service locator. Object instances and object factories can be register and access in one single place.

How to register an instance

final getIt = GetIt.instance;

void setup() {
  getIt.registerSingleton<AppModel>(AppModel());

  // Alternatively you could write it if you don't like global variables
  GetIt.I.registerSingleton<AppModel>(AppModel());
}

How to reference the regstered instance

MaterialButton(
  child: Text("Update"),
  onPressed: getIt<AppModel>().update   // given that your AppModel has a method update
),

Short Intro Of injectable

Manually managing dependencies are tedious since it results many boilerplates.

Boilerplate, boilerplate code everywhere.

This hurts especially when dependency graph is complex.

sample dependency graph

Source: http://kospiotr.github.io/wiki/spring-core-presentation/

Managing such dependency by hand? I prefer not. 😅

injectable generates get_it services register code with dependencies for you so that you don't have to worry about that anymore.

What you write

@injectable
class ServiceA {}

@injectable
class ServiceB {
  ServiceB(ServiceA serviceA);
}

What injectable generates

final getIt = GetIt.instance;

void $initGetIt(GetIt getIt,{String environment,EnvironmentFilter environmentFilter}) {
  final gh = GetItHelper(getIt, environment);
  gh.factory<ServiceA>(() => ServiceA());
  gh.factory<ServiceB>(ServiceA(getIt<ServiceA>()));
}

How To Use

1. Include dependencies in pubspec.yaml

First, add these two packages and their dependencies in proper sections of pubspec.yaml, and then run the command flutter pub get to fetch them to development machine.

dependencies:
  # add injectable to your dependencies
  injectable:
  # add get_it
  get_It:

dev_dependencies:
  # add the generator to your dev_dependencies
  injectable_generator:
  # of course build_runner is needed to run the generator
  build_runner:

2. Configure various dependencies with annotations

Annotate classes you need to inject dependencies or use later

@Singleton()
class ServiceA {}

@Injectable()
class ServiceB {
    ServiceB(ServiceA serviceA);
}

Class annotated with @Singleton() will result a singleton in get_it, while @Injectable() will register a factory that will return a new instance everytime when called via get_it.

Personally I don't use shortcuts @injectable and @singleton as official example on pub.dev page since they cannot pass parameters to customize the annotation.

3. Define a top level function to initialize dependencies

Create a new dart file and add the following code to tell injectable where to place generated code.

final getIt = GetIt.instance;

@InjectableInit(
  initializerName: r'$initGetIt', // default
  preferRelativeImports: true, // default
  asExtension: false, // default
)
void configureDependencies() => $initGetIt(getIt);

4. Generate code

Run the command flutter pub build_runner build and let injectable to do its job.

5. Summon any services anywhere at your will

In anywhere of your code, you can simplly use get_it to access to all instances and factories you have annotated before.

As normal variables

var serviceA = GetIt.I<ServiceA>();
var serviceB = GetIt.I<ServiceB>();

As a change provider

ChangeNotifierProvider.value(
  value: getIt<AppConfigViewModel>(),
  child: ...
)

As a view model

MaterialButton(
  child: Text("Update"),
  onPressed: getIt<AppModel>().update   // given that your AppModel has a method update
),

Closing

Here, I have shared one way to manage dependencies in dart projects. While my dart projects are much more organized following this path, I hope this will also help you and your dart projects.

If you like this article, remember to show your support by buy me a book.