viewModel()とhiltViewModel()の比較
結論
Dagger Hiltが入っているプロジェクトならhiltViewModel
がオススメ
引数がない場合
ほぼ変わらない
引数があるがHiltで解決できる場合
hiltViewModel
であれば引数を渡さなくても依存解決してくれるため行数が短くなる
引数がありAssisted Injectする必要がある場合
viewModel
とhiltViewModel
で引数の型が変わる・・・がhiltViewModel
の方が簡潔になりやすい印象
コード上の違い
hiltViewModel()
は内部でviewModel()
を呼び出している
@Composable
inline fun <reified VM : ViewModel> hiltViewModel(
viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
"No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
},
key: String? = null
): VM {
val factory = createHiltViewModelFactory(viewModelStoreOwner)
return viewModel(viewModelStoreOwner, key, factory = factory)
}
@Composable
inline fun <reified VM : ViewModel, reified VMF> hiltViewModel(
viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
"No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
},
key: String? = null,
noinline creationCallback: (VMF) -> VM
): VM {
val factory = createHiltViewModelFactory(viewModelStoreOwner)
return viewModel(
viewModelStoreOwner = viewModelStoreOwner,
key = key,
factory = factory,
extras = viewModelStoreOwner.run {
if (this is HasDefaultViewModelProviderFactory) {
this.defaultViewModelCreationExtras.withCreationCallback(creationCallback)
} else {
CreationExtras.Empty.withCreationCallback(creationCallback)
}
}
)
}
viewModel
の実装は以下の通り
public inline fun <reified VM : ViewModel> viewModel(
viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
"No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
},
key: String? = null,
factory: ViewModelProvider.Factory? = null,
extras: CreationExtras = if (viewModelStoreOwner is HasDefaultViewModelProviderFactory) {
viewModelStoreOwner.defaultViewModelCreationExtras
} else {
CreationExtras.Empty
}
): VM = viewModel(VM::class.java, viewModelStoreOwner, key, factory, extras)
コード上のviewModel
とhiltViewModel
の違いは
- factoryを
-
viewModelStoreOwner
から生成するか(hiltViewModel) - 直接渡すか(viewModel)
- extrasを
- ViewModelFactory → ViewModel関数から生成するか(hiltViewModel)
- 直接渡すか(viewModel)
と考えることが可能
1. factory
createHiltViewModelFactory(viewModelStoreOwner)
を見てみる
@Composable
@PublishedApi
internal fun createHiltViewModelFactory(
viewModelStoreOwner: ViewModelStoreOwner
): ViewModelProvider.Factory? = if (viewModelStoreOwner is HasDefaultViewModelProviderFactory) {
HiltViewModelFactory(
context = LocalContext.current,
delegateFactory = viewModelStoreOwner.defaultViewModelProviderFactory
)
} else {
// Use the default factory provided by the ViewModelStoreOwner
// and assume it is an @AndroidEntryPoint annotated fragment or activity
null
}
ViewModelStoreOwner
の値によって変わる・・・が基本上の方を通ると思われる(例えばNavBackStackEntry
も継承している)
defaultViewModelProviderFactory
はデフォルトのFactoryとのこと
特にFactoryを指定されていないときはこれを使うらしい?
/**
* Returns the default [ViewModelProvider.Factory] that should be
* used when no custom `Factory` is provided to the
* [ViewModelProvider] constructors.
*/
val defaultViewModelProviderFactory: ViewModelProvider.Factory
次にHiltViewModelFactoryを見てみる
@JvmName("create")
public fun HiltViewModelFactory(
context: Context,
delegateFactory: ViewModelProvider.Factory
): ViewModelProvider.Factory {
val activity = context.let {
var ctx = it
while (ctx is ContextWrapper) {
// Hilt can only be used with ComponentActivity
if (ctx is ComponentActivity) {
return@let ctx
}
ctx = ctx.baseContext
}
throw IllegalStateException(
"Expected an activity context for creating a HiltViewModelFactory " +
"but instead found: $ctx"
)
}
return HiltViewModelFactory.createInternal(
/* activity = */ activity,
/* delegateFactory = */ delegateFactory
)
}
呼び出し元のActivityインスタンスを取得して次に進んでいる
ここでHiltViewModelFactory.createInternal
は、Dagger Hiltライブラリ内に定義されているHiltViewModelFactory
というFactoryクラスのインスタンスを取ってくる処理である
このFactoryにはDagger Hiltで登録されているViewModelの依存関係の解決を行ってくれる関数が生えており、Dagger Hilt側で依存解決できるものに関しても同時に解決してくれるようになっている
まとめると
-
hiltViewModel
の場合、DaggerHilt
で登録されたViewModel
を生成してくれるHiltViewModelFactory
クラスが設定される- ViewModelの引数の中にDagger Hilt側で依存解決ができるものがあれば解決を行ってくれる
- 登録されてなければデフォルトのFactoryクラスを利用する
-
viewModel
の場合、自分でFactoryの取得、解決をする必要がある
2. extras
そもそもextras
とは、(ViewModelFactory) → ViewModel
を保存しているMapのこと
/**
* Simple map-like object that passed in [ViewModelProvider.Factory.create]
* to provide an additional information to a factory.
*
* It allows making `Factory` implementations stateless, which makes an injection of factories
* easier because don't require all information be available at construction time.
*/
public abstract class CreationExtras internal constructor() {
internal val map: MutableMap<Key<*>, Any?> = mutableMapOf()
/**
* Key for the elements of [CreationExtras]. [T] is a type of an element with this key.
*/
public interface Key<T>
/**
* Returns an element associated with the given [key]
*/
public abstract operator fun <T> get(key: Key<T>): T?
/**
* Empty [CreationExtras]
*/
object Empty : CreationExtras() {
override fun <T> get(key: Key<T>): T? = null
}
}
つまりここから対応する(ViewModelFactory) → ViewModel
を探し出してきて適用する挙動をとると思われる
これを踏まえてhiltViewModel
とviewModel
の差分を見てみる
extras = viewModelStoreOwner.run {
if (this is HasDefaultViewModelProviderFactory) {
this.defaultViewModelCreationExtras.withCreationCallback(creationCallback)
} else {
CreationExtras.Empty.withCreationCallback(creationCallback)
}
}
ここで重要と思われるのはwithCreationCallback(creationCallback)
という記述
creationCallback
というのはhiltViewModel
の引数にある、(ViewModelFactory) → ViewModel
@Composable
inline fun <reified VM : ViewModel, reified VMF> hiltViewModel(
viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
"No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
},
key: String? = null,
noinline creationCallback: (VMF) -> VM
)@Composable
inline fun <reified VM : ViewModel, reified VMF> hiltViewModel(
viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
"No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
},
key: String? = null,
noinline creationCallback: (VMF) -> VM
)
withCreationCallback(creationCallback)
は、extrasにcreationCallbackを追加する挙動を行う
/**
* Returns a new {@code CreationExtras} with the original entries plus the passed in creation
* callback. The callback is used by Hilt to create {@link AssistedInject}-annotated {@link
* HiltViewModel}s.
*
* @param callback A creation callback that takes an assisted factory and returns a {@code
* ViewModel}.
*/
fun <VMF> CreationExtras.withCreationCallback(callback: (VMF) -> ViewModel): CreationExtras =
MutableCreationExtras(this).addCreationCallback(callback)
/**
* Returns the {@code MutableCreationExtras} with the passed in creation callback added. The
* callback is used by Hilt to create {@link AssistedInject}-annotated {@link HiltViewModel}s.
*
* @param callback A creation callback that takes an assisted factory and returns a {@code
* ViewModel}.
*/
@Suppress("UNCHECKED_CAST")
fun <VMF> MutableCreationExtras.addCreationCallback(callback: (VMF) -> ViewModel): CreationExtras =
this.apply {
this[HiltViewModelFactory.CREATION_CALLBACK_KEY] = { factory -> callback(factory as VMF) }
}
まとめると、デフォルトのextras
にユーザーが指定したextra
を追加したものを使っていることになる(=対象のViewModel
のextra
だけを渡せばいい)
対してviewModel
ではextras
を渡す必要があり、少し面倒(default + 対象のViewModel
のextra
みたいなコードを書く必要がある)
結論
差分は以下の通り
viewModel
- 渡された
factory
やextras
の値を元にViewModel
を生成して返す - 引数のない
ViewModel
などはfactory
やextras
がそもそもいらないのでデフォルト引数のままでいい
hiltViewModel
- Dagger Hiltの機能を存分に使った
viewModel
の糖衣構文的な関数 -
Factory
はDagger Hiltの機能を用いて勝手に解決してくれる -
extra
が必要な場合、対象となるViewModel
のextra
を用意するだけでよい
これらの性質を踏まえると
- 引数が全くない時はほぼ変わらない
-
viewModel
の方が僅かにパフォーマンスは良い(hilt側のコードを呼ぶ必要がなくなるため) - R8挟んだら差はなくなる可能性もある
-
- 使いこごちの差は引数がある時に顕著になる
-
viewModel
の場合、factory
やextras
を書く必要がある -
hiltViewModel
の場合、Dagger Hilt側で依存解決が出来る場合依存解決を行う -
Assisted Inject
を使うときもfactory
に関しては依存解決してくれる(@AssitedFactory
として定義する必要はある)
-
- Hilt × Composeの場合は
hiltViewModel
を使うとコードがスッキリして良さそう
余談
-
Activity
やFragment
で使えるhiltViewModels
があってもいい気がする(あるのかも?) -
自作ライブラリを作るために調査
- 適当な調査だしライブラリ自体も使い物になるのか怪しいが、こういう風に書けたら便利だと思ってる
Discussion