The Flutter GetX Ecosystem ~ Dependency Injection

Aachman Garg
Flutter Community
Published in
6 min readOct 5, 2020

When it comes to developing production level applications, it’s important to adopt the best software engineering practices to achieve robustness and stability. One such practice is Dependency Injection.

Dependency Injection is the technique of injecting instances of one class into another. We can place different variables and methods in different classes and have them depend on one another, ultimately organizing the code at a greater level.

It makes testing a breeze and also provides a way to create shared instances, which plays a major part in state management.

A basic approach to inject dependencies in Flutter is through constructors.

Now this may seem fine, until you realize that there are way too many widgets to pass down the dependency, while half of them not even using it. At this point we need a more viable solution.

GetX Approach

Not sure what GetX is? Check this out!

The GetX approach is pretty simple. Instead of creating an instance directly, we wrap it with an instance of Get (class), something like this:

Then, we can inject it in any class, like this:

Get.put makes the dependency available to all the child routes. So, in case we need to access the same instance in some other class, we can do that using Get.find.

As simple as that! Get.find will always find the right instance no matter what.

Bindings

While the above approach is neat, we are still declaring our dependencies in the view class. For organizing the code even further, we can (and should) separate them from the view using Bindings.

Bindings are classes where we can declare our dependencies and then ‘bind’ them to the routes. However, this means that we can only use it when using GetX for route management. But that shouldn’t be a problem right? After all, we are in the GetX ecosystem ;)

We start by creating a class that implements Bindings class.

We need to override the dependencies() method, where we’ll insert all our dependencies.

Next, we can ‘bind’ these dependencies to our routes.

GetPage, Get.to and Get.toNamed are parts of route management, so don't overthink it, we'll cover that later.

We can also set a Binding to create the dependencies as soon as the app starts, by declaring it as initialBinding.

Too many steps? GetX also provides BindingsBuilder that lets us use bindings without creating a separate class.

Now to use access the dependencies, we can simply use Get.find.

You might be wondering if Bindings are an overkill. They are! But let’s say you have 10 controllers to be injected as dependencies. Declaring them in view may not look very neat. In this case Bindings are the way to go.

Different Cases, Different Methods

Get.put

Get.put covers most of the use cases for dependency management. When we inject a dependency using Get.put, it calls Get.find internally, which means, the dependency loads immediately, and can be used directly.

There are times when multiple shared instances need to be updated individually. For that, we have the tag property.

Now we have different shared instances of the same Controller class.

By default, the dependencies will be deleted if the route using Get.put is removed from the navigation stack. You may want to prevent this and keep the dependencies in memory for the entire app session. You can do that using permanent property.

Get.lazyPut

As the name suggests, it lazy loads the dependencies, which basically means that the dependencies will be created immediately, but will be loaded to memory only when Get.find is called for the first time. Super useful if you have all your dependencies in the initialBinding, as they get loaded only when they're actually used by the widgets. As it returns void, it should preferably be declared in a Binding.

Normally, Get.lazyPut loads dependencies only one time, which means that if the route gets removed, and created again, Get.lazyPut will not load them again. This default behavior might be preferable in some cases while for others, we have the fenix property.

It will still delete the dependencies, but it will be able to re-initialize them, when the route is back in stack.

Get.putAsync

When using SharedPreferences or databases, we need to work with dependencies asynchronously. We can use Get.putAsync for that.

Get.create

This one is for those rare cases which you may never come across, but when you do, you’ll realize how useful this can be. Unlike all other methods, this one creates a new instance of the dependency every time Get.find is called.

So when should we use it? When we have multiple widgets on a page, that depend on the same controller, but need to update individually. Example? A shopping cart of course!

infinite instances, one controller!

Let’s take a look at the controller first:

Yep! That simple it is.

Next, we inject the dependencies.

We’ll create a list item, which uses two kinds of instances of ShoppingController, one injected by Get.create, another by Get.put.

controller is assigned an instance of Get.find, so we're calling Get.find only once. This means that all 3 widgets are accessing the same instance of ShoppingController.

When we use this widget in a ListView, Get.find is called for each item, which creates separate instances for each of them. This lets us update the quantity for each item individually, without creating separate controllers.

ShoppingController injected by Get.put is used to update and maintain a total as it is shared by all the list items, and across pages as well.

You may find Get.create tricky to work with, and may never use it. But if you do run into a similar use case, remember that you have a handy option.

We do get the ability to create multiple shared instances with Get.create using the tag parameter. It also has thepermanent property, but unlike others, it's true by default.

GetView

It’s a stateless widget that has a controller getter, simple as that. If we have a single controller as a dependency, we can use GetView instead of StatelessWidget and avoid writing Get.find. Cool right?

GetWidget

It’s very similar to GetView with one tiny difference — it gives us the same instance of Get.find every time. This becomes surprisingly useful when used in combination with Get.create, as we can have multiple widgets interact with the same instance of a dependency.

Configuration

GetX provides something called SmartManagement that lets us configure how dependencies behave in terms of memory management.

Full Mode

Everything gets disposed as soon as the route is removed from navigation stack, unless declared permanent.

Keep Factory Mode

Normally, Get.lazyPut initializes dependency only once. Once the dependency is disposed, it won't get initialized again. In keepFactory mode, its possible to initialize deleted dependencies multiple times by Get.lazyPut.

This behavior is same as using Bindings. So, this mode is useful only if you’re using Get.lazyPut without Bindings or as an initialBinding.

Only Builder Mode

It keeps all kinds of dependencies in memory until the app is closed, except those mentioned in init:, like in GetX and GetBuilder, or initialized using Get.lazyPut in a Binding.

Done!

That’s all there is to Dependency Injection using GetX. Flutter community does offer some really good packages for the same, but I think the ease and level of control offered by GetX makes it something worth giving a shot.

What’s next?

The Flutter GetX Ecosystem ~ Route Management

— Coming soon!

Here’s the link to GetX package: https://pub.dev/packages/get

Read Japanese translation by inari_sushio

Check out my previous work!

Any questions or suggestions? Put them all in the comments section :D

Liked the article? Press and hold on the 👏 button! That’ll motivate me to write more and more!

You can find me on LinkedIn, stalk my GitHub, follow me on Twitter or email me at imaachman@gmail.com for any kind of tech discussion.

https://www.twitter.com/FlutterComm

--

--