Dagger: [Feature Request] optional provide activity when using ContributesAndroidInjector

Created on 22 Nov 2017  Â·  19Comments  Â·  Source: google/dagger

When using the ContributesAndroidInjector Annotation there's no way to provide the activity itself. In the rare cases I need this it's a pain to always use the "old" approach.

    @Binds
    @IntoMap
    @ActivityKey(MainActivity::class)
    internal abstract fun bindAndroidInjectorFactory(builder: MainActivitySubcomponent.Builder)
            : AndroidInjector.Factory<out Activity>

    @ActivityScope
    @Subcomponent(modules = arrayOf(ActivityModule::class))
    interface MainActivitySubcomponent : AndroidInjector<MainActivity> {
        @Subcomponent.Builder
        abstract class Builder : AndroidInjector.Builder<MainActivity>() {
            abstract fun module(module: ActivityModule): Builder

            override fun seedInstance(instance: MainActivity) {
                module(ActivityModule(instance))
            }
        }
    }

with the module:

@Module
class ActivityModule(private val activity: AppCompatActivity) {
    @Provides
    @ActivityScope
    fun provideActivity(): AppCompatActivity = activity
}

This is a lot of boilerplater why I love the annotation you introduced in 2.11

I would love to see an additional boolean(?) flag in this annotation(may be no IN this annotation but a different one?) which is default false, so that the activity is provided (the code for the subcomponent is generated automatically)

This could be figured out by reading the type of key (@ActivityKey) in the subcomponent

Most helpful comment

Maybe we could have a way to define an abstract generic module and use a type parameter for the concrete activity type. Then you can specify the module and Dagger will implement it for you:

Something like this:

@Module
@ContributesAndroidInjector.GenericModule // with a better name
interface ActivityModule<T extends Activity> {
  @Binds AppCompatActivity bind(T activity);
  @Binds @MySpecialQualifier Context context(T t);
}

You implement it once, include it in all of your @ContributesAndroidInjector declarations, and Dagger handles the rest. cc: @netdpb

All 19 comments

Given that AndroidInjector.Builder#seedInstance is annotated with @BindsInstance, shouldn't it already work? (it should already be equivalent to what you wrote, except for the @ActivityScope but is that really needed?)

It already works, yes, with the concrete type. And you can alias to the
general supertype with a @Binds.

On Wed, Nov 22, 2017, 10:41 AM Thomas Broyer notifications@github.com
wrote:

Given that AndroidInjector.Builder#seedInstance
https://google.github.io/dagger/api/2.13/dagger/android/AndroidInjector.Builder.html#seedInstance-T-
is annotated with @BindsInstance, shouldn't it already work? (it should
already be equivalent to what you wrote, except for the @ActivityScope
but is that really needed?)

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/google/dagger/issues/961#issuecomment-346388158, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAEEEZ5sC-sB1h4D1Mb4w4mhKlkOsXH6ks5s5EDAgaJpZM4QnjuP
.

Thanks

@Module
abstract class ApplicationModule {

    @ContributesAndroidInjector
    abstract fun provideActivity(): MainActivity

    @Binds
    abstract fun provideActivity(mainActivity: MainActivity): AppCompatActivity
}

One additional question:

What if I have 2 activities (lets say MainActivity and WhateverActivity) which both need to provide an AppCompatActivity?

I tried this and I am (of course) running into the issue that AppCompatActivity is mutliple times bound.

Any ideas on how to solve this?

The bindings for MainActivity and WhateverActivity should be in different components so it shouldn't be an issue, right?

@ronshapiro Indeed, that could work if the subcomponents were being written manually

However, it is rather impossible with the @ContributesAndroidInjector annotation, which makes this quite tricky

@kingsleyadio not very hard with @ContributesAndroidInjector. Simplest way is to add a component-local module like so:

@Module
abstract class ApplicationModule {

    @ContributesAndroidInjector(modules = [MainActivityModule::class])
    abstract fun provideActivity(): MainActivity
}

@Module
abstract class MainActivityModule {
    @Binds
    abstract fun provideActivity(mainActivity: MainActivity): AppCompatActivity
}

Then you have AppCompatActivity bound in the scope of the MainActivity subcomponent IIRC

@liminal I agree this will work, and I wouldn't mind if I have to do this once so that it applies to all the activities provided by the module (the ApplicationModule using your example).

However, the issue is having to implement this extra module (which basically does the same thing) for every new activity/fragment that is subscribed to the graph via @ContributesAndroidInjector

How about an annotation param in @ContributesAndroidInjector.

@Module
abstract class ApplicationModule {

    @ContributesAndroidInjector(additionalBinds = [Activity::class, Interface::class, ...])
    abstract fun provideActivity(): MainActivity
}
class MainActivity : AppCompatActivity, Interface {
}

With this "additionalBinds" parameter in @ContributesAndroidInjector we can specify the addtional binds functions getting created.

The benefit of this "additionalBinds" parameter is, that we are not limited to the supertypes of specified classes.

Hey @Bodo1981. That's a good suggestion.
Perhaps, it could even be used as a meta-annotation, so that we can do something like:

@ContributesAndroidInjector(additionalBinds = [Activity::class, AppCompatActivity::class, ...])
annotation class ContributesActivity

So, we can simply use the @ContributesActivity whenever those additional bindings are required

@Bodo1981 How would that work with Qualifiers?

@GeorgePetri Can you please give me an example where you use Qualifiers with @ContributesAndroidInjector?

@Module
abstract class ApplicationModule {

    @ContributesAndroidInjector(modules = [MainActivityModule::class])
    abstract fun provideActivity(): MainActivity

    @Binds
    @FromApplication
    abstract fun provideApplicationContext(app: App): Context
}

@Module
abstract class MainActivityModule {
    @Binds
    abstract fun provideActivity(mainActivity: MainActivity): AppCompatActivity

    @Binds
    @FromActivity
    abstract fun provideContext(mainActivity: MainActivity): Context
}

@FromActivity and @FromApplication are qualifiers

Maybe we could have a way to define an abstract generic module and use a type parameter for the concrete activity type. Then you can specify the module and Dagger will implement it for you:

Something like this:

@Module
@ContributesAndroidInjector.GenericModule // with a better name
interface ActivityModule<T extends Activity> {
  @Binds AppCompatActivity bind(T activity);
  @Binds @MySpecialQualifier Context context(T t);
}

You implement it once, include it in all of your @ContributesAndroidInjector declarations, and Dagger handles the rest. cc: @netdpb

Any news about this issue?

Will it ever be implemented? This feature'd save us from writing a boilerplate and would make the code much cleaner

@andretietz, actually builder of component or subcomponent created with @ContributesAndroidInjector provides an Activity (or whatever class you wrote). I suggest you make your @Provides methods in ActivityModule static, or in case of Kotlin, make your @Module class an object and annotate @Provides functions with @JvmStatic like this:

@Module
object ActivityModule {
    @Provides
    @ActivityScope
    @JvmStatic
    fun provideObjectDependentFromActivity(someActivity: SomeActivity) = SomeObject(someActivity)
}

So there will be no reason to create subcomponents the long way and you can use @ContributesAndroidInjector without any @Provides or @Binds methods in your components which provide you an Activity instance.
I had the same issue and making @Provides functions static solved it. Maybe it will solve your issue too.

Any news about this

Closing this as with Dagger Hilt's release there's unlikely to be significant new features added to Dagger Android.

Was this page helpful?
0 / 5 - 0 ratings