Dependency injection for Dart and Flutter
Even if I’m writing a small application, I tend to separate the logic from the UI. Because projects grow, I think it’s a good practice to do this if the project is not a proof of concept.
I like to have small services with one responsibility. It’s easier to test, to maintain, to read, to understand. A problem with this principle is that you end up with services having a lot of dependencies. Creating such a service can be tedious if you have to instantiate all the dependencies before calling the constructor. In most of the cases, you can rely on IoC libraries to do this for you and most of them use reflection at runtime to create an object.
But since Flutter cannot run code that depends on
dart:mirrors, we cannot use Dart libraries that lies on reflection to create objects.
So I looked for IoC libraries that we can use in Flutter. Here are the 3 most promising libraries (for me) and why I didn’t use them:
- inject: The package is offered as-is, and I found it too complicated for my needs (I never used Dagger).
- get_it: It’s a really good Service Locator library, but it does not provide a generator in case we want to use it for constructor injection.
- dioc: This is the one that almost fit my needs, but it didn’t support Dart 2 at the time I started my own package. Some other features I wanted were not implemented either.
After this little research, I found the need to create my own library (which is heavily inspired by dioc and get_it) and I ended up sharing it with you now 🙂.
What is kiwi?
At its very core, kiwi is simply a container of instances and factories you can name. It’s coupled with a powerful generator to allow the developer to write less code.
The generator provided by kiwi only supports constructor injection in order to minimize the code that depends on it.
Another important thing is that kiwi only works with Dart ≥ 2.0.0.
The core of kiwi is the
Container class. This is where all your instances and factories are stored.
Container is implemented as a singleton. You can access the single instance like this:
Note: I promise you, even if this is looking like a constructor, you will always end up with the same instance 😉.
The container can store instances, factories, and singletons. What I name singleton here, is a factory that will be called only once, after which the same instance will be provided every time.
Instances can be registered like this:
Factories like that:
And for singletons, you just have to change Factory by Singleton:
Note: The c parameter is the instance of the Container, we will saw later how it can be useful.
Then, you can get the value registered under a specified type like this:
Now if you have a service that depends on another, add the dependency in the constructor. For registering this service, you can then use the
c parameter of the factory to get the value.
For services with a lot of dependencies, it can be tedious to write that sort of code. That’s why kiwi comes with a generator 😃!
The generator let the developer write less code. By using the generator, you minimize also the maintenance cost. When a dependency is added to a service, you just have to run the generator.
The generator handles factories and singletons, but not instances. Only
const instances can appear in metadata, so it would be awkward to support only
const instances here.
Let’s see how to use it:
- l3: You have to create a
partdirective. The value of this directive is simply the name of your file with a
.gbefore the dart extension.
- l5: You have to create an abstract class where all abstract methods are annotated with at least one
- l6: Annotation for registering a singleton for the type
- l7: Annotation for registering a factory that will create a
- l8: Annotation for registering a factory for
ServiceBwith the name
- l9: Annotation for registering a factory for
ServiceCand by specifying to use the
factoryBfactory for the
- l10: Annotation for registering a factory for
ServiceC, and by specifying to use the
- l28: The call to the generated class to register all the types.
The following code represents the generated file:
You can set as many register methods as you want. For example, you can have a method per environment and one method for registering everything that is common:
This is very simple and modular, isn’t it?
This library contains two packages you can now download on pub: kiwi, which contains the container and the annotations, and kiwi_generator which is, as indicated by its name, the generator. I hope it will be as useful to you as it is for me.
You’ll find more details in the GitHub repository (like how to configure your project to use the generator).
Feedbacks are welcome 😃. If you find bugs, please issue them in the GitHub issues page.