Improved viewmodel logic for #39.

- added separate factories for VMs that require the synchronizer and those that do not
- witched to using the factories directly, instead of creating providers, similar to how Google does it in their generic extension functions but removed the need to inject the factory
This commit is contained in:
Kevin Gorham 2020-01-07 01:43:15 -05:00
parent ac626917f4
commit 1937c19a14
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
3 changed files with 21 additions and 49 deletions

View File

@ -4,39 +4,18 @@ import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModelProvider
import cash.z.ecc.android.di.annotation.ActivityScope
import cash.z.ecc.android.di.module.MainActivityModule
import cash.z.ecc.android.di.module.ViewModelsModule
import cash.z.ecc.android.ui.MainActivity
import cash.z.ecc.android.ui.detail.WalletDetailFragment
import cash.z.ecc.android.ui.home.HomeFragment
import cash.z.ecc.android.ui.receive.ReceiveFragment
import cash.z.ecc.android.ui.send.SendAddressFragment
import cash.z.ecc.android.ui.send.SendConfirmFragment
import cash.z.ecc.android.ui.send.SendFinalFragment
import cash.z.ecc.android.ui.send.SendMemoFragment
import cash.z.ecc.android.ui.setup.BackupFragment
import cash.z.ecc.android.ui.setup.LandingFragment
import dagger.BindsInstance
import dagger.Subcomponent
import javax.inject.Named
@ActivityScope
@Subcomponent(modules = [MainActivityModule::class, ViewModelsModule::class])
@Subcomponent(modules = [MainActivityModule::class])
interface MainActivitySubcomponent {
fun inject(activity: MainActivity)
// Fragments
fun inject(fragment: HomeFragment)
fun inject(fragment: ReceiveFragment)
fun inject(fragment: SendAddressFragment)
fun inject(fragment: SendMemoFragment)
fun inject(fragment: SendConfirmFragment)
fun inject(fragment: SendFinalFragment)
fun inject(fragment: WalletDetailFragment)
fun inject(fragment: LandingFragment)
fun inject(fragment: BackupFragment)
fun viewModels(): ViewModelProvider
fun viewModelFactory(): ViewModelProvider.Factory
@Named("BeforeSynchronizer") fun viewModelFactory(): ViewModelProvider.Factory
@Subcomponent.Factory
interface Factory {

View File

@ -2,35 +2,30 @@ package cash.z.ecc.android.di.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import cash.z.ecc.android.ui.MainActivity
import cash.z.ecc.android.ui.base.BaseFragment
inline fun <reified VM : ViewModel> MainActivity.viewModel() = object : Lazy<VM> {
val cached: VM? = null
override fun isInitialized(): Boolean = cached != null
override val value: VM get() {
return cached ?: component.viewModels()[VM::class.java]
}
}
inline fun <reified VM : ViewModel> BaseFragment<*>.viewModel() = object : Lazy<VM> {
val cached: VM? = null
override fun isInitialized(): Boolean = cached != null
override val value: VM get() {
return if (cached != null) cached else {
val factory = mainActivity?.component?.viewModelFactory()
?: throw IllegalStateException("Error: mainActivity should not be null by the time the ${VM::class.java.simpleName} viewmodel is lazily accessed!")
ViewModelProvider(this@viewModel, factory)[VM::class.java]
}
}
override val value: VM
get() = cached
?: ViewModelProvider(this@viewModel, scopedFactory<VM>())[VM::class.java]
}
inline fun <reified VM : ViewModel> BaseFragment<*>.activityViewModel() = object : Lazy<VM> {
inline fun <reified VM : ViewModel> BaseFragment<*>.activityViewModel(isSynchronizerScope: Boolean = true) = object : Lazy<VM> {
val cached: VM? = null
override fun isInitialized(): Boolean = cached != null
override val value: VM get() {
return cached ?: mainActivity?.component?.viewModels()?.get(VM::class.java)
?: throw IllegalStateException("Error: mainActivity should not be null by the time the ${VM::class.java.simpleName} activityViewModel is lazily accessed!")
}
override val value: VM
get() {
return cached
?: scopedFactory<VM>(isSynchronizerScope)?.let { factory ->
ViewModelProvider(this@activityViewModel.mainActivity!!, factory)[VM::class.java]
}
}
}
inline fun <reified VM : ViewModel> BaseFragment<*>.scopedFactory(isSynchronizerScope: Boolean = true): ViewModelProvider.Factory {
val factory = if (isSynchronizerScope) mainActivity?.synchronizerComponent?.viewModelFactory() else mainActivity?.component?.viewModelFactory()
return factory ?: throw IllegalStateException("Error: mainActivity should not be null by the time the ${VM::class.java.simpleName} viewmodel is lazily accessed!")
}

View File

@ -2,11 +2,9 @@ package cash.z.ecc.android.di.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import cash.z.ecc.android.di.annotation.ActivityScope
import javax.inject.Inject
import javax.inject.Provider
@ActivityScope
class ViewModelFactory @Inject constructor(
private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
@ -14,8 +12,8 @@ class ViewModelFactory @Inject constructor(
val creator = creators[modelClass] ?: creators.entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException(
"No map entry found for ${modelClass.canonicalName}." +
" Verify that this ViewModel has been added to the ViewModelModule."
"No map entry found for ${modelClass.canonicalName}. Verify that this ViewModel has" +
" been added to the ViewModelModule. ${creators.keys}"
)
@Suppress("UNCHECKED_CAST")
return creator.get() as T