Flutter – Dependency injection (DI) is simple with get_it and injectable

Tram Ho

Flutter actually provided us with a solution of DI that is InheritedWidget . However, InheritedWidget has a limitation of having to embed it directly in the UI, so if we declare many dependencies, nesting is inevitable. And again, when you want to get an instance, you must have a context , which is not available anywhere but only in widgets.

From these problems, get_it out as a better, easier to use alternative and separates the dependency declaration from the UI.

Note: This tutorial is for version 4.0.4 (latest up to the time of writing) to help you learn more about get_it , from which the same can be applied to future versions. Go to get_it’s documentation to get advanced and see what’s changed

Setting

Like other pubspec.yaml , you need to add it to the pubspec.yaml file in your project, then run flutter packages get to install it.

Then in the project we will create a new file, I name it injection.dart . In this file let’s create a function so that later on we will register the dependencies in it. The file content is similar to this:

And finally open the main.dart file, call the function we just created before rendering the UI:

Create instances

get_it provides us with almost complete patterns to create the instance:

Factory

Factory is understood as an object factory. Every time you call to get an object, a new instance will be created and returned to you.

We use Factory when we always want to get a new instance every time we use it without regard to the previous instance to avoid reusing the old data started from the previous instance or the old pointer (this is very clear if you used to use redux, you must always return a new object in order to be re-rendered).

The factory should not be used if your object contains complicated logic of code that can slow the initialization process and waste resources by always recreating it whenever needed.

Singleton

Singleton is the opposite of factory , which only creates a single instance since the app started, then if any place is used it will only return the instance created before. So throughout the app, you’ll be using only one instance of that object.

Contrary to factory , should use singleton when you only want to initialize object once and use in many places, avoid wasting resources. Should not be used if it depends too much on the value and pointer, prone to logic errors if not handled app carefully.

Lazy-singleton

Lazy-singleton is like a singleton , except that it will be initialized on the first instance call , not when the app starts. Use it if creating this instance takes time, you don’t want the app to stay at the splash screen for too long to wait for instance creation, resulting in poor app UX.

Also, if you think that this object in any case can not be used, you can also use this way to avoid wasting resources.

For example, when the network is lost, the user will not need to call api, resulting in no need for a network instance. Only when the user has the network, api is called for the first time, the instance is initialized and used normally

Use

It’s very simple, you just need to use getIt.get<T>() in case the instance you need is synchronous, getIt.getAsync<T>() for the case asynchronous.

Resolve dependency

There are cases where object A needs to provide object B to work (A depends on B), so what do we do?

Factory / singleton A depends on factory / singleton B

Factory / singleton A depends on asynchronous factory / singleton B

Now we have to convert the constructor A to asynchronous, not use synchronous anymore, specifically, we must use:

  • registerFactoryAsync instead of registerFactory
  • registerSingletonAsync instead of registerSingleton

Asynchronous factory / singleton A depends on factory / singleton B

This part is quite similar to Factory/singleton A phụ thuộc vào factory/singleton B

Asynchronous factory / singleton A depends on asynchronous factory / singleton B

This part is also quite similar to Factory/singleton A phụ thuộc vào asynchronous factory/singleton B

Pass the params to the factory

In some cases, you want to pass parameters to the constructor when initializing the object, for example User(age: 12, name: 'Kevin') , get_it also allows you to pass a param with a maximum of 2 params.

If you want to pass more than 2 params, you can create a class that represents the params and pass it as a normal param:

Automatically register dependencies with injectable

I have coded Java Spring and found its inject dependency mechanism is quite good, just add the annotation on top of the class that needs to be injected and it will automatically find and inject myself, not declare as above.

Fortunately, with build_runner and injectable , this tedious task can be fully automated.

Setting

First we need to add pubspec.yaml , run flutter packages get to install it.

Now open the injection.dart file, edit it as follows:

You will see an error at import 'injection.config.dart'; and $initGetIt . Don’t worry, open a terminal, go to the project and run the following command:

flutter packages pub run build_runner build

After the terminal finishes running, you will see a new file called injection.g.dart created by injectable , located at the same location as injection.dart and the above error is gone. So we have the setup done.

Use

Now instead of writing everything in configureDependencies() , forget it for now and pass the object you need to initialize.

Assuming I have 2 classes A and B, I want to add it to DI as a factory, A depends on B, then I just need to import and add the @injectable annotation above those two classes:

Run the flutter packages pub run build_runner build command again and open the injection.config.dart file to see if it looks similar to this, then we are successful.

During the coding process, we can replace the flutter packages pub run build_runner build with flutter packages pub run build_runner watch , and just save the file and injectable will rebuild the file for you.

Of course, outside the factory, we can also use singleton and lazy-singleton with the @singleton and @lazySingleton annotations.

With asynchronous factory you can use @injectable on class and @factoryMethod on constructor like so:

injectable also helps us to separate dependencies according to different environments to use, easily write unit tests, mock data, … For example, you can create your own DevRepository with dev url, separate config compared to StgRepository or ProdRepository to avoid TestRepository the wrong environment, or TestRepository includes sample data to facilitate TestRepository , unit testing.

Since this article is quite long, this feature and other advanced features, please refer to the injectable document to learn more, very good.

Conclude

For medium and large projects Dependency Injection is indispensable to ensure the maintainability and scalable of the project. There are many ways to apply DI, get_it and injectable personally I find one of the best DI support tools for Flutter / Dart up to now.

How about you? Which tool are you using? Please share with everyone to expand your knowledge

Share the news now

Source : Viblo