Open23

KoinをHiltに移行する作戦 〜これが終わったら俺結婚するんだ〜

sobayasobaya

HiltでInjectしたクラスにKoinとHiltの両方でフィールドインジェクトするとHiltだけ失敗する

sobayasobaya

やっぱりFragmentにKoinでViewModelをInjectしてViewModelにUseCaseをHiltで、他のクラスをKoinでInjectしてるとクラッシュしたけど、
FragmentにViewModelをInjectするのをHiltにしたらクラッシュしない

sobayasobaya

ViewPager(FragmentStateAdapter)内のFragmentで
ViewModelをHiltでInject
ViewModelにKoinComponentをつけて他クラスをKoinでInject
すると

Can't access ViewModels from detached fragment

が出てクラッシュする

sobayasobaya

ViewPagerとFragmentPagerAdapterの組み合わせだとクラッシュしない
ViewPager2とFragmentStateAdapterだとクラッシュする

sobayasobaya

小さい環境で調べたらViewPager2も関係なかった
何か別の原因があるっぽい

sobayasobaya

デバッグモードにしてみたらViewModelのインスタンスは作れてるからエラーメッセージと動作が合ってない・・・

sobayasobaya

KoinとHiltでインスタンス生成のタイミングが違うことが問題な様子

private val viewModel: MenuViewModel by viewModels()
private val adapter: Adapter(viewModel.flag)

としたい時にHiltだとFragmentが生成されていないのにViewModelを生成しようとしてクラッシュする
だけど、なんでKoinだと生成できるんだ???
※Hiltだとandroidx.app.Fragment内のチェック処理でFragmentManagerがnullだからクラッシュする

sobayasobaya

そもそもby viewModels()とかby androidViewModels()がandroidx.app.Fragment側の持ち物だからクラッシュしただけ
Hiltの機能だと思い込んでた

sobayasobaya

KoinのSharedViewModelはActivityに紐付くと何かで見たけど嘘な気がするな(バージョンによるかも)
HiltでactivityViewModelを使った時と動作違う気がする

sobayasobaya
init {
    _liveData.postValue("Test")
}

private val _liveData = MutableLiveData<String?>(null)

KoinでInjectすると↑は動くけど、HIltだと_liveDataのインスタンスが作られるのが遅れてクラッシュする
※他の条件があるかもしれない

sobayasobaya

KoinがFragment完成前に生成できる理由を追うためにアマゾンに行く

結論
androidx.lifecycle.ViewModelProviderを自前で生成してるから
※androidx.lifecycle.ViewModelProviderが完成すれば公式にgetできる

補足

fun <T : ViewModel> ViewModelStoreOwner.getViewModel(
    clazz: KClass<T>,
    qualifier: Qualifier? = null,
    parameters: ParametersDefinition? = null
): T {
    return getKoin().getViewModel(this, clazz, qualifier, parameters)
}

getViewModelの第一引数のthisにActivityやFragmentが入る

sobayasobaya

ViewModelProvider(@NonNull ViewModelStoreOwner owner)
でインスタンス生成できるのになんで周りくどいことしてるんだろ???

sobayasobaya

Hiltの生成

Fragment.ViewModelProvider.Factory getDefaultViewModelProviderFactory()
を呼んでる

        if (mFragmentManager == null) {
            throw new IllegalStateException("Can't access ViewModels from detached fragment");
        }
sobayasobaya
@MainThread
fun <VM : ViewModel> Fragment.createViewModelLazy(
    viewModelClass: KClass<VM>,
    storeProducer: () -> ViewModelStore,
    factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    val factoryPromise = factoryProducer ?: {
        defaultViewModelProviderFactory
    }
    return ViewModelLazy(viewModelClass, storeProducer, factoryPromise)
}

ここからクラッシュするところ呼んでる

sobayasobaya

mFragmentManagerはFragmentTransactionに追加される時に設定される