如何避免将 FragmentX 绑定到 Fragment

Mar*_*tek 2 android dependency-injection kotlin dagger-2

如何避免将 FragmentX 绑定到 Fragment

FragmentX我有几个文件,其中我只是声明 a到 a Fragment(或ActivityXto )的绑定Activity,以便能够将对象作为基类依赖项注入。

这些文件看起来像这样

@Module
abstract class FragmentXModule {

    @Binds
    @FragmentScoped
    internal abstract fun bindFragment(fragmentX: FragmentX): Fragment
}
Run Code Online (Sandbox Code Playgroud)

这是一遍又一遍地重复。

是否可以避免这些文件创建重复并将所有绑定分组到一个文件中?

Dav*_*jak 5

更新:实际上更容易!

我写了一个很长的答案,说明如果不复制所有代码就不可能真正实现,而事实上情况并非如此。您可以阅读下面的旧答案以供参考,我将在顶部包含简单的解决方案。

事实证明,存在AndroidInjector.Factory接口和AndroidInjector.Builder类是有充分理由的。我们可以自己实现接口并使用我们的构建器!这样我们仍然可以继续使用 Dagger Android 部件来注入我们的组件,而不需要我们自己从头开始创建东西。

不同的组件可以使用不同的构建器,最终他们只需要实现AndroidInjector.Factory<T>. 以下构建器显示了绑定类型和一个超类型的通用方法。

abstract class SuperBindingAndroidInjectorBuilder<S, T : S> : AndroidInjector.Factory<T> {

    override fun create(instance: T): AndroidInjector<T> {
        seedInstance(instance)
        superInstance(instance)
        return build()
    }

    // bind the object the same way `AndroidInjector.Builder` does
    @BindsInstance
    abstract fun seedInstance(instance: T)

    // _additionally_ bind a super class!
    @BindsInstance
    abstract fun superInstance(instance: S)

    abstract fun build(): AndroidInjector<T>
}
Run Code Online (Sandbox Code Playgroud)

我们可以使用这个 Builder,而不是AndroidInjector.Builder它也允许我们绑定超类型。

@Subcomponent
interface MainActivitySubcomponent : AndroidInjector<MainActivity> {

    @Subcomponent.Builder
    abstract class Builder : SuperBindingAndroidInjectorBuilder<Activity, MainActivity>()

}
Run Code Online (Sandbox Code Playgroud)

使用上面的构建器,我们可以声明我们不想注入的基类型作为我们的第一个类型参数以及我们要注入的实际类型。这样我们就可以以最小的努力Activity同时提供这两种服务MainActivity


是否可以避免这些文件创建重复并将所有绑定分组到一个文件中?

基本上只有两种方法可以将绑定添加到 Dagger。一种是您采用的模块方法,它需要添加具有正确绑定的模块,另一种是将实例直接绑定到Component.Builder. (是的,您还可以向构建器添加带有构造函数参数的模块,但这具有相同的效果并导致更多代码)

如果您没有使用 AndroidInjection 但仍然手动创建每个组件,那么您所要做的就是向@BindsInstance abstract fun activity(instance: Activity)您的Subcomponent.Builder添加一个并在构造组件时将其传入。如果您想使用 AndroidInjection,那么我们还需要做更多的事情,我将在下面的文章中详细介绍。

在您的具体用例中,我将继续做您现在正在做的事情,但我将向您展示另一种方法,您可以如何处理这个问题。这里的缺点是我们不能使用@ContributesAndroidInjectorAndroidInjection.inject()使用......

为什么我们不能使用@ContributesAndroidInjector

@ContributesAndroidInjector将为我们生成烦人的样板文件,但我们需要修改这个生成的代码。特别是,我们需要使用组件实现的不同接口,唯一的选择就是自己编写样板文件。是的,我们当然可以创建自己的 AnnotationProcessor 来生成我们想要的样板文件,但这超出了本答案的范围。

以下部分已过时,但我将其保留为 AndroidInjection 中实际发生的情况的参考。如果您想添加其他绑定,请使用上面提供的解决方案。

AndroidInjection 已经将SpecificActivity自身绑定到图表,但它不允许我们将其视为Activity. 为此,我们必须使用我们自己的类并将其绑定为Activity. 也许 Dagger 将来会获得这样的功能。

有哪些修改?

@ContributesAndroidInjector我们从为我们生成的默认设置开始。这是你应该熟悉的一个。(如果您尚未使用 AndroidInjection,请不要担心,我们将在下一步中创建我们自己的设置)

@Component(modules = [AndroidSupportInjectionModule::class, ActivityModule::class])
interface AppComponent {

    fun inject(app: App)

    @Component.Builder
    interface Builder {
        @BindsInstance fun app(app: App) : AppComponent.Builder
        fun build(): AppComponent
    }
}

@Module(subcomponents = [MainActivitySubcomponent::class])
internal abstract class ActivityModule {
    @Binds
    @IntoMap
    @ActivityKey(MainActivity::class)
    internal abstract fun bindMainActivityFactory(builder: MainActivitySubcomponent.Builder): AndroidInjector.Factory<out Activity>
}

@Subcomponent
interface MainActivitySubcomponent : AndroidInjector<MainActivity> {

    @Subcomponent.Builder
    abstract class Builder : AndroidInjector.Builder<MainActivity>()

}
Run Code Online (Sandbox Code Playgroud)

通过此设置,我们现在可以安全地注入MainActivity,甚至将其绑定到Activity模块中,但这不是我们想要的。我们希望这种绑定是自动化的。让我们看看是否可以做得更好。

正如前面所暗示的,我们不能使用AndroidInjection.inject(). 相反,我们需要创建自己的界面。本着这么多 Android 库的精神,我将调用我的界面AwesomeActivityInjector。我将保持简短,但您可以阅读AndroidInjector以获取更多信息——我基本上只是复制了它。

不过我添加了一项修改。activity(activity : Activity)也将允许我们将我们的活动绑定到组件。

interface AwesomeActivityInjector<T : Activity> {

    fun inject(instance: T)

    interface Factory<T : Activity> {
        fun create(instance: T): AwesomeActivityInjector<T>
    }

    abstract class Builder<T : Activity> : AwesomeActivityInjector.Factory<T> {
        override fun create(instance: T): AwesomeActivityInjector<T> {
            activity(instance) // bind the activity as well
            seedInstance(instance)
            return build()
        }

        @BindsInstance
        abstract fun seedInstance(instance: T)

        @BindsInstance
        abstract fun activity(instance: Activity)

        abstract fun build(): AwesomeActivityInjector<T>
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个简单的接口,我们的组件及其构建器将实现它,其方式与 AndroidInjection 当前的实现方式完全相同。通过在子组件上使用通用接口,我们可以使用它来创建组件并注入活动。

调整我们的组件

现在我们有了界面,我们将子组件和模块切换为使用它。这仍然是相同的代码,我只是替换AndroidInjectorAwesomeActivityInjector.

@Module(subcomponents = [MainActivitySubcomponent::class])
internal abstract class ActivityModule {
    @Binds
    @IntoMap
    @ActivityKey(MainActivity::class)
    internal abstract fun bindMainActivityFactory(builder: MainActivitySubcomponent.Builder): AwesomeActivityInjector.Factory<out Activity>
}

@Subcomponent
interface MainActivitySubcomponent : AwesomeActivityInjector<MainActivity> {

    @Subcomponent.Builder
    abstract class Builder : AwesomeActivityInjector.Builder<MainActivity>()

}
Run Code Online (Sandbox Code Playgroud)

准备注射

现在一切都设置好了,我们需要做的就是添加一些代码来注入我们的 Activity。AndroidInjection 部分通过让应用程序实现接口等来更好地完成此操作。您可以查看这是如何完成的,但现在我将直接注入我们的工厂并使用它们。

使用通配符时要小心 Dagger 和 Kotlin!

class App : Application() {

    @Inject
    lateinit var awesomeInjectors: Map<Class<out Activity>, @JvmSuppressWildcards Provider<AwesomeActivityInjector.Factory<out Activity>>>
}

object AwesomeInjector {

    fun inject(activity: Activity) {
        val application = activity.application as App

        val factoryProviders = application.awesomeInjectors
        val provider = factoryProviders[activity.javaClass] as Provider<AwesomeActivityInjector.Factory<out Activity>>

        @Suppress("UNCHECKED_CAST")
        val factory = provider.get() as AwesomeActivityInjector.Factory<Activity>
        factory.create(activity).inject(activity)
    }
}
Run Code Online (Sandbox Code Playgroud)

使用我们的AwesomeActivityInjector

现在,有了这一切,我们就得到了有效的注射。我们现在可以同时注入两者Activity以及MainActivity.

class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var mainActivity: MainActivity
    @Inject
    lateinit var activity: Activity

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        AwesomeInjector.inject(this)
    }
}
Run Code Online (Sandbox Code Playgroud)

该代码适用于“活动”,并且可以类似地扩展以涵盖“片段”。

最后的话

是的,这可能有点矫枉过正。至少我会这么说,如果我们只想绑定MainActivityActivity. 我写这个答案是为了举例说明 AndroidInjection 的工作原理以及如何调整和修改它。

通过类似的方法,您还可以拥有能够在方向变化后继续存在的PerScreen作用域,或者比应用程序寿命短但比 Activity 寿命长的UserScope 。目前,这些都无法通过 AndroidInjection 开箱即用,并且需要如上所示的自定义代码。