LoginSignup
53
26

More than 3 years have passed since last update.

Dagger-HiltはViewModelのFactoryをどのように差し込むのか

Last updated at Posted at 2020-05-30

AAkiraさんのブログを見てViewModelのInjectがすごいと思って不思議に思って仕組みを調べてみました。
https://aakira.app/blog/2020/05/dagger-hilt/

まだ正式にリリースされているわけではないので後々仕組みが変わる可能性があります。
このブログで紹介されているサンプルコードをベースに見ていきます。 (理解しやすいサンプルを公開していただき、本当にありがとうございます :pray: )
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というクラスがいることがわかります

image.png

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);

まとめ

中身を知らなくても作れるように作られていますが、気になって見てみても読めるコードになっていて面白かったです。ぜひ興味があれば見てみてください。

53
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
53
26