안녕하세요, 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 쪽을 다루면서 이어서 설명하겠습니다. 감사합니다.
고수....ㄷㄷㄷ