Dependency Injection In Flutter Using Get_It

Dependency Injection In Flutter Using Get_It

In software engineering, dependency injection is a technique in which an object receives other objects that it depends on. These other objects are called dependencies.

That's according to Wikipedia. In other words, when class A uses functionalities from class B, then it can be said that class A is dependent on class B.

For example, our Api class contains the code necessary to communicate with the joke API used in the app. The network calls made to the joke API is handled by the Client class from the http package.

class Api {
    Client client = Client();
    //...
}

From the above example the Api class is dependent on the Client class and the most basic way to inject this dependency would be to pass it through the constructor

class Api {
    Client client;

    Api({this.client});
    //...
}

This way when you need the Api class somewhere else in your codebase, you would have to pass in the client class

Api api = Api(client: Client());

Now in a situation where we also use dependency injection where the Api class is needed

class HomeScreen extends StatelessWidget {
    Api api;

    HomeScreen({this.api})
}

To Call HomeScreen somewhere in the codebase we would pass in the Api class it depends on and also pass in the Client class that the Api class depends on.

HomeScreen(api: Api(client: Client()));

I'm sure we can see how things can easily get out of hand when we try to access classes with multiple dependencies across multiple places in the codebase.

This is where Get_it comes in. With Get_it we simply register our Dart classes/Objects and the classes they depend on, which can then be easily accessed from anywhere.

So far we've been able to write unit, widget and integration test for a very simple flutter app in the previous articles. And this was possible because we injected our class dependencies through the constructor, that way when we write the test for the class we can mock those dependencies and pass the mocked versions through the constructors. Which allows us to control the behavior of the mocked class.

Getting Started

To get started, simply clone the example project from github here.

git clone https://github.com/o-ifeanyi/test-sample

Add the Get_It dependency to the pubspec.yaml file and run flutter pub get.

dependencies:
  flutter:
    sdk: flutter
  get_it: ^6.0.0
  http: ^0.13.1

Registering the classes

Using get_it, classes can be registered in two ways.

  • Factory: With Factory registration, when you request an instance of the class from GetIt 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 Instance of the class upon registration (registerSingleton) or provide an anonymous function that will be invoked the first time the class is needed (registerLazySingleton). The service locator keeps a single instance of your registered type for the rest of the apps lifetime and will always return you that instance when requested.

Registering classes as LazySingletons helps with performance as the classes are only created when needed unlike registering as Singleton which creates the classes when the app starts.

First create a file named injection.dart under the lib folder. This is where we would register our Dart classes.

Next we would get an instance of GetIt

import 'package:get_it/get_it.dart';

final sl = GetIt.instance; // sl is short for service locator

Next step is to create an initGetIt method which would be called before runApp().

void initGetIt() {
  //...
}

For our joke app the only Dart class we would need to register is the Api class and its dependency (the Client class). To do this we call sl.registerLazySingleton(() {});

sl.registerLazySingleton<Api>(() => Api(client: sl()));

sl.registerLazySingleton<Client>(() => Client());

What's going on is we register a LazySingleton of Type Api which returns an instance of our Api class when its needed. The Api class also depends on the client class and we simply call sl() in its place. This tells the service locator to find a registered instance of Type Client which we've also registered.

The complete injection.dart file

import 'package:example/api.dart';
import 'package:get_it/get_it.dart';
import 'package:http/http.dart';

final sl = GetIt.instance;

void initGetIt() {
  sl.registerLazySingleton<Api>(() => Api(client: sl()));

  sl.registerLazySingleton<Client>(() => Client());
}

Using The Service Locator

To make use of the service locator we have to initialize it by running the initGetIt method before runApp in main.dart

void main() {
  initGetIt();
  runApp(MyApp());
}

And the MyApp class which formerly looked like this

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      ///...
      home: MyHomePage(api: Api(client: http.Client())),
    );
  }
}

Becomes this

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      //...
      home: MyHomePage(api: sl<Api>()),
    );
  }
}

Conclusion

So that's basically it. The more an app grows the more services like Get_it would be required. Especially when following clean architecture and writing test as there would be a lot of dependency injections going on in the codebase.

Typical usage of Get_It includes:

  • Accessing service objects like REST API clients or databases so that they easily can be mocked.
  • Accessing View/AppModels/Managers/BLoCs from Flutter Views

Thanks for reading

For more information on how to use Get_it, see the documentation provided by the Get_it package.

You can also find a video and written tutorial on FilledStack.

You can also check out the project I was building while learning how to use Get_it A twitter clone.

Check out the full code here