What is Dependency Injection? Dependency Injection in Flutter

Batuhan Sahin
Codimis
Published in
5 min readAug 10, 2022

I have started to learn Spring Boot and there is one argument that caught my attention “IoC and Dependency Injection”. Then I asked myself “How can I handle ‘Dependency Injection’ in Dart? Is there any library for making Dependency Injection easy?

First thing first let’s learn IoC and Dependency Injection.

Giving control to the container to create and inject instances of objects that your application depends on.In other words, instead of creating an object with the new operator, let the container do that for you. Inversion of control relies on dependency injection because a mechanism is needed in order to activate the components providing the specific functionality

Dependency injection is a pattern used to create instances of objects that other objects rely upon. This is accomplished without knowing at compile time which class is going to be used to provide the functionality. Instead, it is done by simply injecting properties into an object, a process called dependency injection.

What is difference between IoC and DI?

IoC is a generic term meaning rather than having the application call the methods in a framework, the framework calls implementations provided by the application.

DI is a form of IoC, where implementations are passed into an object through constructors/setters/service look-ups, which the object will ‘depend’ on in order to behave correctly.

Why do we need to use DI?

Dependency injection in Flutter is an object-oriented technique that sends the dependencies of another object to an object. The objective of the dependency injection strategy is to eliminate this dependency by isolating the use from the formation of the item.

This diminishes the measure of required standard code and further develops adaptability.

In dart, the most basic way to handle dependency injection is by passing services to a class through the constructor.

class MyClass {//MyClass is dependent on OtherClass
OtherClass service;
MyClass(this.service)
}

It’s easy to see why classes with many dependencies can get out of hand very fast.

Libraries for Dependency Injection

Although dependency injection is a simple example, libraries are frequently used to abstract it from the designer. Many of these libraries take advantage of reflection (mirrors in Dart). There are issues in a flutter, however:

  1. Mirrors are crippled for execution reasons.
  2. The settled idea of widgets makes it unfeasible to pass conditions many levels down the tree.

Injection Library

The injection library, on the other hand, can be used to solve similar issues. Annotations used by the injection library are listed below.

@Injector — An inversion of control container developed from a bunch of modules.

@provides and @module — Define classes and methods providing dependencies.

@component — In performing an injection.

https://github.com/google/inject.dart

@moduleclass 
DripCoffeeModule {
@provide
@brandName
String provideBrand() => ‘Coffee by Dart Inc.’;
@provide
@modelName
String provideModel() => ‘DripCoffeeStandard’;
@provide
@asynchronous
Future<PowerOutlet> providePowerOutlet() async => new PowerOutlet(); @provide
@singleton
Electricity provideElectricity(PowerOutlet outlet) => new Electricity(outlet);
@provide
@asynchronous
Future<Heater> provideHeater(Electricity e) async => new ElectricHeater(e);
@provide
Pump providePump(Heater heater) => new Thermosiphon(heater);
}

flutter_simple_dependency_injection Library

This implementation does not rely on the dart reflection apis (mirrors) and favours a simple factory based approach. This increases the performance and simplicity of this implementation.

Support for multiple injectors (useful for unit testing or code running in isolates)

Support for types and named types

Support for singletons

Support simple values (useful for configuration parameters like api keys or urls)

https://pub.dev/packages/flutter_simple_dependency_injection

import 'package:flutter_simple_dependency_injection/injector.dart';

void main() {

final injector = ModuleContainer().initialise(Injector());

injector.get<SomeService>().doSomething();


final instances = injector.getAll<SomeType>();
print(instances.length); // prints '3'
final instance =
injector.get<SomeOtherType>(additionalParameters: {"id": "some-id"});
print(instance.id); // prints 'some-id'
}

class ModuleContainer {
Injector initialise(Injector injector) {
injector.map<Logger>((i) => Logger(), isSingleton: true);
injector.map<String>((i) => "https://api.com/", key: "apiUrl");
injector.map<SomeService>(
(i) => SomeService(i.get<Logger>(), i.get<String>(key: "apiUrl")));

// note that you can configure mapping in a fluent programming style too
injector.map<SomeType>((injector) => SomeType("0"))
..map<SomeType>((injector) => SomeType("1"), key: "One")
..map<SomeType>((injector) => SomeType("2"), key: "Two");

injector.mapWithParams<SomeOtherType>((i, p) => SomeOtherType(p["id"]));

return injector;
}
}
class Logger {
void log(String message) => print(message);
}

class SomeService {
final Logger _logger;
final String _apiUrl;

SomeService(this._logger, this._apiUrl);

void doSomething() {
_logger.log("Doing something with the api at '$_apiUrl'");
}
}

class SomeType {
final String id;
SomeType(this.id);
}

class SomeOtherType {
final String id;
SomeOtherType(this.id);
}

Injector Library

Injector is a simple dependency injection lib for Dart.

It does not replace a complex dependency injection framework like Dagger, but it provides the basics that most apps need. Feature requests are welcomed!

Internally the injector is a singleton that stores instances and builders in a Map.

https://pub.dev/packages/injector

// Register a dependency (Every time a new instance)
injector.registerDependency<Car>(() {
var engine = injector.get<Engine>();
var fuel = injector.get<Fuel>();
var driver = injector.get<Driver>();

return CarImpl(engine,fuel,driver);
});

// Register a singleton
injector.registerSingleton<Car>(() {
var engine = injector.get<Engine>();
var fuel = injector.get<Fuel>();
var driver = injector.get<Driver>();

return CarImpl(engine,fuel,driver);
});

// Use custom Factories by extending [Factory].
injector.register(CustomFactory(() => Engine()));

// Register your dependencies / singletons in the __main.dart__ file

Kiwi Library

A simple yet efficient IoC container for Dart and Flutter.

The container does not rely on reflection, it’s just a Map, so it's fast.

IMPORTANT: Dart2 is required to use this package.

This package can be used with, or without code generation. While code generation allows you to code faster, it comes with additional configuration on your part (to be setup once). This section is only about Kiwi, which contains the IoC container and the annotations. If you are looking for the kiwi_generator configuration, you can find documentation here.

https://pub.dev/packages/kiwi

class Service {}

class ServiceA extends Service {}

class ServiceB extends Service {
ServiceB(ServiceA serviceA);
}
...
// Registers a complex factory by resolving the dependency
// when the type is resolved.
KiwiContainer container = KiwiContainer();
container.registerFactory((c) => ServiceB(c.resolve<ServiceA>()));

Flutter Modular Library

Modular proposes to solve two problems:

- Modularized routes.

- Modularized Dependency Injection.

In a monolithic architecture, where we have our entire application as a single module. However, producing a larger app in a “monolithic” way can generate technical debt in both maintenance and scalability. With this in mind, developers adopted architectural strategies to better divide the code, minimizing the negative impacts on the project’s maintainability and scalability.

https://pub.dev/packages/flutter_modular

A module represents a set of Routes and Binds.

- ROUTE: Page setup eligible for navigation.

- BIND: Represents an object that will be available for injection to other dependencies.

void main(){ 
return runApp(ModularApp(module: AppModule(),
child: <MainWidget>));
}
class AppModule extends Module {
@override
List<Bind> get binds => [];
@override
List<ModularRoute> get routes => [];
}

--

--