How to avoid binding FragmentX to Fragment
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.
Those files look like this
@Module
abstract class FragmentXModule {
@Binds
@FragmentScoped
internal abstract fun bindFragment(fragmentX: FragmentX): Fragment
}
This is repeating over and over again.
Is it possible to avoid those file creation repeat and group all bindings in one file?
Update: It's actually much easier!
I wrote quite a long answer on how it's not really possible without duplicating all the code, when in fact this is not the case. You can read the old answer below for reference, I will just include the simple solution here on top.
Turns out there is a good reason why there is an
AndroidInjector.Factory
interface and aAndroidInjector.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.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.We can use this Builder instead of
AndroidInjector.Builder
which allows us to bind a supertype as well.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
andMainActivity
with minimal effort.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)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 yourSubcomponent.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.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
orAndroidInjection.inject()
anymore...Why we can't use
@ContributesAndroidInjector
@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.The AndroidInjection already binds
SpecificActivity
itself to the graph, but it will not allow us to treat it as anActivity
. To do this, we will have to use our own classes and also bind it as anActivity
. Maybe Dagger will get a feature like this in the future.What modifications?
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)With this setup we can now safely inject
MainActivity
, even bind it toActivity
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.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 interfaceAwesomeActivityInjector
. I will keep this short and simple, but you can read AndroidInjector for more information—which I basically just copied.I added one modification though.
activity(activity : Activity)
will allow us to bind our activity to the component as well.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.
Adapting our components
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
withAwesomeActivityInjector
.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.
Be careful with Dagger and Kotlin when using Wildcards!
Using our
AwesomeActivityInjector
Now with all of this we got ourselves a working injection. We can now inject both,
Activity
as well asMainActivity
.This code works for Activities and can be similarly expanded to also cover Fragments.
Final Words
Yes, this is probably overkill. At least I would say so if we just want to bind
MainActivity
as anActivity
. I wrote this answer to give an example of how AndroidInjection works and how one could adapt and modify it.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.