【问题标题】:Dagger2 Inherited subcomponent multibindingsDagger2 继承的子组件多绑定
【发布时间】:2019-12-29 04:41:18
【问题描述】:

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

根据官方文档:

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

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

这就是问题所在。

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

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

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

@ViewModelKey 是自定义 Dagger 注释。

我也有一个这样实现的 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 中的绑定视图模型没有被添加到 AppComponent 的 ViewModelFactory 映射中。

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

如果我将 viewModels 绑定移动到任何 AppComponent 模块中,那么一切正常。

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

【问题讨论】:

  • 嘿,你找到解决办法了吗?
  • 我也在为这种情况而苦苦挣扎。
  • 我遇到了类似的情况,我不得不将ViewModelFactory 设置移动到每个子组件而不是父组件(使用模块继承)。我也很想知道为什么它不像记录的那样工作。

标签: android kotlin dagger-2 dagger


【解决方案1】:

您将ViewModelProvider.Factory 限定为@Singleton。这确保了它会被创建并保存在@Singleton 组件中。

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

【讨论】:

    【解决方案2】:

    文档是准确的。虽然 Dagger 在生成 Set/Map Multibindinds 时确实按照描述的方式运行,但它的工作方式不同,因为您处于极端情况。

    举例说明

    假设您有以下模块:

    /**
     * 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&lt;Class&lt;out ViewModel&gt;, Provider&lt;ViewModel&gt;&gt; 中应该提供什么?由于您在AppComponent 的范围内注入,因此ViewModelFactory 将只能创建AppViewModel 的实例,而不能创建MyViewModel,因为绑定是在子组件中定义的。

    如果您在MyActivity 中注入ViewModelProvider.Factory,那么由于我们都在AppComponentMyActivitySubcomponent 的范围内,那么新创建的ViewModelFactory 将能够创建AppViewModelMyViewModel.

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

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

    1. MyApplication.onCreate() 被调用。你创建你的DaggerAppComponent
    2. DaggerAppComponent 的构造函数中,Dagger 构建了一个映射,其中包含Class&lt;AppViewModel&gt;Provider&lt;AppViewModel&gt; 的映射。
    3. 它使用该 Map 作为 ViewModelFactory 的依赖项,然后将其保存在组件中。
    4. 注入 Activity 时,Dagger 检索到该 ViewModelFactory 的引用并直接注入(它不会修改 Map)。

    你可以做些什么来让它按预期工作

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

    【讨论】:

    • 你的想法是对的,但这不是因为单例范围。这是因为他在 AppComponent 中创建了地图,但他试图将 ViewModel 添加到子组件中的地图中。有关更多信息,请参阅我的解释。
    【解决方案3】:

    问题

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

    我为此苦苦挣扎了好几天,我同意你说匕首文档没有很好地概述这一点。直觉上,您认为 AppComponent 中声明的依赖项可用于所有子组件。 但 Map Multibindings 并非如此。 或者至少不完全正确。 MyViewModel 未添加到地图中,因为创建它的工厂存在于 AppComponent 中。


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

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

    示例

    我做了一个示例 repo,你可以看看:https://github.com/mitchtabian/DaggerMultiFeature/

    检查分支:“feature-specific-vm-factories”。我会确保按原样保留那个分支,但我可能会在将来的某个时间更换主分支。

    【讨论】:

      【解决方案4】:

      当 Dagger 实例化你的 ViewModelFactory 时,它需要在它的 构造函数。并且对于地图中的所有 key/ViewModel 对,Dagger 必须知道如何 在 CURRENT COMPONENT 级别构建它们。

      在您的情况下,您的 ViewModelFactory 仅在 AppComponent 级别定义,因此地图 Dagger 用于注入它不包含在其子组件中定义的任何 ViewModel。

      为了让 Dagger 表现出您期望的继承的子组件绑定行为,您必须让您的子组件再次提供 ViewModelFactory,并将您的片段/活动与子组件一起注入。

      当 Dagger 为您的子组件构造 ViewModelFactory 时,它可以访问您的 ViewModels 定义在子组件中,因此可以将它们添加到用于注入工厂的地图中。

      您可以参考第 10 页的 Dagger 教程: https://dagger.dev/tutorial/10-deposit-after-login

      请注意本教程如何使用子组件提供的 CommandRouter 继承的多重绑定。

      【讨论】:

        猜你喜欢
        • 2018-07-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-07-20
        • 2013-06-21
        相关资源
        最近更新 更多