Instagram Clone Clean Architecture – Part 9

We’re almost done with our app user methods, and app architecture. We got small pieces of code in out app which is separated according to the principle of the Clean Architecture. Now before going forward and utilize them in UI we must notice one thing. We have many classes and are decoupled from its dependencies, and accepting these dependencies through the constructor. But we somehow, have to pass in them.

Instagram Clone Clean Architecture Course

course - Instagram Clone Clean Architecture

This is just single part of this series. Checkout all the other parts here and learn to build clean and maintainable apps.

Dependency Injection:

Almost, every class has some dependencies, if look at the FirebaseRepositoryImpl and also FirebaseRemoteDataSourceImpl. There are multiple service locators but we will use get_it in this series, this is a simple service locator for Dart and Flutter, and can be used instead of InheritedWidget and Provider.

So far we’ve created many classes, we will go over all of them, from top Presentation Logic Holders (Cubits) to the downward Remote Data Sources.

Let’s create an initial setup in a new file and name it injection_container. This file will be located in the lib directory. Basic structure of our file will look something like this.

injection_container.dart

				
					import 'package:get_it/get_it.dart';

final sl = GetIt.instance;

Future<void> init() async {
  // Cubits

  // Use Cases

  // Repository

  // Remote Data Source

  // Externals
}

				
			

This init() method will be called when our app starts from main.dart inside this init() all the classes and contracts will be registered and subsequently also injected using the singleton instance of GetIt stored inside the sl (which is shortened of service locator).

 

The get_it package provide creating singletons and factory instances and many other methods. In this article we’re only going to touch the registerFactory, registerSingleton and registerLazySingleton. As you can see the first comment Cubits, first we’re going to register Cubits.

Register Factory:

Factory registration provide new instance of the class after each call to it. And this is a good way of registering View Models or in our case Cubits. The process of registration is pretty much simple. GetIt class has call() method, very similar to our use cases have call() method.

				
					// Cubits
sl.registerFactory(
      () => AuthCubit(
    signOutUseCase: sl.call(),
    isSignInUseCase: sl.call(),
    getCurrentUidUseCase: sl.call(),
  ),
);

				
			

By using type inference, the sl.call() will determine which object should pass in the constructor argument. You are right! This can become possible when the type is also registered. It’s clear now we also should register the use cases.

Note: Presentation logic holders should not be registered as singletons. Because your app do have multiple pages like our app Instagram have multiple pages, when we navigate between pages, we probably want to do some clean up like closing streams. Like when we navigate from one page to the next we closed one opened stream and when we back to the same page we want new instance of Cubit to rebuild it.

In case if we use singletons and navigate between pages, we will be using closed streams when we back to the same page instead of creating the new instance with opened Stream.

Because the get_it singletons create one instance, cache it and use it throughout the lifetime of the app. It do not create new instance.

Finally by registering all the Cubits injection_container.dart will looks something like this:

				
					// Cubits
sl.registerFactory(
  () => AuthCubit(
    signOutUseCase: sl.call(),
    isSignInUseCase: sl.call(),
    getCurrentUidUseCase: sl.call(),
  ),
);

sl.registerFactory(
      () => CredentialCubit(
        signUpUseCase: sl.call(),
        signInUserUseCase: sl.call(),
  ),
);

sl.registerFactory(
      () => UserCubit(
        updateUserUseCase: sl.call(),
        getUsersUseCase: sl.call()
  ),
);

				
			

Register Singletons:

As mentioned earlier we’ve two kind of singletons. That are registerSingleton and registerLazySingleton. The difference between them is the regular non-lazy singleton is registered immediately after the app starts, while the lazy singleton is only registered when it’s requested as a dependency for some other class.

Cubit Dependencies:

In our app we will use registerLazySingleton to register our Cubit’s dependencies which are Use Cases. These lazy singletons will create only one instance and will use it throughout lifetime of the app. These registrations will go down below the Use Cases comment.

 

After registration of all the Use Cases our injection container will look like this:

				
					// Use Cases
sl.registerLazySingleton(() => SignOutUseCase(repository: sl.call()));
sl.registerLazySingleton(() => IsSignInUseCase(repository: sl.call()));
sl.registerLazySingleton(() => GetCurrentUidUseCase(repository: sl.call()));
sl.registerLazySingleton(() => SignUpUseCase(repository: sl.call()));
sl.registerLazySingleton(() => SignInUserUseCase(repository: sl.call()));
sl.registerLazySingleton(() => UpdateUserUseCase(repository: sl.call()));
sl.registerLazySingleton(() => GetUsersUseCase(repository: sl.call()));
sl.registerLazySingleton(() => CreateUserUseCase(repository: sl.call()));
sl.registerLazySingleton(() => GetSingleUserUseCase(repository: sl.call()));

				
			

Register Repository:

To register the Repository we will again use registerLazySingleton, and if you notice this FirebaseRepository is an abstract class or a contract and cannot be instantiated but instead we will instantiate its Implementation of repository. This can become possible by specifying the type parameter on the registerLazySingleton method.

				
					// Repository
sl.registerLazySingleton<FirebaseRepository>(() => FirebaseRepositoryImpl(remoteDataSource: sl.call()));

				
			

Register Data Sources:

As the FirebaseRepositoryImpl has dependency remoteDataSource we also should register that type.

				
					// Remote Data Source
sl.registerLazySingleton<FirebaseRemoteDataSource>(() => FirebaseRemoteDataSourceImpl(firebaseFirestore: sl.call(), firebaseAuth: sl.call()));

				
			

After registration of Firebase Repository and Remote Data Source the code will look like:

				
					// Repository

sl.registerLazySingleton<FirebaseRepository>(() => FirebaseRepositoryImpl(remoteDataSource: sl.call()));

// Remote Data Source
sl.registerLazySingleton<FirebaseRemoteDataSource>(() => FirebaseRemoteDataSourceImpl(firebaseFirestore: sl.call(), firebaseAuth: sl.call()));

				
			

External Dependencies:

We’ve come all the way down from Presentation Logic Holders (Cubits) to the 3rd party libraries. Now we need to register the FirebaseFirestore and FirebaseAuth also. This last one will be a bit different from all of the registration above

First you need to take the instance of them and then register as lazy singleton.

				
					// Externals

final firebaseFirestore = FirebaseFirestore.instance;
final firebaseAuth = FirebaseAuth.instance;

sl.registerLazySingleton(() => firebaseFirestore);
sl.registerLazySingleton(() => firebaseAuth);

				
			

Finishing up:

Go to > main.dart and initialize that init() method same as we did for firebase.

main.dart

				
					import 'injection_container.dart' as di;

Future main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  await di.init();
  runApp(MyApp());
}

				
			

Solution Code:

				
					import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:get_it/get_it.dart';
import 'package:instagram_clone_app/features/data/data_sources/remote_data_source/remote_data_source.dart';
import 'package:instagram_clone_app/features/data/data_sources/remote_data_source/remote_data_source_impl.dart';
import 'package:instagram_clone_app/features/data/repository/firebase_repository_impl.dart';
import 'package:instagram_clone_app/features/domain/repository/firebase_repository.dart';
import 'package:instagram_clone_app/features/domain/usecases/firebase_usecases/user/create_user_usecase.dart';
import 'package:instagram_clone_app/features/domain/usecases/firebase_usecases/user/get_current_uid_usecase.dart';
import 'package:instagram_clone_app/features/domain/usecases/firebase_usecases/user/get_single_user_usecase.dart';
import 'package:instagram_clone_app/features/domain/usecases/firebase_usecases/user/get_users_usecase.dart';
import 'package:instagram_clone_app/features/domain/usecases/firebase_usecases/user/is_sign_in_usecase.dart';
import 'package:instagram_clone_app/features/domain/usecases/firebase_usecases/user/sign_in_user_usecase.dart';
import 'package:instagram_clone_app/features/domain/usecases/firebase_usecases/user/sign_out_usecase.dart';
import 'package:instagram_clone_app/features/domain/usecases/firebase_usecases/user/sign_up_user_usecase.dart';
import 'package:instagram_clone_app/features/domain/usecases/firebase_usecases/user/update_user_usecase.dart';
import 'package:instagram_clone_app/features/presentation/cubit/auth/auth_cubit.dart';
import 'package:instagram_clone_app/features/presentation/cubit/credentail/credential_cubit.dart';
import 'package:instagram_clone_app/features/presentation/cubit/user/get_single_user/get_single_user_cubit.dart';
import 'package:instagram_clone_app/features/presentation/cubit/user/user_cubit.dart';

final sl = GetIt.instance;

Future<void> init() async {
  // Cubits
  sl.registerFactory(
    () => AuthCubit(
      signOutUseCase: sl.call(),
      isSignInUseCase: sl.call(),
      getCurrentUidUseCase: sl.call(),
    ),
  );

  sl.registerFactory(
        () => CredentialCubit(
          signUpUseCase: sl.call(),
          signInUserUseCase: sl.call(),
    ),
  );

  sl.registerFactory(
        () => UserCubit(
          updateUserUseCase: sl.call(),
          getUsersUseCase: sl.call()
    ),
  );

  sl.registerFactory(
        () => GetSingleUserCubit(
        getSingleUserUseCase: sl.call()
    ),
  );

  // Use Cases
  sl.registerLazySingleton(() => SignOutUseCase(repository: sl.call()));
  sl.registerLazySingleton(() => IsSignInUseCase(repository: sl.call()));
  sl.registerLazySingleton(() => GetCurrentUidUseCase(repository: sl.call()));
  sl.registerLazySingleton(() => SignUpUseCase(repository: sl.call()));
  sl.registerLazySingleton(() => SignInUserUseCase(repository: sl.call()));
  sl.registerLazySingleton(() => UpdateUserUseCase(repository: sl.call()));
  sl.registerLazySingleton(() => GetUsersUseCase(repository: sl.call()));
  sl.registerLazySingleton(() => CreateUserUseCase(repository: sl.call()));
  sl.registerLazySingleton(() => GetSingleUserUseCase(repository: sl.call()));
  // Repository

  sl.registerLazySingleton<FirebaseRepository>(() => FirebaseRepositoryImpl(remoteDataSource: sl.call()));

  // Remote Data Source
  sl.registerLazySingleton<FirebaseRemoteDataSource>(() => FirebaseRemoteDataSourceImpl(firebaseFirestore: sl.call(), firebaseAuth: sl.call()));


  // Externals

  final firebaseFirestore = FirebaseFirestore.instance;
  final firebaseAuth = FirebaseAuth.instance;

  sl.registerLazySingleton(() => firebaseFirestore);
  sl.registerLazySingleton(() => firebaseAuth);
}

				
			

main.dart

				
					Future main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  await di.init();
  runApp(MyApp());
}

				
			

Conclusion:

Dependency injection is a programming technique that makes a class independent of its dependencies. It achieves that by decoupling the usage of an object from its creation. In the next article we’ll finally go for calling the methods in our UI and will see all the code we’ve done so far in action. In order to not miss the upcoming video be sure to subscribe to eTechViral and hit the bell Icon to make sure you get notified whenever new video is uploaded.

Website:
Have any Questions? Find me on

Leave a Reply

You may like