Dependency injection in Flutter done the right way

Sebastian Waloszek
Deviniti Technology-Driven Blog
4 min readJun 30, 2020

Whether to use dependency injection in your Flutter project is not such a hard question to answer. You should. That’s it. But what is dependency injection anyways? Well… let me explain with a simple example of what you should always do in your code.

Let’s say we have a user repository class that manages users in our app. It uses 2 other classes:

  • UserDataSource — a remote user data source for communicating with the backend used for user authentication.
  • SecureStorage — a secure storage class that manages saving and retrieving things from storage.

So let’s define our user repository class, shall we? Here it comes:

Let me just tell you this: “This is a big no-no. Bad programmer!” The class depends on two services which “are trapped inside it with no perspective of ever escaping”. That means no dependency injection for you. So how should it look like? Actually something more akin to this:

Now we’re talking! Now while defining the user repository we can decide which service instances it will use bypassing (injecting) them through the constructor.

So that’s basically the idea. Dependency injection is a technique that lets objects receive other objects that it depends on. It can be realized through different methods, but using constructors to do this turns out the be the most robust option available.

Which DI package to choose?

There are many options available, but for our case, we decided upon using the kiwi package. In my experience, it’s the most intuitive one and works great for our use cases. Therefore, we will use kiwi for any further examples. But feel free to test out any other packages. The basic principles should be the same.

Usage

The package’s pub page contains extensive instructions on how to use the package. I on the other hand would like to share with you an example of a custom wrapper used to manage our dependency injections. Together with different environment specifications, this enables us to define different app flavors. We can define different object instances for both development and production environment e.g using a mock data interceptor for development and real data for production.

Here are some examples of different dependency injections for a clean architecture based app together with the BLOC architecture used for state management.

As you can see we register different injections and the package takes care of deciding which ones will be used based on the type definitions.

When to register instances, factories & singletons

The next topic I want to cover is the differences between registering our dependencies as instances, factories, or singletons. Here are the loose definitions:

  • instance — the dependency is initialized “from the get-go”.
  • singleton — the factory responsible for initializing the dependency will be called only the first time we try to access the dependency. Any further access attempt will always yield the same instance of that dependency.
  • factory — the factory responsible for initializing the dependency will be called every time we try to access the dependency which means that the dependency will be recreated every time.

What are some exemplary use cases for all of them? Well.. generally most of your dependencies will take the form of singletons. If you are using the BLOC architecture and you have an authentication bloc responsible for managing and checking your user’s login status the natural inclination is to make it as a singleton so as to prevent the state from changing just by trying to access it.

In the case of factories, you might have a page BLOC dedicated to a specific page that handles some forms the user can fill out and submit. In this case, we don’t want the state of that page to stay the same and would rather have a new state for each time we try to access it. In this case, we register the dependency as a factory.

Lastly, registering dependencies as instances is appropriate when they represent single variables on which other classes depend upon and don’t have any dependencies of their own.

Accessing dependencies in a BLOC state app

The last concept I’d like to address is the way of accessing different dependencies in a BLOC state app. In a typical BLOC app we could achieve that task by wrapping our widgets inside a BlocProvider and let it do the managing of the dependencies:

But from my experience, it created problems when certain blocs were closed when we don’t want them to. So we decided to just manually handle our dependencies and dispose of them when we need to like this:

We start by initializing our dependency variables in the initState method for each page by calling the resolve() method of the Injector. Then in the case of BLOCs, we pass them onto the relevant BlocBuilders.

Conclusion

That’s all I’d like to share with you about dependency injection in Flutter apps. I hope you found it useful. Thanks for reading! Good luck with implementing your own dependency injection in your Flutter project.

Deviniti is your guide to the universe of digital transformation and enterprise software. Check out who we are and what we can do on our website.

If you want to learn more Flutter tips and tricks, make sure to check out other articles available on Deviniti Technology-Driven Blog:

--

--