Hilt와 Dagger, JSR-330 (1) - Activity

이태훈·2022년 2월 3일
2

Hilt, Dagger, JSR-330

목록 보기
1/3

안녕하세요, Hilt와 JSR-330의 Annotation에 대해 알아보겠습니다.

먼저, JSR-330은 Java용 Dependency Injection 이며, 주입 가능한 클래스에 사용할 annotation을 표준화합니다.

아래와 같은 인터페이스들이 있습니다.

@Inject, @Name, @Qualifier, @Scope, @Singleton, Provider<*>

Hilt나 Dagger를 사용해보셨던 분들이라면 익숙할 annotation들입니다. 왜냐면, Dagger가 JSR330 기반이고, Hilt는 Dagger 기반이기 때문입니다.

그래서, JSR330와 Dagger 대해 알아두시면 Hilt를 이해하기 조금 더 수월해질 겁니다. 추후에 빌드된 Hilt 코드에서 JSR330, Dagger의 annotation이 나오면 짚어서 설명해드리겠습니다.

각설하여, 이번 시리즈에서 알아볼 것은 Hilt에서 ViewModel이 Inject되는 과정을 상세히 짚어보고, 그 과정에서 나오는 Dagger2 및 JSR330의 annotation에 대해서도 알아보겠습니다.

그 이후에는, Multi Module에서 Pure Java/Kotlin Module을 사용하여 Hilt를 사용할 수 없는 환경에서 JSR-330을 이용하여 Dependency Injection을 어떻게 하는지 다뤄보겠습니다.

기본적으로 HiltViewModel은 다음과 같이 사용합니다.

@AndroidEntryPoint
class ScreenActivity : ComponentActivity() {
  val viewModel: ScreenViewModel by viewmodels()
}

@HiltViewModel
class ScreenViewModel @Inecjt constructor() : ViewModel()

우리가 사용할 ViewModel에 @HiltViewModel Annotation을 붙이고, 종속항목을 넣고 @Inject를 붙여주면 간편하게 사용할 수 있습니다.

여기서 두 가지 갈래길이 생깁니다.
Activity 쪽 @AndroidEntryPoint로 인해 생기는 파일과 ScreenViewModel쪽 @HiltViewModel, @Inject로 인해 생기는 파일들이 있습니다.
우선 Activity 쪽을 살펴보겠습니다.

Hilt_ScreenActivity.java

public abstract class Hilt_ScreenActivity extends ComponentActivity implements GeneratedComponentManagerHolder {
 
  ...
 
  @Override
  public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
    return DefaultViewModelFactories.getActivityFactory(this, super.getDefaultViewModelProviderFactory());
  }
}

위와 같이 Activity이름 앞에 Hilt_가 붙은 파일이 생성됩니다. 생성된 파일 맨 아래쪽을 보시면 ViewModelProivder.Factory를 반환해주는 getDefaultViewModelProivderFactory() 함수가 있습니다.

이 함수의 쓰임새를 찾아 viewModels Delegate Property에 들어가보겠습니다.

@MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
  noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
  val factoryPromise = factoryProducer ?: {
      defaultViewModelProviderFactory
  }

  return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}

여기서 defaultViewModelProviderFactory를 이어서 타고 들어가면

@NonNull
@Override
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
  if (getApplication() == null) {
    throw new IllegalStateException("Your activity is not yet attached to the "
      + "Application instance. You can't request ViewModel before onCreate call.");
  }
  if (mDefaultFactory == null) {
    mDefaultFactory = new SavedStateViewModelFactory(
      getApplication(),
      this,
      getIntent() != null ? getIntent().getExtras() : null
    );
  }
  return mDefaultFactory;
}

위에서 Hilt가 생성한 Hilt_ScreenActivity의 getDefaultViewModelProviderFactory() 함수와 같은 함수인 것을 볼 수 있습니다.

우리가 Activity/Fragment에서 viewModels delegate property를 이용해 viewmodel을 선언하면 Hilt에서 생성한 Activity/Fragment에서 반환해주는 ViewModelProvider.Factory를 사용하는 것을 알 수 있습니다.

이런게 가능할 수 있는 이유는 우리가 작성한 Activity/Fragment 클래스가 Hilt에서 생성한 Activity/Fragment 클래스를 상속받기 때문입니다.

다시 Hilt_ScreenActivity 클래스로 돌아와서 getDefaultViewModelProviderFactory()를 계속 타고 들어가보겠습니다.

Hilt_ScreenActivity.java

@Override
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
  return DefaultViewModelFactories.getActivityFactory(this, super.getDefaultViewModelProviderFactory());
}
DefaultViewModelFactories.java

public static ViewModelProvider.Factory getActivityFactory(
  ComponentActivity activity,
  ViewModelProvider.Factory delegateFactory
) {
  return EntryPoints.get(activity, ActivityEntryPoint.class)
      .getHiltInternalFactoryFactory()
      .fromActivity(activity, delegateFactory);
}

여기서, EntryPoint라는 개념이 나옵니다. EntryPoint는 Hilt에서 지원하지 않는 안드로이드 구성요소가 Dagger 객체에 접근이 필요할 때 사용하게 됩니다.

이 EntryPoint를 사용하려면, @EntryPoint와 @InstallIn을 사용하여 인터페이스에 컴포넌트를 지정해주고, 해당 요소를 정의해줘야 합니다.

해당 클래스의 아래 쪽을 보면 해당 EntryPoint를 정의해놓은 것을 볼 수 있습니다.

DefaultViewModelFactories.java

@Inject
InternalFactoryFactory(
  Application application,
  @HiltViewModelMap.KeySet Set<String> keySet,
  ViewModelComponentBuilder viewModelComponentBuilder
) {
  this.application = application;
  this.keySet = keySet;
  this.viewModelComponentBuilder = viewModelComponentBuilder;
}

@Module
@InstallIn(ActivityComponent.class)
interface ActivityModule {
  @Multibinds
  @HiltViewModelMap.KeySet
  abstract Set<String> viewModelKeys();
}

@EntryPoint
@InstallIn(ActivityComponent.class)
public interface ActivityEntryPoint {
  InternalFactoryFactory getHiltInternalFactoryFactory();
}
MainApplication_HiltComponents.java

@Subcomponent(
  modules = {
    HiltWrapper_ActivityModule.class,
    HiltWrapper_DefaultViewModelFactories_ActivityModule.class,
    FragmentCBuilderModule.class,
    ViewCBuilderModule.class
  }
)
@ActivityScoped
  public abstract static class ActivityC implements ActivityComponent,
    DefaultViewModelFactories.ActivityEntryPoint, ... {
  @Subcomponent.Builder
  abstract interface Builder extends ActivityComponentBuilder {
  }
}
DaggerMainApplication_HiltComponents_SingletonC.java

private static final class ActivityCImpl extends MainApplication_HiltComponents.ActivityC {

  ...
  
  @Override
  public DefaultViewModelFactories.InternalFactoryFactory getHiltInternalFactoryFactory() {
    return DefaultViewModelFactories_InternalFactoryFactory_Factory.newInstance(ApplicationContextModule_ProvideApplicationFactory.provideApplication(singletonC.applicationContextModule), getViewModelKeys(), new ViewModelCBuilder(singletonC, activityRetainedCImpl));
  }

InternalFactoryFactory 같은 경우는 Dagger의 Component를 통해서 만들어집니다.

@Subcomponent annotation은 Dagge의 annotation입니다.

@Subcomponent를 이용하면 계층관계의 오브젝트 그래프를 그릴 수 있는데, 이를 통해 앱 구성요소의 생명주기에 맞게 메모리를 할당/해제할 수 있다는 것만 참고해두시면 될 것 같습니다.

생명주기와 다른 DefaultViewModelFactories_ActivityModule을 보실 수 있는데, InternalFactoryFactory의 생성자 주입에 @HiltViewModelMap.KeySet Qualifier가 적용되어 있어 이에 대한 종속항목을 가져오기 위한 모듈임을 알 수 있습니다.

하지만, getHiltInternalFactoryFactory를 통해 일반적으로 InterFactoryFactory를 사용하기 때문에, 주석에도 나와있다 싶이 Optional한 부분입니다.

다음 시리즈인 ViewModel 쪽에 더 자세히 나오는 부분입니다만, ViewModel쪽에 HiltViewModelMap.KeySet 을 정의해주는 코드가 따로 있고 그 종속항목을 사용하는 형태로 진행됩니다.

DefaultViewModelFactories.java

public static ViewModelProvider.Factory getActivityFactory(
  ComponentActivity activity,
  ViewModelProvider.Factory delegateFactory
) {
  return EntryPoints.get(activity, ActivityEntryPoint.class)
      .getHiltInternalFactoryFactory()
      .fromActivity(activity, delegateFactory);
}

그럼 이어서, fromActivity 함수를 보겠습니다.

DefaultViewModelFactories.java

ViewModelProvider.Factory fromActivity(
  ComponentActivity activity, ViewModelProvider.Factory delegateFactory
) {
  return getHiltViewModelFactory(
      activity,
      activity.getIntent() != null ? activity.getIntent().getExtras() : null,
      delegateFactory
  );
}
DefaultViewModelFactories.java

private ViewModelProvider.Factory getHiltViewModelFactory(
  SavedStateRegistryOwner owner,
  @Nullable Bundle defaultArgs,
  @Nullable ViewModelProvider.Factory extensionDelegate
) {
  ViewModelProvider.Factory delegate = extensionDelegate == null
    ? new SavedStateViewModelFactory(application, owner, defaultArgs)
    : extensionDelegate;
    
  return new HiltViewModelFactory(
    owner, defaultArgs, keySet, delegate, viewModelComponentBuilder
  );
}

fromActivity 부분을 보시면 HiltViewModelFactory를 생성해주는 것을 볼 수 있습니다.

이 다음 부분은 다음 시리즈인 ViewModel 쪽을 다루면서 이어서 설명하겠습니다. 감사합니다.

profile
https://www.linkedin.com/in/%ED%83%9C%ED%9B%88-%EC%9D%B4-7b9563237

1개의 댓글

comment-user-thumbnail
2022년 4월 9일

고수....ㄷㄷㄷ

답글 달기