AAkiraさんのブログを見てViewModelのInjectがすごいと思って不思議に思って仕組みを調べてみました。
https://aakira.app/blog/2020/05/dagger-hilt/
まだ正式にリリースされているわけではないので後々仕組みが変わる可能性があります。
このブログで紹介されているサンプルコードをベースに見ていきます。 (理解しやすいサンプルを公開していただき、本当にありがとうございます )
https://github.com/AAkira/dagger-hilt-example
以下のように書いておくことで、ViewModelをInjectできるそうです。
class MainViewModel @ViewModelInject constructor(
private val repository: SampleRepository,
@Assisted private val savedState: SavedStateHandle
) : ViewModel() {
}
@AndroidEntryPoint
class MainActivity : AppCompatActivity(R.layout.activity_main) {
private val viewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.v("main activity", "activity view model: $viewModel")
}
}
なぜ、これだけでViewModelのDIができるのでしょうか?
viewModels{}メソッドについて
ここではHiltではなく、元からAndroid JetpackにあるviewModels<>()
という関数を使っているのですが、viewModels<>()はデフォルトでは**ComponentActivity#getDefaultViewModelProviderFactory()
を経由してViewModelを生成して、getViewModelStore()で管理しているところにViewModelを保存します。
つまり、うまくこのProviderFactoryを変えることができれば、ViewModelのinjectができそう**です。
@MainThread
inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
// **ここでComponentActivity#getDefaultViewModelProviderFactory()を使う**
defaultViewModelProviderFactory
}
return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}
どうやってgetDefaultViewModelProviderFactory()
を変えるか?
実際にアプリを動かしてコードを見てみましょう。
MainActivityのsuperクラスはAppCompatActivityのはずでしたが。。。
@AndroidEntryPoint
class MainActivity : AppCompatActivity(R.layout.activity_main) {
デバッグで確認する以下のように MainActivityのsuperクラスとして、Hilt_MainActivity
というクラスがいることがわかります
Hilt_MainActivityは以下のようになっており、 **getDefaultViewModelProviderFactory() が実装されています。**これによってViewModelのProviderFactoryが生成されます。
@Generated("dagger.hilt.android.processor.internal.androidentrypoint.ActivityGenerator")
public abstract class Hilt_MainActivity extends AppCompatActivity implements GeneratedComponentManager<Object> {
private volatile ActivityComponentManager componentManager;
...
@Override
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
ViewModelProvider.Factory factory = DefaultViewModelFactories.getActivityFactory(this);
if (factory != null) {
return factory;
}
return super.getDefaultViewModelProviderFactory();
}
}
また少し脇道にそれますが、Hilt_MainActivityでは面白いことにこの生成されたコードの中でcomponentを持つActivityComponentManagerを生成しています。
@Generated("dagger.hilt.android.processor.internal.androidentrypoint.ActivityGenerator")
public abstract class Hilt_MainActivity extends AppCompatActivity implements GeneratedComponentManager<Object> {
private volatile ActivityComponentManager componentManager;
...
protected ActivityComponentManager createComponentManager() {
return new ActivityComponentManager(this);
}
protected final ActivityComponentManager componentManager() {
if (componentManager == null) {
synchronized (componentManagerLock) {
if (componentManager == null) {
componentManager = createComponentManager();
}
}
}
return componentManager;
}
話を戻しますが、DefaultViewModelFactories.getActivityFactory(this)
を追ってみましょう。
@Generated("dagger.hilt.android.processor.internal.androidentrypoint.ActivityGenerator")
public abstract class Hilt_MainActivity extends AppCompatActivity implements GeneratedComponentManager<Object> {
private volatile ActivityComponentManager componentManager;
...
@Override
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
// ********
ViewModelProvider.Factory factory = DefaultViewModelFactories.getActivityFactory(this);
生成されたコード内でしか使わないで!というメソッドがあります。 基本的にはactivityからDaggerのコンポーネントを取得して、そのコンポーネントにあるモジュールからViewModelのProviderFactoryを取得します。
EntryPoints.get(activity, ActivityEntryPoint.class)
というのを呼び出すのですが、EntryPointsについては深くは説明しないのですが(インスタンス取得を楽にする面白い仕組みがありそうでした。)、ここではHilt_MainActivityのメソッドで作られているActivityのスコープのComponentを取得して返しています。
その後、.getActivityViewModelFactory()
で、そのComponentに生えているgetActivityViewModelFactory()
を呼び出すことで、ModuleでProvideされたViewModelのProviderFactoryを取得しています。
public final class DefaultViewModelFactories {
/**
* Retrieves the default view model factory for the activity.
*
* <p>Do not use except in Hilt generated code!
*/
@Nullable
public static ViewModelProvider.Factory getActivityFactory(ComponentActivity activity) {
return getFactoryFromSet(
EntryPoints.get(activity, ActivityEntryPoint.class).getActivityViewModelFactory());
}
Hiltのライブラリ内にあるProviderFactoryをProvideするModuleのコードです。
ここでProviderFactoryがProvideされます。
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public final class ViewModelFactoryModules {
/**
* Hilt Modules for providing the activity level ViewModelFactory
*/
@Module
@InstallIn(ActivityComponent.class)
public abstract static class ActivityModule {
@NonNull
@Multibinds
abstract Map<String, ViewModelAssistedFactory<? extends ViewModel>> viewModelFactoriesMap();
@Provides
@IntoSet
@NonNull
@DefaultActivityViewModelFactory
static ViewModelProvider.Factory provideFactory(
@NonNull Activity activity,
@NonNull Application application,
@NonNull Map<String, Provider<ViewModelAssistedFactory<? extends ViewModel>>>
viewModelFactories) {
// Hilt guarantees concrete activity is a subclass of ComponentActivity.
SavedStateRegistryOwner owner = (ComponentActivity) activity;
Bundle defaultArgs = activity.getIntent() != null
? activity.getIntent().getExtras() : null;
SavedStateViewModelFactory delegate =
new SavedStateViewModelFactory(application, owner, defaultArgs);
return new HiltViewModelFactory(owner, defaultArgs, delegate, viewModelFactories);
まとめ
中身を知らなくても作れるように作られていますが、気になって見てみても読めるコードになっていて面白かったです。ぜひ興味があれば見てみてください。