[Flutter] : TDD - 12. Dependency Injection


이제 Number Trivia App의 모든 조각을 완성하였다. UI와 연결하면서 이 조각들을 연결 할 일만 남았다.

모든 조각들은 생성자를 통해서 의존성 주입을 하도록 디커플링을 시켜놓았다. 문제는 어떻게 넘겨 줄 것이냐이다.

여기서 Get_It 을 사용하도록 하자.

가장 root 폴더인 lib 폴더에 injection_container.dart 파일을 만들자.

injection_container.dart

final sl = GetIt.instance;

void init() {
  //! Features - Number Trivia

  //! Core

  //! External

}

1. Registering a Factory


//! Features - Number Trivia
//Bloc
sl.registerFactory(
  () => NumberTriviaBloc(
    concrete: sl(),
    random: sl(),
    inputConverter: sl(),
  ),
); 

registerFactory를 통해 NumberTriviaBloc을 생성해주자.

Parameter는 sl의 call() method를 이용하도록 하자.

여기서 Bloc은 절대 signleton으로 생성하면 안된다. UI와 연결된 Bloc은 UI에 따라 dispose될 수도 있기 때문이다.

2. Registering Singletons


GetIt에는 registerSingletonregisterLazySingleton 2가지 선택지가 있다.

이름 그대로 registerSingleton 는 바로 생성해서 등록해주는 함수이며

registerLazySingleton 는 호출 시점에 생성해서 등록해주는 함수이다.

NumberTrivia는 간단한 앱이라서 Bloc이 1개뿐이다. 즉 모두 즉시 생성된다. 하지만 여기서 우리는 registerLazySingleton 으로 작성하도록 하겠다.

Bloc Dependencies


//! Features - Number Trivia
...
// Use cases
sl.registerLazySingleton(() => GetConcreteNumberTrivia(sl()));
sl.registerLazySingleton(() => GetRandomNumberTrivia(sl()));

//! Core
sl.registerLazySingleton(() => InputConverter());

Repository Registration


여기서 우리가 주의할 점은 Usecase에서 Repository를 호출 할 때

NumberTriviaRepositoryImpl이 아니라 NumberTriviaRepository 즉 abstract class라는 점이다.

하지만 실제 생성은 NumberTriviaRepositoryImpl로 해주어야 한다.

//! Features - Number Trivia
...
// Repository
sl.registerLazySingleton<NumberTriviaRepository>(
  () => NumberTriviaRepositoryImpl(
    remoteDataSource: sl(),
    localDataSource: sl(),
    networkInfo: sl(),
  ),
);

이렇게 느슨한 결합을 통해 얻는 장점은 다음과 같다.

  1. 테스트가 가능해진다.
  2. 구현부를 교체할 때 더 용이하다.

Data Sources & NetworkInfo


여기서도 느슨한 결합임을 기억하자.

//! Features - Number Trivia
...
// Data sources
sl.registerLazySingleton<NumberTriviaRemoteDataSource>(
  () => NumberTriviaRemoteDataSourceImpl(client: sl()),
);

sl.registerLazySingleton<NumberTriviaLocalDataSource>(
  () => NumberTriviaLocalDataSourceImpl(sharedPreferences: sl()),
);

//! Core
...
sl.registerLazySingleton<NetworkInfo>(() => NetworkInfoImpl(sl()));

External Dependencies


이 앱에서 우리는 http.client , DataConnectionChecker , SharedPreferences 3가지 외부 라이브러리를 사용했다. 여기서 SharedPreferences만 주의하자.

SharedPreferences는 async로 instance를 만들 때

final sharedPreferences = await SharedPreferences.getInstance();

이렇게 생성해야 한다.

그래서 아래와 같이 작성하는 경우가 많은데 아래의 경우를 확인해보자.

sl.registerLazySingleton(() async => await SharedPreferences.getInstance());

위 코드의 문제점은 상위 함수에서 봤을 때 Future로 return값이 나오게 된다.

하지만 우리가 원하는 건 Future가 아닌 내용물이다.

아래와 같이 작성 해주자.

Future<void> init() async {
  ...
  //! External
  final sharedPreferences = await SharedPreferences.getInstance();
  sl.registerLazySingleton(() => sharedPreferences);
  sl.registerLazySingleton(() => http.Client());
  sl.registerLazySingleton(() => DataConnectionChecker());
}

2. 초기 설정


자 이제 기본적인 설정은 끝났다.

이제 get_it을 main.dart에서 호출 해주자.

main.dart

import 'injection_container.dart' as di;

void main() async {
  await di.init();
  runApp(MyApp());
}

여기서 중요한 점은 await다. di.init()의 결과 값이 void라고 하더라도 기다려 줘야 한다.

다 끝났다.

이제 UI를 만들어보자.