Dagger2继承的子组件多绑定

时间:2019-08-23 21:45:17

标签: android kotlin dagger-2 dagger

希望日复一日研究这个非常有趣的主题“继承的子组件multibindings,您可以在这里找到Inherited subcomponent multibindings,这是该页面的最后一个主题。

根据官方文档:

  

subComponent可以将元素添加到其父级绑定的multibound集或映射中。发生这种情况时,集合或映射将根据注入的位置而有所不同。当将其注入到subcomponent上定义的绑定中时,它具有由子组件的multibindings定义的值或条目以及由父组件的multibindings定义的值或条目。当将其注入到在父组件上定义的绑定中时,它仅具有在那里定义的值或条目。

换句话说。如果父Component有一个multibound set or map且一个child component已绑定到该多绑定,则这些绑定将链接/添加到父映射中,具体取决于这些绑定在匕首作用域中的注入位置如果有的话。

这是问题所在。

在Android应用程序中使用dagger version 2.24使用Kotlin。我有一个ApplicationComponent使用了新的@Component.Factory方法。 ApplicationComponent已安装AndroidSupportInjectionModule

我也有一个使用新ActivitySubComponent方法的@Component.Factory,并且使用Module注释的subComponents参数将该方法链接到AppComponent。  这个ActivitySubComponent通过这样的绑定提供了ViewModel

@Binds
@IntoMap
@ViewModelKey(MyViewModel::class)
fun provideMyViewModel(impl: MyViewModel): ViewModel

@ViewModelKey是自定义的匕首注释。

我也有一个这样实现的ViewModelFactory。

@Singleton
class ViewModelFactory @Inject constructor(
    private val viewModelsToInject: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T = 
    viewModelsToInject[modelClass]?.get() as T
}

普通的ViewModelFactory

这里的区别是,我在AppComponents模块之一中提供了此ViewModelFactory。但是ActivitySubComponent中的绑定viewModel不会添加到AppComponent的ViewModelFactory地图中。

换句话说。该文档所描述的内容根本没有发生。

如果将viewModels绑定移动到任何AppComponent模块中,则所有工作正常。

你知道这里会发生什么吗?

3 个答案:

答案 0 :(得分:1)

您将ViewModelProvider.Factory的范围定为@Singleton。这样可以确保将其创建并保留在@Singleton组件中。

删除范围是安全的,因为它不保留任何状态,并且可以使用正确的绑定集在需要的地方创建工厂。

答案 1 :(得分:1)

文档是准确的。尽管Dagger确实按照生成Set / Map Multibindinds时所描述的方式进行操作,但由于您处于极端情况,因此Dagger的工作方式有所不同。

示例解释

假设您具有以下模块:

/**
 * Binds ViewModelFactory as ViewModelProvider.Factory.
 */
@Module
abstract class ViewModelProviderModule {

    @Binds abstract fun bindsViewModelFactory(impl: ViewModelFactory): ViewModelProvider.Factory
}

/**
 * For the concept, we bind a factory for an AppViewModel 
 * in a module that is included directly in the AppComponent.
 */ 
@Module
abstract class AppModule {

    @Binds @IntoMap
    @ViewModelKey(AppViewModel::class)
    abstract fun bindsAppViewModel(vm: AppViewModel): ViewModel
}

/**
 * This module will be included in the Activity Subcomponent.
 */
@Module
abstract class ActivityBindingsModule {

    @Binds @IntoMap
    @ViewModelKey(MyViewModel::class)
}

/**
 * Generate an injector for injecting dependencies that are scoped to MyActivity.
 * This will generate a @Subcomponent for MyActivity.
 */
@Module
abstract class MyActivityModule {

    @ActivityScoped
    @ContributesAndroidInjector(modules = [ActivityBindingsModule::class])
    abstract fun myActivity(): MyActivity
}

如果要将ViewModelProvider.Factory注入应用程序类,那么Map<Class<out ViewModel>, Provider<ViewModel>>应该提供什么?由于您要插入AppComponent的范围,因此该ViewModelFactory将只能创建AppViewModel的实例,而不能创建MyViewModel的实例,因为绑定是在子组件中定义的。

如果您在ViewModelProvider.Factory中注入MyActivity,则由于我们都在AppComponentMyActivitySubcomponent的范围内,因此将新建一个ViewModelFactory能够创建AppViewModelMyViewModel的两个实例。

这里的问题是 ViewModelFactory被标注为@Singleton 。因此,将创建ViewModelFactory的单个实例并将其保存在AppComponent中。由于MainActivityComponentAppComponent的子组件,因此它将继承该单例,并且不会创建包含具有2个ViewModel绑定的Map的新实例。

以下是发生的一系列事情:

  1. MyApplication.onCreate()被调用。您创建了DaggerAppComponent
  2. DaggerAppComponent的构造函数中,Dagger构建了一个Map,该映射具有从Class<AppViewModel>Provider<AppViewModel>的映射。
  3. 它将Map用作ViewModelFactory的依赖项,然后将其保存在组件中。
  4. 注入活动时,Dagger会检索对该ViewModelFactory的引用,然后直接注入(它不会修改地图)。

如何使它按预期工作

  1. 删除@Singleton上的ViewModelFactory注释。这样可以确保Dagger每次需要时都创建一个ViewModelFactory的新实例。这样,ViewModelFactory将收到包含两个绑定的Map。
  2. @Singleton上的ViewModelFactory注释替换为@Reusable。这样,Dagger将尝试重用ViewModelProvider的实例,而不能保证在整个应用程序中使用唯一的实例。如果检查生成的代码,则会注意到AppComponentMyActivitySubcomponent的每个实例中都保留了一个不同的实例。

答案 2 :(得分:1)

问题

这是因为在AppComponent中创建了地图,并且您正在将ViewModel添加到子组件中的地图。换句话说,当应用启动时,它会使用ViewModelFactory创建地图。但是MyViewModel不会添加到地图,因为它存在于子组件中。

我为此苦苦挣扎了好几天,当您说匕首文档对它的概述不太好时,我同意了。直观上,您认为AppComponent中声明的依赖项可用于所有子组件。 但这不适用于Map Multibindings。或至少不是完全正确。 MyViewModel未添加到地图,因为创建它的工厂位于AppComponent内部。


解决方案(至少一种可能的解决方案)

无论如何,我最终实现的解决方案是我创建了特定于功能的ViewModelFactory。因此,对于每个子组件,我创建了一个ViewModelFactory,它具有自己的 Key 和一组多重绑定。

示例

我制作了一个示例存储库,您可以查看:https://github.com/mitchtabian/DaggerMultiFeature/

签出分支:“ feature-specific-vm-factories” 。我将确保按原样离开该分支,但将来可能会更改主分支。

enter image description here