我只用dagger2 (不是匕首和甲骨文)在我的项目中。使用multibinding注入ViewModel时工作得很好,但有一个问题,以前没有dagger2时,我在多个片段中使用活动中使用的同一个viewModel实例(使用fragment-ktx方法activityViewModels())。但是有一个问题,以前没有dagger2的时候,我是在多个片段中使用activity中使用的同一个viewmodel实例(使用fragment-ktx方法activityViewModels()),但是现在由于 dagger2在注入视图模型时,总是给新的实例。 (在每个fragment中用hashCode检查)的viewmodel的每个fragment,这只是中断了fragment之间使用viewmodel的通信。
fragment &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...
}
这是viewmodel依赖注入的代码。
//-----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
}
有没有什么办法 实现多个片段的同一个viewmodel实例,同时在片段中注入view模型。另外,是否需要bindViewModelFactory方法,因为即使没有这个方法,似乎对应用也没有影响。
一个 变通办法 可能是为了使 基礎片段 (BaseFragment) 为共享视图模型的片段,但这将再次包含模板代码,而且我也不是BaseFragmentBaseActivity的忠实粉丝。
这是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);
}
}
问题是,当你像这样注入viewmodel的时候
class MyFragment: Fragment() {
@Inject lateinit var chartViewModel: ChartViewModel
dagger只是创建了一个新的viewmodel实例。没有任何viewmodel-fragment-lifecycle的魔法发生,因为这个viewmodel不在activityfragment的viewmodelstore中,也没有被你创建的viewmodelfactory提供。在这里,你可以把viewmodel看成任何一个正常的类,真的。举个例子。
class MyFragment: Fragment() {
@Inject lateinit var anything: AnyClass
}
class AnyClass @Inject constructor(private val repository: ChartRepository) {
//live data code...
}
你的viewmodel就相当于这个 AnyClass
因为viewmodel不在viewmodelstore中,也不在fragmentactivity的生命周期内。
有什么办法可以实现多个fragment的viewmodel的同一个实例,同时也可以在fragment中注入viewmodel。
没有,因为上面列出的原因。
另外,还有没有必要使用bindViewModelFactory方法,因为即使没有这个方法,它似乎也不会对应用产生任何影响。
它没有任何影响是因为(我假设)你没有使用 ViewModelFactory
的任何地方。由于它没有被引用到任何地方,这个viewmodelfactory的匕首代码是没有用的。
@Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
所以删除它对应用没有影响。
那么有什么解决办法呢?你需要将工厂注入到fragmentactivity中,并使用工厂获得viewmodel的实例。
class MyFragment: Fragment() {
@Inject lateinit var viewModelFactory: ViewModelFactory
private val vm: ChartViewModel by lazy {
ViewModelProvider(X, YourViewModelFactory).get(ChartViewModel::class.java)
}
什么是 X
在这里?X是 ViewModelStoreOwner
. A ViewModelStoreOwner
是有viewmodels下的东西。ViewModelStoreOwner
是由activity和fragment实现的。所以你有几种创建viewmodel的方法。
ViewModelProvider(this, YourViewModelFactory)
ViewModelProvider(this, YourViewModelFactory)
ViewModelProvider(requireParentFragment(), YourViewModelFactory)
ViewModelProvider(requireActivity(), YourViewModelFactory)
一个变通的办法是为片段制作一个共享视图模型的BaseFragment,但这将再次包含模板代码,而且我也不是很喜欢BaseFragmentBaseActivity。
是的,这的确是个坏主意。解决办法是使用 requireParentFragment()
和 requireActivity()
来获取viewmodel的实例,但你会在每个有viewmodel的fragmentactivity中写同样的东西。但你会在每个有viewmodel的fragmentactivity中写同样的东西。为了避免这种情况,你可以抽象出这个 ViewModelProvider(x, factory)
部分在基类fragmentactivity类中,同时在基类中注入工厂,这样可以简化你的子类fragmentactivity代码。
class MyFragment: Fragment() {
private val vm: ChartViewModel by bindViewModel() // or bindParentFragmentViewModel() or bindActivityViewModel()
你可以共享 ViewModel
当实例化时,如果片段具有相同的父活动,则在片段之间的活动。
FragmentOne
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")
}
}
FragmentTwo
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")
}
}
将您的ViewModel添加为 PostListViewModel
里面 ViewModelModule
:
@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))
}
//...
}
更多内容请看这篇文章。用Dagger2注入ViewModel。 还有 查看github