dee*_*mar 5 android viewmodel android-fragments dagger-2 android-mvvm
我在我的项目中只使用 dagger2 (不是 dagger-android)。使用多重绑定注入 ViewModel 工作正常。但是以前没有 dagger2 有一个问题,我在多个片段的活动中使用了相同的视图模型实例(使用 fragment-ktx 方法 activityViewModels()),但现在由于dagger2 正在注入视图模型,它总是提供新实例(在每个片段的视图模型的每个片段中使用 hashCode 检查,这只是使用 viewmodel 中断了片段之间的通信。
片段和视图模型代码如下:
class MyFragment: Fragment() {
@Inject lateinit var chartViewModel: ChartViewModel
override fun onAttach(context: Context) {
super.onAttach(context)
(activity?.application as MyApp).appComponent.inject(this)
}
}
//-----ChartViewModel class-----
class ChartViewModel @Inject constructor(private val repository: ChartRepository) : BaseViewModel() {
//live data code...
}
Run Code Online (Sandbox Code Playgroud)
这是视图模型依赖注入的代码:
//-----ViewModelKey class-----
@MapKey
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
//-----ViewModelFactory class------
@Singleton
@Suppress("UNCHECKED_CAST")
class ViewModelFactory
@Inject constructor(
private val viewModelMap: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = viewModelMap[modelClass] ?: viewModelMap.asIterable()
.firstOrNull { modelClass.isAssignableFrom(it.key) }?.value
?: throw IllegalArgumentException("Unknown ViewModel class $modelClass")
return try {
creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
//-----ViewModelModule class-----
@Module
abstract class ViewModelModule {
@Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
@Binds
@IntoMap
@ViewModelKey(ChartViewModel::class)
abstract fun bindChartViewModel(chartViewModel: ChartViewModel): ViewModel
}
Run Code Online (Sandbox Code Playgroud)
有什么方法可以为多个片段实现相同的视图模型实例,并同时在片段中注入视图模型。 是否还需要 bindViewModelFactory 方法,因为即使没有此方法,它似乎对应用程序也没有影响。
一种解决方法可能是为共享公共视图模型的片段创建一个BaseFragment,但这将再次包含样板代码,而且我也不是 BaseFragment/BaseActivity 的忠实粉丝。
这是 ChartViewModel 生成的代码,它总是创建 viewModel 的 newInstance:
@SuppressWarnings({
"unchecked",
"rawtypes"
})
public final class ChartViewModel_Factory implements Factory<ChartViewModel> {
private final Provider<ChartRepository> repositoryProvider;
public ChartViewModel_Factory(Provider<ChartRepository> repositoryProvider) {
this.repositoryProvider = repositoryProvider;
}
@Override
public ChartViewModel get() {
return newInstance(repositoryProvider.get());
}
public static ChartViewModel_Factory create(Provider<ChartRepository> repositoryProvider) {
return new ChartViewModel_Factory(repositoryProvider);
}
public static ChartViewModel newInstance(ChartRepository repository) {
return new ChartViewModel(repository);
}
}
Run Code Online (Sandbox Code Playgroud)
问题是当你像这样注入视图模型时
class MyFragment: Fragment() {
@Inject lateinit var chartViewModel: ChartViewModel
Run Code Online (Sandbox Code Playgroud)
dagger 只是创建一个新的 viewmodel 实例。没有 viewmodel-fragment-lifecycle 魔法正在进行,因为这个 viewmodel 不在活动/片段的 viewmodelstore 中,并且不是由您创建的 viewmodelfactory 提供的。在这里,您可以真正将视图模型视为任何普通类。举个例子:
class MyFragment: Fragment() {
@Inject lateinit var anything: AnyClass
}
class AnyClass @Inject constructor(private val repository: ChartRepository) {
//live data code...
}
Run Code Online (Sandbox Code Playgroud)
您的 viewmodel 与此等效,AnyClass因为 viewmodel 不在 viewmodelstore 中,也不限于片段/活动的生命周期。
有什么方法可以为多个片段实现相同的视图模型实例,并同时在片段中注入视图模型
不会。因为上面列出的原因。
是否还需要 bindViewModelFactory 方法,因为即使没有此方法,它似乎对应用程序也没有影响。
它没有任何影响,因为(我假设)您没有在ViewModelFactory任何地方使用。由于它没有在任何地方被引用,viewmodelfactory 的这个匕首代码是没有用的。
@Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
Run Code Online (Sandbox Code Playgroud)
这就是为什么删除它对应用程序没有影响的原因。
那么解决方法是什么呢?您需要将工厂注入片段/活动并使用工厂获取视图模型的实例
class MyFragment: Fragment() {
@Inject lateinit var viewModelFactory: ViewModelFactory
private val vm: ChartViewModel by lazy {
ViewModelProvider(X, YourViewModelFactory).get(ChartViewModel::class.java)
}
Run Code Online (Sandbox Code Playgroud)
什么是X在这里吗?X 是ViewModelStoreOwner。AViewModelStoreOwner是在它们下面有视图模型的东西。ViewModelStoreOwner由活动和片段实现。所以你有几种创建视图模型的方法:
ViewModelProvider(this, YourViewModelFactory)
Run Code Online (Sandbox Code Playgroud)
ViewModelProvider(this, YourViewModelFactory)
Run Code Online (Sandbox Code Playgroud)
ViewModelProvider(requireParentFragment(), YourViewModelFactory)
Run Code Online (Sandbox Code Playgroud)
ViewModelProvider(requireActivity(), YourViewModelFactory)
Run Code Online (Sandbox Code Playgroud)
一种解决方法可能是为共享公共视图模型的片段创建一个 BaseFragment,但这将再次包含样板代码,而且我也不是 BaseFragment/BaseActivity 的忠实粉丝
是的,这确实是个坏主意。解决方案是使用requireParentFragment()并requireActivity()获取 viewmodel 实例。但是您将在每个具有视图模型的片段/活动中编写相同的内容。为了避免这种情况,您可以ViewModelProvider(x, factory)在基本片段/活动类中抽象出这部分,并在基类中注入工厂,这将简化您的子片段/活动代码,如下所示:
class MyFragment: BaseFragment() {
private val vm: ChartViewModel by bindViewModel() // or bindParentFragmentViewModel() or bindActivityViewModel()
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
4209 次 |
| 最近记录: |