4. Dependency Injection

4. Dependency Injection

Hello, Welcome back. Glad that you’re here.

We are building a Movie App with the best coding practices and tools out there. In previous tutorials, Datasources and Repositories & UseCase, we've created many classes and instantiated them directly in main.dart.

In this tutorial, we'll see what is Dependency Injection(DI), why we need DI and how can we introduce DI in the Flutter application.

What is Dependency Injection?

Before we understand DI, let's see what is a Dependency first. In very simple terms, when class A needs class B to perform its operations, then class A is dependent on class B and class B acts as a dependency for class A.

Let's see what is dependency and what is dependent in our code so far. Look at the below code:

ApiClient apiClient = ApiClient(Client());
MovieRemoteDataSource dataSource = MovieRemoteDataSourceImpl(apiClient);
MovieRepository movieRepository = MovieRepositoryImpl(dataSource);
GetTrending getTrending = GetTrending(movieRepository);

Here are the dependencies and dependants:

  1. ApiClient depends on Client
  2. MovieRemoteDataSource depends on ApiClient
  3. MovieRepository depends on MovieRemoteDataSource
  4. GetTrending depends on MovieRepository

For every usecase call, you'll have to instantiate all its dependencies like above. This is overhead and a waste of productive hours for any developer. Not only this is a waste of time, but it might also lead to creating some objects multiple times at multiple places which can lead to consuming more memory.

How good it will be if we can rely on some separate dependency provider to provide us with the correct type of dependency whenever required? This dependency provider will also maintain lazy initializations as well as single instances throughout the application.

There are many plugins in Flutter created by open-source contributors for the same purpose. We'll use the get_it plugin in this series.

GET_IT

Open pubspec.yaml, add the below dependency and run flutter pub get command

get_it: ^4.0.2

In the di folder, create a new file get_it.dart

Import get_it library, and get the static instance of GetIt in a variable:

import 'package:get_it/get_it.dart';

final getItInstance = GetIt.I;

For the rest of the code in the application, we'll now use getItInstance. In our datasource tutorial, we made network calls, so let's start with that first.

Add the below code in the get_it.dart to initialize Client from http:

getItInstance.registerLazySingleton<Client>(() => Client());
  1. <Client> tells GetIt, what type of object to register.
  2. () is the factory function, that returns the type Client
  3. => Client() actually initializes the data source. This is the way, we tell GetIt what to initialize.
  4. registerLazySingleton will initialize the instance of Client when it is first used in the app.

ApiClient depends on Client, so let's add that too in get_it.dart:

getItInstance.registerLazySingleton<ApiClient>(() => ApiClient(getItInstance()));
  1. getInstance() in ApiClient(getItInstance()) resolves the dependency for ApiClient.

As, we've asked GetIt to initialize Client for us, so we rely on getItInstance() to provide Client to ApiClient instance.

MovieRemoteDataSource depends on ApiClient, so let's add that in get_it.dart:

getItInstance.registerLazySingleton<MovieRemoteDataSource>(
  () => MovieRemoteDataSourceImpl(getItInstance()));

Till now, I've shown only the registerLazySingleton() method of GetIt, but there are other methods too. We'll see them in the coming tutorials. Since, the Client, ApiClient, and MovieRemoteDataSource are used throughout the application, so they should have only one instance throughout the application.

Remaining Instances

Let's declare Repository and UseCases. Open get_it.dart and declare:

//1
getItInstance.registerLazySingleton<GetTrending>(() => GetTrending(getItInstance()));
getItInstance.registerLazySingleton<GetPopular>(() => GetPopular(getItInstance()));
getItInstance.registerLazySingleton<GetPlayingNow>(() => GetPlayingNow(getItInstance()));
getItInstance.registerLazySingleton<GetComingSoon>(() => GetComingSoon(getItInstance()));

//2
getItInstance.registerLazySingleton<MovieRepository>(() => MovieRepositoryImpl(getItInstance()));
  1. All UseCases are dependent on MovieRepository, which will be resolved by GetIt.
  2. MovieRepository depends on MovieRemoteDataSource, which will be resolved by GetIt.

We're done with adding all the objects in get_it.dart, let's use them in main.dart. Open main.dart and instead of a lot of initialisations we did in the previous tutorials, this time only use GetTrending from getItInstance now:

//1
import 'package:pedantic/pedantic.dart';
//2
import 'di/get_it.dart' as getIt;
//3
unawaited(getIt.init());
//4
GetTrending getTrending = getItInstance<GetTrending>();
  1. With the help of the Pedantic package, you can use unawaited that will allow the app to not wait for GetIt initialization to happen before launching its first frame.
  2. Import the get_it file that we created.
  3. Use unawaited and call the init() method to initialize GetIt
  4. Give the type of instance you need, like we need <GetTrending> here.

Now run the app. There is no difference in the output. You'll see a list of trending movies on the console.

GetIt injects the dependencies required for us. This was all about using GetIt for Dependency Injection. See you in the next part of the series.

Did you find this article valuable?

Support Prateek Sharma by becoming a sponsor. Any amount is appreciated!