Dane Mackier
Jun 11 · 7 min read

In this tutorial, we will cover the three forms of the accepted dependency injection in Flutter. We’ll look at , and to get our objects where they are required. Before we do that let's define what dependency injection is, in plain English.

Full Video with written below. Originally posted here

Definition

Dependency injection is writing code that supplies your objects with other objects that they depend on. Let’s look at an example of what a dependency is.

Above you can see that the depends on the object. The is dependent on the . The usual way to get to a dependency into a class is through the constructor.

The same idea here can be applied to a Widget. Let’s look at this example in terms of a widget.

An easy way to supply this dependency is by passing it through the constructor.

So why all the fuss about dependency injection if we can just use the constructor?

Motivation

Passing dependencies through a constructor is perfectly fine for accessing data one level down, maybe even two. What if you’re four levels deep in the widget tree and you suddenly need the data from an object in your code? Imagine these are all widgets in separate files with their own logic.

HomeView

  • MyCustomList
  • PostItem
  • PostMenu
  • PostActions
  • — LikeButton

If you want to pass the AppInfo to the LikeButton to display the like count, you would have to create and add the constructor and keep the local member variable just to pass it down to the next widget constructor. Now, what if you swap the LikeButton to be on a different parent widget. You’d have to remove all the unnecessary code and then add it to all the new parents of the widgets that require your data. Not a very pleasant development experience.

Instead of doing things the hard way, we’ll use a technique called Dependency Injection. We’ll make sure that if data is required, anywhere in the widget tree, by any widget, that we’ll be able to retrieve it easily. We’ll start with the method that Flutter is “built on” called InheritedWidget.

Inherited Widget

In simple terms an inherited widget effectively allows you to provide access, through the , to all it's properties, to every widget in its subtree. It's common in Flutter and is used for the Theme, MediaQueries and everything else the base app provides. This is how an empty looks.

This can be generated through the snippets in VS by typing and pressing enter/tab on the option. We'll add our AppInfo as a property on the Widget. We'll keep a final instance that's long-running and return that instance through a getter.

Usage

The way inherited widgets are used is by wrapping the tree you want in the inherited widget. we want this widget to be supplied to our entire app so we’ll wrap the MaterialApp with the InheritedInjection widget.

The way we access the inherited widget in our code is by using the .of call and passing the context. We can now update our HomeView and remove the AppInfo passed through the constructor as well as the class variable we kept. We can now make our HomeView look like this.

Now anywhere in the app where you want to use your object all you'll do is.

No need to pass anything down through 5 constructors just to access the data. Let's move onto a more traditional form of dependency injection. .

Pros

  • This is how everything in Flutter is built.
  • Forces one-directional data flow.

Cons

  • Boilerplate around instance tracking. Meaning when you want a new instance every time you request the type you have to set that up. The same with if you want a singleton.
  • Injecting into objects where the context is not available is almost impossible. You have to clutter up the InheritedWidget itself with all the setup and manually inject objects where the context is not available.
  • Very verbose

get_it

Get it is what is known as a simple service locator. Traditionally you register your types against an interface and provide the concrete implementation to it. This way you benefit from developing against an interface which also makes unit testing easier because you can provide test specific implementations. Today we won’t be doing that. This is just about dependency injection. Let’s get to the code.

Add get_it to the pubspec

Then we’ll set up our service locator. We’ll create a file in the root, next to main.dart called locator.dart. Inside it, we’ll keep an instance of globally that we can access wherever we import the file. We'll also create a function called setupLocator where we will register all of our types we want access to.

The types have to be registered before the App starts. So in the the file we'll call setupLocator before the app is kicked off.

Now we can register our AppInfo. Using get_it, class types can be registered in two ways.

Factory: When you request an instance of the type from the service provider you’ll get a new instance every time. Good for registering ViewModels that need to run the same logic on start or that has to be new when the view is opened.

Singleton: Singletons can be registered in two ways. Provide an implementation upon registration or provide a lamda that will be invoked the first time your instance is requested (LazySingleton). The Locator keeps a single instance of your registered type and will always return you that instance.

We’ll register our AppInfo as a singleton so that there’s only ever one instance of it while the app is running.

This completes all the setup for it. Now we can remove them widget from the main file.

In the HomeView, we can get the from our locator.

Pros

  • Can request type anywhere using the global locator
  • Instance tracking is automatically taken care of by registering types as Factories or Singleton
  • Can register types against interfaces and abstract your architecture from the implementation details
  • Compact setup code with minimal boilerplate

Cons

  • Disposing is not a top priority in the framework
  • Loose coding guidelines that can lead to badly written software
  • Global object usage which is the start of multi-directional data flow which is the opposite of what Flutter promotes

The last method is using a package called Provider.

Provider

Provider is basically InheritedWidget on steroids. It has specialised provider types like StreamProvider, ChangeNotifierProvider, ListenableProvider that can be used to architect your entire app. Today we’re just looking at how it can be used for dependency injection into widgets.

First, we have to add it to the pubspec

Then similarly to the InheritedWidget, the Provider injected value is only available in its subtree. To get the value everywhere We will wrap our entire app in a provider.

Now in the HomeView when you want to access the value from the provider all you have to do it.

Easy peasy.

Pros

  • Great for StateManagement, my number one choice
  • Forces one-directional data flow
  • The SpecialtyProviders removes loooots of boilerplate code
  • Using the Consumers for your providers is neat and clean
  • Supplies a way to dispose of all provider objects

Cons

  • Injecting into objects that don’t have a BuildContext requires lots of ProxyProvider stringing

To see an in-depth article on how to combine these methods for the most maintainable architecture using provider for state management look at this article.

Flutter Community

Articles and Stories from the Flutter Community

Dane Mackier

Written by

A full stack software developer focused on building mobile products, its tools and architecture. Always reducing boiler plate code and experimenting.

Flutter Community

Articles and Stories from the Flutter Community