如何避免将FragmentX绑定到Fragment [英] How to avoid binding FragmentX to Fragment

查看:65
本文介绍了如何避免将FragmentX绑定到Fragment的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何避免将FragmentX绑定到片段

How to avoid binding FragmentX to Fragment

我有几个文件,我只是在其中声明FragmentXFragment(或ActivityXActivity)的绑定,以便能够将对象作为基类依赖项注入.

I am having several files where I just declare the binding of a FragmentX to a Fragment (or ActivityX to Activity) in order to be able to inject the objects as base class dependencies.

这些文件如下

@Module
abstract class FragmentXModule {

    @Binds
    @FragmentScoped
    internal abstract fun bindFragment(fragmentX: FragmentX): Fragment
}

这是一遍又一遍的重复.

This is repeating over and over again.

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

推荐答案

更新:它实际上要简单得多!

我写了一个很长的答案,就是如果不复制所有代码,实际上是不可能的,但实际上并非如此.您可以阅读下面的旧答案以供参考,我将在此处包括简单的解决方案.

有一个很好的理由说明为什么有一个AndroidInjector.Factory接口和一个AndroidInjector.Builder类.我们可以自己实现接口,而使用我们的构建器!这样,我们仍然可以继续使用Dagger Android部件来注入我们的组件,而无需自己从头开始创建东西.

Turns out there is a good reason why there is an AndroidInjector.Factory interface and a AndroidInjector.Builder class. We can just implement the interface ourselves and use our builder instead! This way we can still keep using the Dagger Android parts to inject our components, with no need to create things from scratch ourselves.

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

Different components can use different builders, in the end they just have to implement AndroidInjector.Factory<T>. The following builder shows a generic approach to bind the type and one supertype.

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>
}

我们可以使用此Builder代替AndroidInjector.Builder,这也允许我们绑定超类型.

We can use this Builder instead of AndroidInjector.Builder which allows us to bind a supertype as well.

@Subcomponent
interface MainActivitySubcomponent : AndroidInjector<MainActivity> {

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

}

使用上面的构建器,我们可以将我们不想注入的基本类型声明为我们的第一个类型参数,以及将要注入的实际类型.这样,我们可以以最小的努力为ActivityMainActivity提供服务.

With the builder above we can declare the base type that we wan't to inject as our first type parameter along with the actual type that we are going to inject. This way we can provide both, Activity and MainActivity with minimal effort.

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

Is it possible to avoid those file creation repeat and group all bindings in one file?

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

There are basically just 2 ways to add a binding to Dagger. One is the module approach that you took, which requires to add a module with the correct binding, the other is to bind the instance directly to the Component.Builder. (Yes, you could also add a module with a constructor argument to the builder, but this has the same effect and leads to even more code)

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

If you're not using AndroidInjection but are still manually creating every component, then all you have to do is add a @BindsInstance abstract fun activity(instance: Activity) to your Subcomponent.Builder and pass it in while constructing the component. If you want to make use of AndroidInjection then we have to do a bit more, which I will detail in the following post.

在您的特定用例中,我将继续做您现在正在做的事情,但是我将向您展示另一种方法.不利之处是我们不能再使用@ContributesAndroidInjectorAndroidInjection.inject() ...

In your specific use case I would just keep doing what you're doing now, but I will show another way how you could handle this. The downside here is that we can't use @ContributesAndroidInjector or AndroidInjection.inject() anymore...

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

@ContributesAndroidInjector will generate the annoying-to-write boilerplate for us, but we need to modify this generated code. In particular, we need to use different interfaces that our components implement and the only option to do so is to write the boilerplate ourselves. And yes, of course we could create our own AnnotationProcessor that generates Boilerplate like we want it, but this is out of the scope of this answer.

以下部分已过时,但我将保留它作为对AndroidInjection中实际发生情况的引用.如果要添加其他绑定,请使用上面介绍的解决方案.

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

The AndroidInjection already binds SpecificActivity itself to the graph, but it will not allow us to treat it as an Activity. To do this, we will have to use our own classes and also bind it as an Activity. Maybe Dagger will get a feature like this in the future.

我们从默认设置开始,这将为我们生成@ContributesAndroidInjector.这是您应该熟悉的一个. (如果您还没有使用AndroidInjection,请放心,我们将在下一步中创建自己的设置)

We start with our default setup which @ContributesAndroidInjector would generate for us. This is the one you should be familiar with. (If you're not using AndroidInjection yet, don't worry, we'll be creating our own setup in the next step)

@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>()

}

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

With this setup we can now safely inject MainActivity, even bind it to Activity in a module, but that's not what we want. We want this binding to be automated. Let's see if we can do better.

如前所述,我们不能使用AndroidInjection.inject().相反,我们需要创建自己的接口.本着众多Android库的精神,我将我的界面称为AwesomeActivityInjector.我将保持简短和简单,但是您可以阅读 AndroidInjector 了解更多信息,我基本上只是复制了该信息.

As previously hinted, we can't use AndroidInjection.inject(). Instead, we need to create our own interfaces. In the spirit of so many Android libraries I will call my interface AwesomeActivityInjector. I will keep this short and simple, but you can read AndroidInjector for more information—which I basically just copied.

不过我添加了一个修改. activity(activity : Activity)将使我们也可以将活动绑定到组件.

I added one modification though. activity(activity : Activity) will allow us to bind our activity to the component as well.

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>
    }
}

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

This is a simple interface that our component and its builder will implement, in the very same way as AndroidInjection does it currently. By using a common interface on our subcomponents we can use it to create our components and inject our activities.

现在有了接口,我们切换出子组件和模块以使用它.这仍然是相同的代码,我只是将AndroidInjector替换为AwesomeActivityInjector.

Now that we have our interface, we switch out our Subcomponent and Module to use that instead. This is still the same code, I just replaced AndroidInjector with AwesomeActivityInjector.

@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>()

}

准备注射

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

Preparing for injection

Now with everything set up, all we need to do is add some code to inject our Activity. The AndroidInjection part does this more nicely by having the application implement an interface, etc. You can look up how this is done, but for now I will just directly inject our factories and use them.

使用通配符时,请小心使用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)
    }
}

使用我们的AwesomeActivityInjector

现在,所有这些我们都得到了有效的注射.现在,我们可以同时注入ActivityMainActivity.

Using our AwesomeActivityInjector

Now with all of this we got ourselves a working injection. We can now inject both, Activity as well as 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)
    }
}

此代码适用于活动",并且可以类似地扩展为涵盖片段.

This code works for Activities and can be similarly expanded to also cover Fragments.

是的,这可能太过分了.至少我会这样说,如果我们只想将MainActivity绑定为Activity.我写了这个答案,以举例说明AndroidInjection的工作方式以及如何适应和修改它.

Yes, this is probably overkill. At least I would say so if we just want to bind MainActivity as an Activity. I wrote this answer to give an example of how AndroidInjection works and how one could adapt and modify it.

使用类似的方法,您还可以拥有 PerScreen 范围,该范围可以适应方向更改,或者具有 UserScope ,其寿命比Application短,但比Activity长.目前,这一切都无法与AndroidInjection一起使用,并且需要如上所示的自定义代码.

With a similar approach you can also have PerScreen scopes that survive orientation changes, or a UserScope that lives shorter than the Application, but longer than an Activity. None of this will work currently out of the box with AndroidInjection and requires custom code like shown above.

这篇关于如何避免将FragmentX绑定到Fragment的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆