Flutter fast dependency injection!
Plus some help separating business logic and making your code a little simpler…
How will this help?
It’s fast to add, easy to use, and comes with extras
Dependency Injection solutions handle some of the big jobs in our Apps, and they typically have a few things in common:
- First, they instantiate a single version of a service or controller that can be shared throughout our code
- Second, they require a mechanism to locate or find that service or controller from anywhere within our code.
For example, an AuthService might be needed in the Login, Signup, and Profile pages of our App. To enable Dependency Injection, we would typically need to create our AuthService before any of those pages need it, and then those pages will use some kind of locator or finder to access methods like getuser()
on the AuthService.
Our Dependency Injection solution skips the need to write that locator/finder and throws in some additional benefits that might also speed up your App development.
What do I need?
Not too much actually
We’re going to use a package called Get. Why this particular package? According to the package author :
“Get is a microframework for Flutter”
Because it’s a framework it can do much more than just Dependency injection, and some of our speed gains will come using other features on the framework. Plus it’s really easy to use, and according to the documentation it will grant us superpowers, so that’s a plus.
Start the timer
STEP 1 — add Get into pubspec.yaml and import it into our App.
STEP 2 — change our MaterialApp
to GetMaterialApp
.
The Get microframework is now available for us.
STEP 3 —add our services.
We’re going to try out Dependency Injection on two services. First we’ll instantiate the TestService immediately.
Second, we’ll do a lazy instantiation on AuthService which means the service won’t be created until it’s needed. It’s generally considered good form to use this method unless you need access to your service immediately.
Either way is fine, and here’s what that looks like.
STEP 4 — we inject our services.
Just as there are two ways to instantiate a service for Injection, Get thas two ways to Inject a Dependency:
- The first uses a static method at the start of the class to be injected — we’ll use this one to inject TestService
- The second requires a type declaration<> to qualify the injection — we’ll use this one to inject AuthService.
I find the first method easier to read but the performance is identical.
And we’re done
We now have a fully functioning Dependency Injection solution with two injected services that present their output through a SnackBar
in 65 lines!
Here’s a look at a snippet of our code.
Abstract Classes and Testing
Although I’ve use concrete classes in my example, Get also supports abstract classes, perfect for testing or interfacing your code.
Here’s an abstract MyService
class extended with our Implementation code:
Get.lazyPut<MyService>(() => MyServiceImpl());
And here’s the same abstract service extended with a Mock testing class:
Get.lazyPut<MyService>(() => MyServiceMock());
Either way, our call to MyService
is the same:
MyService.to.multiply(2, 3);
I’ve included a gist of the example here, thanks to Stefan de Vogelaere for his help and example.
What else can I do?
A few added bonuses come with our DI solution
Let’s do a few things to make our code a little simpler to write and read.
Bonus 1 —Help separate our Business Logic
Notice I said help. This isn’t a state management solution (although Get has two), it’s more to help you with some of the things you may want to do from inside of your Business Logic classes.
Say for instance our Business Logic TestService needs to present a DialogBox
, a SnackBar
, or a BottomSheet
? Let’s try it out with that SnackBar
we used earlier.
We’ll remove the Builder
widget and the following showSnackBar
line from the StatefulWidget
in our TestPage :
Scaffold.of(context).showSnackBar(SnackBar(content: Text(result))
After that we simply add the following line to our TestService:
Get.snackBar("TestService", result)
Did you notice we did that without referencingcontext
?
We could also have done that from our StatefulWidget
if we didn’t want to move the SnackBar
to our Business Logic. It’s still much cleaner than the original code and doesn’t need context
or the Builder
widget above it.
This is a great enabler for the separation of concerns in architectures like MVVM, BLoC, and MVC.
Bonus 2— Simplify that syntax
Let’s be honest, Flutter can be a little wordy at times, so anything that can simplify those calls to get a value from theme
or context
is good.
Let’s say we want to get the height of a page. That would be:
MediaQuery.of(context).size.height;
With Get, that becomes:
Get.height;
Now let’s see if the dark theme is active with:
MediaQuery.of(context).platformBrightness == Brightness.dark
? true : false;
And with Get:
Get.isDarkMode;
It’s cleaner and faster to write. These were just a few of the convenience wrappers Get provides.
Here’s part of our updated code. As you can see our SnackBar
looks a little different, and it’s also quite configurable.
So, we’ve created a fully functional App to demonstrate Dependency Injection, Separation of Concerns, and a little code cleaning and we’re still less than 100 lines!
You can find a gist of our code here.
How is this possible?
Please explain
When we changed from MaterialApp
to GetMaterialApp
we gave Get stewardship over context
and the master widget in our App. From then on we could just call Get from anywhere in our code to make things happen.
When we used Get.put
to create our TestService and Get.lazyPut
on our AuthService we were actually instantiating them inside of the Get Instance. Once GetMaterialApp
was added those services were available anywhere in our app.
Things like Get.length
are simply convenience wrappers around the standard Flutter methods that we access through Get.
Get.snackbar
works the same way, but offers a more configurable and visually appealing solution than its standard Flutter counterpart.
If you want a slightly better understanding of how to add Get to your project, or you’re interested in managing themes, have a look here.
So how did we do?
Added Dependency Injection with just a few lines of code? Yep.
Found a simple way to remove some of that Business Logic from our Flutter pages? Yep.
Made our code a little easier on the eyes and a little clearer to write? Yep.
Enjoy.