【问题标题】:Use the same instance of view model in multiple fragments using dagger2使用 dagger2 在多个片段中使用相同的视图模型实例
【发布时间】:2020-05-23 13:27:01
【问题描述】:

我在我的项目中仅使用 dagger2(不是 dagger-android)。使用多重绑定注入 ViewModel 工作正常。但是有一个问题,以前没有 dagger2 我在多个片段的活动中使用相同的视图模型实例(使用 fragment-ktx 方法 activityViewModels()),但现在由于 dagger2 正在注入视图模型,它总是给出每个片段的视图模型的新实例(使用每个片段中的 hashCode 检查),这只是破坏了片段之间使用视图模型的通信。

片段和视图模型代码如下:

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。

这是为 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);
  }
}

【问题讨论】:

  • 简单来说,你为什么不试试“activityViewModels”呢?您希望 2 个片段能够访问同一个 ViewModel 实例,对吗?

标签: android android-fragments viewmodel dagger-2 android-mvvm


【解决方案1】:

问题是当你像这样注入视图模型时

class MyFragment: Fragment() {
    @Inject lateinit var chartViewModel: ChartViewModel

dagger 只是创建一个新的视图模型实例。没有 viewmodel-fragment-lifecycle 魔法发生,因为这个 viewmodel 不在活动/片段的 viewmodelstore 中,也不是由您创建的 viewmodelfactory 提供。在这里,您可以将视图模型视为真正的任何普通类。举个例子:

class MyFragment: Fragment() {
    @Inject lateinit var anything: AnyClass
}
class AnyClass @Inject constructor(private val repository: ChartRepository) {
   //live data code...
}

您的 viewmodel 等效于这个 AnyClass,因为 viewmodel 不在 viewmodelstore 中,并且不在片段/活动的生命周期范围内。

有什么方法可以为多个片段实现相同的视图模型实例,同时在片段中注入视图模型

没有。由于上述原因。

还需要 bindViewModelFactory 方法,因为即使没有此方法,它似乎对应用也没有影响。

它没有任何效果,因为(我假设)您没有在任何地方使用ViewModelFactory。由于它没有在任何地方引用,因此 viewmodelfactory 的这个匕首代码是无用的。

@Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory

@binds 正在做的事情是:12

这就是为什么删除它对应用没有影响。

那么解决方法是什么?您需要将工厂注入到片段/活动中并使用工厂获取视图模型的实例

class MyFragment: Fragment() {
    @Inject lateinit var viewModelFactory: ViewModelFactory

    private val vm: ChartViewModel by lazy {
        ViewModelProvider(X, YourViewModelFactory).get(ChartViewModel::class.java)
    }

这里的X 是什么? X 是ViewModelStoreOwnerViewModelStoreOwner 是下面有视图模型的东西。 ViewModelStoreOwner 由activity和fragment实现。所以你有几种创建视图模型的方法:

  1. 活动中的视图模型
ViewModelProvider(this, YourViewModelFactory)
  1. 片段中的视图模型
ViewModelProvider(this, YourViewModelFactory)
  1. 片段 (B) 中的视图模型作用于父片段 (A) 并在 A 下的子片段之间共享
ViewModelProvider(requireParentFragment(), YourViewModelFactory)
  1. 片段中的视图模型限定为父 Activity 并在 Activity 下的片段之间共享
ViewModelProvider(requireActivity(), YourViewModelFactory)

一种解决方法可能是为共享通用视图模型的片段制作 BaseFragment,但这将再次包含样板代码,而且我不是 BaseFragment/BaseActivity 的忠实粉丝

是的,这确实是个坏主意。解决方案是使用requireParentFragment()requireActivity() 来获取viewmodel 实例。但是您将在每个具有视图模型的片段/活动中编写相同的内容。为避免这种情况,您可以在基片段/活动类中抽象出这个 ViewModelProvider(x, factory) 部分,并将工厂注入基类中,这将简化您的子片段/活动代码,如下所示:

class MyFragment: BaseFragment() {

    private val vm: ChartViewModel by bindViewModel() // or bindParentFragmentViewModel() or bindActivityViewModel()

【讨论】:

  • 谢谢@sonnet,工作就像一个魅力。值得一提的是,我使用的是fragment-ktx 函数activityViewModels,而不是使用 ViewModelProvider 初始化视图模型,它更简洁。
  • 是的,这是另一种方式,相当于4号。另外,如果你使用导航组件,那么你也可以使用by navgraphviewmodels,相当于no.3
  • 我设法用ViewModelProviderlazy 实现了这一点,但是当我使用fragment-ktxviewModels 时,我无法在Activity 和Fragment 之间进行通信@denvercoder9 你有什么想法吗?在活动中:val viewModel : ShopViewModel by viewModels{ viewModelFactory } 在片段中:private val viewModel: ShopViewModel by viewModels{ requireActivity() viewModelFactory }
  • 我相信有一个扩展函数叫做activityViewModels()。你可以使用stackoverflow.com/a/56811245/2235972
  • @AndroidDev123 所以假设你有一个父片段 A,在 A 下你有其他三个子片段 B、C 和 D。如果你在 B、C 和 D 中执行 requireParentFragment(),这意味着视图模型的范围是片段 A 的生命周期,片段 A 是 B、​​C 和 D 的父片段。只有当父片段 A 被销毁时,视图模型才会被销毁。
【解决方案2】:

如果片段具有相同的父活动,则可以在片段之间共享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")
  }
}

片段二

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")

 }
}

【讨论】:

  • 谢谢,但我想用匕首注入视图模型。
  • 哦,我找到了this我认为你可以应用相同的概念
  • 谢谢!另请参阅类似的解决方案:blog.mindorks.com/…
【解决方案3】:

将您的 ViewModel 添加为 PostListViewModelViewModelModule

@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 Dagger2Check github

【讨论】:

  • 谢谢,但我想用匕首注入视图模型。
  • 这是 Dagger 注入人,这就是你如何用 Dagger2 注入 viewModel。检查我提到的链接并阅读它
  • 这不起作用,仍然获得不同的视图模型实例,并且不推荐使用 ViewModelProviders。如果我们不能通过字段注入来注入视图模型,那么最好在共享视图模型的情况下停止存储库标签处的 DI,并在视图模型中使用存储库的手动实例化并使用视图模型或活动视图模型(来自 ktx 库)在活动/片段中实例化视图模型。
  • 是的,你是对的。已弃用我编辑了答案。使用惰性注入。检查这个链接proandroiddev.com/…它真的会帮助你@deepakkumar
猜你喜欢
  • 1970-01-01
  • 2020-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-20
  • 1970-01-01
相关资源
最近更新 更多