android - 如何使用 Dagger 2 在 ViewPager 中注入相同片段的 ViewModel
问题描述
我正在尝试将 Dagger 2 添加到我的项目中。我能够为我的片段注入 ViewModels(AndroidX 架构组件)。
我有一个ViewPager,它有 2 个相同片段的实例(每个选项卡只有一个小的更改),并且在每个选项卡中,我正在观察LiveData
更新数据更改(来自 API)。
问题是,当 api 响应到来并更新时LiveData
,当前可见片段中的相同数据正在发送给所有选项卡中的观察者。(我认为这可能是因为 的范围ViewModel
)。
这就是我观察数据的方式:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
activityViewModel.expenseList.observe(this, Observer {
swipeToRefreshLayout.isRefreshing = false
viewAdapter.setData(it)
})
....
}
我正在使用这个类来提供ViewModel
:
class ViewModelProviderFactory @Inject constructor(creators: MutableMap<Class<out ViewModel?>?, Provider<ViewModel?>?>?) :
ViewModelProvider.Factory {
private val creators: MutableMap<Class<out ViewModel?>?, Provider<ViewModel?>?>? = creators
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel?>? = creators!![modelClass]
if (creator == null) { // if the viewmodel has not been created
// loop through the allowable keys (aka allowed classes with the @ViewModelKey)
for (entry in creators.entries) { // if it's allowed, set the Provider<ViewModel>
if (modelClass.isAssignableFrom(entry.key!!)) {
creator = entry.value
break
}
}
}
// if this is not one of the allowed keys, throw exception
requireNotNull(creator) { "unknown model class $modelClass" }
// return the Provider
return try {
creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
companion object {
private val TAG: String? = "ViewModelProviderFactor"
}
}
我ViewModel
像这样绑定我的:
@Module
abstract class ActivityViewModelModule {
@MainScope
@Binds
@IntoMap
@ViewModelKey(ActivityViewModel::class)
abstract fun bindActivityViewModel(viewModel: ActivityViewModel): ViewModel
}
我正在使用@ContributesAndroidInjector
这样的片段:
@Module
abstract class MainFragmentBuildersModule {
@ContributesAndroidInjector
abstract fun contributeActivityFragment(): ActivityFragment
}
我将这些模块添加到我的MainActivity
子组件中,如下所示:
@Module
abstract class ActivityBuilderModule {
...
@ContributesAndroidInjector(
modules = [MainViewModelModule::class, ActivityViewModelModule::class,
AuthModule::class, MainFragmentBuildersModule::class]
)
abstract fun contributeMainActivity(): MainActivity
}
这是我的AppComponent
:
@Singleton
@Component(
modules =
[AndroidSupportInjectionModule::class,
ActivityBuilderModule::class,
ViewModelFactoryModule::class,
AppModule::class]
)
interface AppComponent : AndroidInjector<SpenmoApplication> {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
}
我正在扩展DaggerFragment
和注入ViewModelProviderFactory
这样的:
@Inject
lateinit var viewModelFactory: ViewModelProviderFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
....
activityViewModel =
ViewModelProviders.of(this, viewModelFactory).get(key, ActivityViewModel::class.java)
activityViewModel.restartFetch(hasReceipt)
}
这key
两个片段都会有所不同。
如何确保只有当前片段的观察者得到更新。
编辑 1 ->
我添加了一个带有错误的示例项目。似乎只有在添加自定义范围时才会出现问题。请在此处查看示例项目:Github 链接
master
分支有问题的应用程序。如果您刷新任何选项卡(滑动以刷新),更新的值将反映在两个选项卡中。仅当我向其添加自定义范围时才会发生这种情况(@MainScope
)。
working_fine
分支具有相同的应用程序,没有自定义范围并且工作正常。
如果问题不清楚,请告诉我。
解决方案
我想回顾一下最初的问题,这里是:
我目前正在使用 working
fine_branch
,但我想知道,为什么使用范围会破坏这一点。
根据我的理解,您的印象是,仅仅因为您试图获取ViewModel
使用不同键的实例,那么您应该提供不同的实例ViewModel
:
// in first fragment
ViewModelProvider(...).get("true", PagerItemViewModel::class.java)
// in second fragment
ViewModelProvider(...).get("false", PagerItemViewModel::class.java)
现实,有点不同。如果您输入以下登录片段,您会看到这两个片段使用的是完全相同的实例PagerItemViewModel
:
Log.i("vvv", "${if (oneOrTwo) "one:" else "two:"} viewModel hash is ${viewModel.hashCode()}")
让我们深入了解为什么会发生这种情况。
在内部ViewModelProvider#get()
将尝试获取PagerItemViewModel
from a的实例,该实例ViewModelStore
基本上是String
to的映射ViewModel
。
当FirstFragment
请求的实例PagerItemViewModel
为map
空时,因此mFactory.create(modelClass)
被执行,最终在ViewModelProviderFactory
. creator.get()
最终DoubleCheck
使用以下代码调用:
public T get() {
Object result = instance;
if (result == UNINITIALIZED) { // 1
synchronized (this) {
result = instance;
if (result == UNINITIALIZED) {
result = provider.get();
instance = reentrantCheck(instance, result); // 2
/* Null out the reference to the provider. We are never going to need it again, so we
* can make it eligible for GC. */
provider = null;
}
}
}
return (T) result;
}
instance
是 now ,因此会创建null
一个新的实例PagerItemViewModel
并保存在其中instance
(参见 // 2)。
现在完全相同的过程发生在SecondFragment
:
- 片段要求一个实例
PagerItemViewModel
map
now不为空,但不包含withPagerItemViewModel
key的实例false
- 一个新的实例
PagerItemViewModel
被发起创建通过mFactory.create(modelClass)
- 内部
ViewModelProviderFactory
执行达到creator.get()
谁的实现是DoubleCheck
现在,关键时刻。这与请求DoubleCheck
时用于创建实例的实例相同。为什么是同一个实例?因为您已将范围应用于提供程序方法。DoubleCheck
ViewModel
FirstFragment
( if (result == UNINITIALIZED)
// 1) 正在评估为 false 并且完全相同的实例ViewModel
被返回给调用者 - SecondFragment
。
现在,两个片段都使用相同的实例,ViewModel
因此它们显示相同的数据非常好。
推荐阅读
- reactjs - React 中的样式化组件:TypeError:无法读取未定义的属性“颜色”
- neo4j - neo4j中如何匹配满足多重间接关系的记录?
- c# - 使用 PackageReference 时如何控制安装的依赖版本
- java - java - 为什么使用RoundingMode时java BigDecimal除法结果与双除法不同
- html - 选中时如何更改单选按钮中的背景颜色?
- hive - 在 hive 中命名案例的结果
- python - 用于线性回归的具有多个系列的 pandas 数据帧的 Y 截距
- python - Mac 上的 Python // 更改工作目录
- python - 如何按字母顺序将项目放在桌子上的循环中?
- python - 如何使用 python 代码创建表格,然后在浏览器中查看?