我在我的项目中只使用dagger2(不是dagger-android(。使用多重绑定注入视图模型工作正常。但是以前没有 dagger2 有一个问题,我使用了在多个片段中的活动中使用的相同视图模型实例(使用 fragment-ktx 方法 activityViewModels(((,但现在由于dagger2 正在注入视图模型,它总是为每个片段提供视图模型的新实例(在每个片段中使用哈希代码检查(,这只是使用 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...
}
下面是视图模型依赖关系注入的代码:
//-----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
}
有没有办法为多个片段实现视图模型的相同实例,同时在片段中注入视图模型。也不需要绑定视图模型工厂方法,因为即使没有此方法,它似乎对应用程序也没有影响。
一种解决方法可能是为共享公共视图模型的片段创建一个 BaseFragment,但这将再次包含样板代码,而且我也不是BaseFragment/BaseActivity 的忠实粉丝。
这是为 ChartViewModel 生成的代码,它始终创建 viewModel 的新实例:
@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);
}
}
问题是当你像这样注入视图模型时
class MyFragment: Fragment() {
@Inject lateinit var chartViewModel: ChartViewModel
Dagger 只是创建一个新的视图模型实例。没有视图模型片段生命周期的魔术,因为此视图模型不在活动/片段的视图模型存储中,并且不是由您创建的视图模型工厂提供的。在这里,您可以将视图模型视为任何普通类。举个例子:
class MyFragment: Fragment() {
@Inject lateinit var anything: AnyClass
}
class AnyClass @Inject constructor(private val repository: ChartRepository) {
//live data code...
}
您的视图模型等效于此AnyClass
因为视图模型不在视图模型存储中,并且不限于片段/活动的生命周期。
有没有办法为多个片段实现视图模型的相同实例,同时在片段中注入视图模型
不。由于上面列出的原因。
也不需要绑定视图模型工厂方法,因为即使没有此方法,它似乎对应用程序也没有影响。
它没有任何效果,因为(我假设(您没有在任何地方使用该ViewModelFactory
。由于它没有在任何地方引用,因此视图模型工厂的这个dagger代码是无用的。
@Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
以下是@binds正在做的事情: 1 2
这就是为什么删除它对应用程序没有影响的原因。
那么解决方案是什么?您需要将工厂注入到片段/活动中,并使用工厂获取视图模型的实例
class MyFragment: Fragment() {
@Inject lateinit var viewModelFactory: ViewModelFactory
private val vm: ChartViewModel by lazy {
ViewModelProvider(X, YourViewModelFactory).get(ChartViewModel::class.java)
}
这里X
是什么?X 是ViewModelStoreOwner
。ViewModelStoreOwner
是在其下具有视图模型的东西。ViewModelStoreOwner
由活动和片段实现。因此,您可以通过以下几种方式创建视图模型:
- 视图中的模型
ViewModelProvider(this, YourViewModelFactory)
- 查看片段中的模型
ViewModelProvider(this, YourViewModelFactory)
- 视图模型 在片段 (B( 中限定为父片段 (A( 并在 A 下的子片段之间共享
ViewModelProvider(requireParentFragment(), YourViewModelFactory)
- 视图片段中的模型,范围限定为父活动,并在活动下的片段之间共享
ViewModelProvider(requireActivity(), YourViewModelFactory)
一种解决方法可能是为共享公共视图模型的片段创建一个 BaseFragment,但这将再次包含样板代码,而且我也不是 BaseFragment/BaseActivity 的忠实粉丝
是的,这确实是一个坏主意。解决方案是使用requireParentFragment()
和requireActivity()
来获取视图模型实例。但是,您将在每个具有视图模型的片段/活动中编写相同的内容。为了避免这种情况,您可以在基片段/活动类中抽象出此ViewModelProvider(x, factory)
部分,并在基类中注入工厂,这将简化您的子片段/活动代码,如下所示:
class MyFragment: BaseFragment() {
private val vm: ChartViewModel by bindViewModel() // or bindParentFragmentViewModel() or bindActivityViewModel()
实例化片段是否具有相同的父活动,则可以在片段之间共享ViewModel
碎片一
class FragmentOne: Fragment() {
private lateinit var viewmodel: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewmodel= activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} : throw Exception("Invalid Activity")
}
}
碎片二
class FragmentTwo: Fragment() {
private lateinit var viewmodel: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewmodel= activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
}
}
将视图模型添加为ViewModelModule
中的PostListViewModel
:
@Singleton
class ViewModelFactory @Inject constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T = viewModels[modelClass]?.get() as T
}
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
@MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
@Module
abstract class ViewModelModule {
@Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
@Binds
@IntoMap
@ViewModelKey(PostListViewModel::class)
internal abstract fun postListViewModel(viewModel: PostListViewModel): ViewModel
//Add more ViewModels here
}
最后,我们的活动将被注入ViewModelProvider.Factory
,并将其传递给private val viewModel: PostListViewModel by viewModels { viewModelFactory }
class PostListActivity : AppCompatActivity() {
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private val viewModel: PostListViewModel by viewModels { viewModelFactory }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_post_list)
getAppInjector().inject(this)
viewModel.posts.observe(this, Observer(::updatePosts))
}
//...
}
欲了解更多信息,请查看这篇文章:Inject ViewModel with Dagger2 and Check github