使用 dagger2 在多个片段中使用相同的视图模型实例
Use the same instance of view model in multiple fragments using dagger2
我在我的项目中只使用了 dagger2(不是 dagger-android)。使用多重绑定注入 ViewModel 效果很好。但是有一个问题,以前没有 dagger2 我在多个片段中使用 activity 中使用的相同视图模型实例(使用片段-ktx 方法 activityViewModels()),但现在因为 dagger2 正在注入视图模型,它总是为每个片段提供视图模型的新实例(在每个片段中使用 hashCode 检查),这只是打破了使用视图模型的片段之间的通信。
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...
}
这是视图模型依赖注入的代码:
//-----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
}
有没有办法为多个片段实现相同的视图模型实例,同时在片段中注入视图模型。
另外是否需要 bindViewModelFactory 方法,因为即使没有此方法,它似乎对应用程序也没有影响。
一个解决方法可能是为共享公共视图模型的片段制作一个BaseFragment,但这将再次包含样板代码和我也不是 BaseFragment/BaseActivity.
的忠实粉丝
这是为始终创建 viewModel 的新实例的 ChartViewModel 生成的代码:
@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
activity
片段一
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")
}
}
将您的 ViewModel 添加为 PostListViewModel
inside 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
}
最后,我们的 activity 将注入 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))
}
//...
}
更多信息请查看此 post:Inject ViewModel with Dagger2 And Check github
问题是当你像这样注入视图模型时
class MyFragment: Fragment() {
@Inject lateinit var chartViewModel: ChartViewModel
dagger 只是创建一个新的视图模型实例。没有 viewmodel-fragment-lifecycle 魔术发生,因为此视图模型不在 activity/fragment 的视图模型存储中,并且不是由您创建的视图模型工厂提供的。在这里,您可以将视图模型视为任何普通的 class。例如:
class MyFragment: Fragment() {
@Inject lateinit var anything: AnyClass
}
class AnyClass @Inject constructor(private val repository: ChartRepository) {
//live data code...
}
您的视图模型等同于此 AnyClass
,因为视图模型不在视图模型存储中,并且不在 fragment/activity.
的生命周期范围内
Is there any way to achieve the same instance of viewmodel for multiple fragment and also at the same time inject the view model in fragments
没有。由于上面列出的原因。
Also is there any need for the bindViewModelFactory method as it seems to have no effect on app even without this method.
它没有任何效果,因为(我假设)您没有在任何地方使用 ViewModelFactory
。因为它没有在任何地方被引用,所以这个 viewmodelfactory 的匕首代码是无用的。
@Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
这是 @binds 正在做的事情: 2
这就是为什么删除它对应用程序没有影响的原因。
那么解决方法是什么?您需要将工厂注入 fragment/activity 并使用工厂
获取视图模型的实例
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
是由activity和fragment实现的。所以你有几种创建视图模型的方法:
- activity
中的视图模型
ViewModelProvider(this, YourViewModelFactory)
- 片段中的视图模型
ViewModelProvider(this, YourViewModelFactory)
- 片段 (B) 中的视图模型限定为 parent 片段 (A) 并在 A
下的 child 个片段之间共享
ViewModelProvider(requireParentFragment(), YourViewModelFactory)
- 片段中的视图模型范围为 parent activity 并在 activity
下跨片段共享
ViewModelProvider(requireActivity(), YourViewModelFactory)
One workaround could be to make a BaseFragment for fragments which shares the common viewmodel, but that will again include the boilerplate code and also I am not a great fan of BaseFragment/BaseActivity
是的,这确实是个坏主意。解决方案是使用 requireParentFragment()
和 requireActivity()
来获取 viewmodel 实例。但是您将在每个具有视图模型的 fragment/activity 中编写相同的内容。为避免这种情况,您可以在基础 fragment/activity class 中抽象出此 ViewModelProvider(x, factory)
部分,并将工厂注入基础 classes,这将简化您的 child fragment/activity 代码如下:
class MyFragment: BaseFragment() {
private val vm: ChartViewModel by bindViewModel() // or bindParentFragmentViewModel() or bindActivityViewModel()
我在我的项目中只使用了 dagger2(不是 dagger-android)。使用多重绑定注入 ViewModel 效果很好。但是有一个问题,以前没有 dagger2 我在多个片段中使用 activity 中使用的相同视图模型实例(使用片段-ktx 方法 activityViewModels()),但现在因为 dagger2 正在注入视图模型,它总是为每个片段提供视图模型的新实例(在每个片段中使用 hashCode 检查),这只是打破了使用视图模型的片段之间的通信。
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...
}
这是视图模型依赖注入的代码:
//-----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
}
有没有办法为多个片段实现相同的视图模型实例,同时在片段中注入视图模型。 另外是否需要 bindViewModelFactory 方法,因为即使没有此方法,它似乎对应用程序也没有影响。
一个解决方法可能是为共享公共视图模型的片段制作一个BaseFragment,但这将再次包含样板代码和我也不是 BaseFragment/BaseActivity.
的忠实粉丝这是为始终创建 viewModel 的新实例的 ChartViewModel 生成的代码:
@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
activity
片段一
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")
}
}
将您的 ViewModel 添加为 PostListViewModel
inside 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
}
最后,我们的 activity 将注入 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))
}
//...
}
更多信息请查看此 post:Inject ViewModel with Dagger2 And Check github
问题是当你像这样注入视图模型时
class MyFragment: Fragment() {
@Inject lateinit var chartViewModel: ChartViewModel
dagger 只是创建一个新的视图模型实例。没有 viewmodel-fragment-lifecycle 魔术发生,因为此视图模型不在 activity/fragment 的视图模型存储中,并且不是由您创建的视图模型工厂提供的。在这里,您可以将视图模型视为任何普通的 class。例如:
class MyFragment: Fragment() {
@Inject lateinit var anything: AnyClass
}
class AnyClass @Inject constructor(private val repository: ChartRepository) {
//live data code...
}
您的视图模型等同于此 AnyClass
,因为视图模型不在视图模型存储中,并且不在 fragment/activity.
Is there any way to achieve the same instance of viewmodel for multiple fragment and also at the same time inject the view model in fragments
没有。由于上面列出的原因。
Also is there any need for the bindViewModelFactory method as it seems to have no effect on app even without this method.
它没有任何效果,因为(我假设)您没有在任何地方使用 ViewModelFactory
。因为它没有在任何地方被引用,所以这个 viewmodelfactory 的匕首代码是无用的。
@Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
这是 @binds 正在做的事情:
这就是为什么删除它对应用程序没有影响的原因。
那么解决方法是什么?您需要将工厂注入 fragment/activity 并使用工厂
获取视图模型的实例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
是由activity和fragment实现的。所以你有几种创建视图模型的方法:
- activity 中的视图模型
ViewModelProvider(this, YourViewModelFactory)
- 片段中的视图模型
ViewModelProvider(this, YourViewModelFactory)
- 片段 (B) 中的视图模型限定为 parent 片段 (A) 并在 A 下的 child 个片段之间共享
ViewModelProvider(requireParentFragment(), YourViewModelFactory)
- 片段中的视图模型范围为 parent activity 并在 activity 下跨片段共享
ViewModelProvider(requireActivity(), YourViewModelFactory)
One workaround could be to make a BaseFragment for fragments which shares the common viewmodel, but that will again include the boilerplate code and also I am not a great fan of BaseFragment/BaseActivity
是的,这确实是个坏主意。解决方案是使用 requireParentFragment()
和 requireActivity()
来获取 viewmodel 实例。但是您将在每个具有视图模型的 fragment/activity 中编写相同的内容。为避免这种情况,您可以在基础 fragment/activity class 中抽象出此 ViewModelProvider(x, factory)
部分,并将工厂注入基础 classes,这将简化您的 child fragment/activity 代码如下:
class MyFragment: BaseFragment() {
private val vm: ChartViewModel by bindViewModel() // or bindParentFragmentViewModel() or bindActivityViewModel()