gpt4 book ai didi

android - 如何使用 Dagger 2 在 ViewPager 中注入(inject)相同 fragment 的 ViewModel

转载 作者:行者123 更新时间:2023-12-04 16:03:26 24 4
gpt4 key购买 nike

我正在尝试将 Dagger 2 添加到我的项目中。我能够为我的 fragment 注入(inject) ViewModels(AndroidX 架构组件)。

我有一个 ViewPager 有 2 个相同 fragment 的实例 (每个选项卡只有微小的变化)并且在每个选项卡中,我观察到 LiveData获取有关数据更改的更新(来自 API)。

问题是当 api 响应到来并更新 LiveData ,当前可见 fragment 中的相同数据正在发送给所有选项卡中的观察者。 (我认为这可能是因为 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"
}
}

我正在绑定(bind)我的 ViewModel像这样:
@Module
abstract class ActivityViewModelModule {
@MainScope
@Binds
@IntoMap
@ViewModelKey(ActivityViewModel::class)
abstract fun bindActivityViewModel(viewModel: ActivityViewModel): ViewModel
}

我正在使用 @ContributesAndroidInjector对于我这样的 fragment :
@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并注入(inject) 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两个 fragment 会有所不同。

如何确保只有当前 fragment 的观察者得到更新。

编辑 1 ->

我添加了一个带有错误的示例项目。似乎只有在添加自定义范围时才会出现问题。请在此处查看示例项目: Github link
master分支有问题的应用程序。如果您刷新任何选项卡(滑动以刷新),更新的值将反射(reflect)在两个选项卡中。仅当我向其添加自定义范围时才会发生这种情况( @MainScope )。
working_fine分支具有相同的应用程序,没有自定义范围并且工作正常。

如果问题不清楚,请告诉我。

最佳答案

我想回顾一下最初的问题,这里是:

I am currently using the working fine_branch, but I want to know, why would using scope break this.



据我了解,您有一个印象,就是因为您试图获取 ViewModel 的实例使用不同的 key ,那么应该为您提供 ViewModel 的不同实例:
// in first fragment
ViewModelProvider(...).get("true", PagerItemViewModel::class.java)

// in second fragment
ViewModelProvider(...).get("false", PagerItemViewModel::class.java)

现实,有点不同。如果您输入以下登录 fragment ,您会看到这两个 fragment 使用的是完全相同的 PagerItemViewModel 实例。 :
Log.i("vvv", "${if (oneOrTwo) "one:" else "two:"} viewModel hash is ${viewModel.hashCode()}")

让我们深入了解为什么会发生这种情况。

内部 ViewModelProvider#get()将尝试获取 PagerItemViewModel 的实例来自 ViewModelStore这基本上是 String 的 map 至 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现在是 null ,因此 PagerItemViewModel 的新实例创建并保存在 instance (见//2)。

现在, SecondFragment 发生了完全相同的过程。 :
  • fragment 请求 PagerItemViewModel 的实例
  • map now 不为空,但不包含 PagerItemViewModel 的实例带 key false
  • PagerItemViewModel 的新实例通过 mFactory.create(modelClass) 发起创建
  • 内部 ViewModelProviderFactory执行达到creator.get()其实现是DoubleCheck

  • 现在,关键时刻。这个 DoubleCheck同一个实例DoubleCheck用于创建 ViewModel FirstFragment 时的实例要求它。为什么是同一个实例?因为您已将范围应用于提供程序方法。
    if (result == UNINITIALIZED) (//1) 正在评估为 false 和 ViewModel 的完全相同的实例正在返回给调用者 - SecondFragment .

    现在,两个 fragment 都使用了 ViewModel 的相同实例。因此,它们显示相同的数据是非常好的。

    关于android - 如何使用 Dagger 2 在 ViewPager 中注入(inject)相同 fragment 的 ViewModel,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59986199/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com