Chintan Soni
Chintan Soni

Reputation: 25267

kotlin + dagger 2 : Execution failed for task ':app:kaptDebugKotlin'

So, I am trying to create an example, inspiring from GithubBrowserSample with Kotlin. I have successfully migrated to kotlin code but I getting error with my ApplicationComponent.kt.

Error:(12, 2) error: [dagger.android.AndroidInjector.inject(T)] java.util.Map<java.lang.Class<? extends android.support.v4.app.Fragment>,javax.inject.Provider<dagger.android.AndroidInjector.Factory<? extends android.support.v4.app.Fragment>>> cannot be provided without an @Provides-annotated method.
public abstract interface ApplicationComponent {
                ^
      java.util.Map<java.lang.Class<? extends android.support.v4.app.Fragment>,javax.inject.Provider<dagger.android.AndroidInjector.Factory<? extends android.support.v4.app.Fragment>>> is injected at
          dagger.android.DispatchingAndroidInjector.<init>(injectorFactories)
      dagger.android.DispatchingAndroidInjector<android.support.v4.app.Fragment> is injected at
          com.chintansoni.android.architecturecomponentsblueprint.base.BaseActivity.dispatchingAndroidInjector
      com.chintansoni.android.architecturecomponentsblueprint.view.activity.SplashActivity is injected at
          dagger.android.AndroidInjector.inject(arg0)

I can see that issue is surfaced from AndroidInjectionModule from import dagger.android.AndroidInjectionModule

Let me share my code snippets.

KotlinApplication.kt

class KotlinApplication : Application(), HasActivityInjector {

    @Inject
    lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>

    override fun onCreate() {
        super.onCreate()
        initializeLogger()
        initializeAppInjector()
    }

    private fun initializeAppInjector() {
        AppInjector.init(this)
    }

    private fun initializeLogger() {
        if (BuildConfig.DEBUG) {
            Timber.plant(Timber.DebugTree())
        }
    }

    override fun activityInjector(): DispatchingAndroidInjector<Activity>? {
        return dispatchingAndroidInjector
    }
}

AppInjector.kt

object AppInjector {
    fun init(kotlinApplication: KotlinApplication) {
        DaggerApplicationComponent.builder()
                .application(kotlinApplication)
                .build()
                .inject(kotlinApplication)

        kotlinApplication.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {

            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                handleActivity(activity)
            }

            override fun onActivityStarted(activity: Activity) {

            }

            override fun onActivityResumed(activity: Activity) {

            }

            override fun onActivityPaused(activity: Activity) {

            }

            override fun onActivityStopped(activity: Activity) {

            }

            override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {

            }

            override fun onActivityDestroyed(activity: Activity) {

            }
        })
    }

    private fun handleActivity(activity: Activity) {
        if (activity is HasSupportFragmentInjector) {
            AndroidInjection.inject(activity)
        }
        (activity as? FragmentActivity)?.supportFragmentManager?.registerFragmentLifecycleCallbacks(
                object : FragmentManager.FragmentLifecycleCallbacks() {
                    override fun onFragmentCreated(fm: FragmentManager, f: Fragment,
                                                   savedInstanceState: Bundle) {
                        if (f is Injectable) {
                            AndroidSupportInjection.inject(f)
                        }
                    }
                }, true)
    }
}

ApplicationComponent.kt

@Singleton
@Component(modules = [(AndroidInjectionModule::class), (AppModule::class), (SplashActivityModule::class)])
interface ApplicationComponent {

    @Component.Builder
    interface Builder {

        @BindsInstance
        fun application(application: Application): Builder

        fun build(): ApplicationComponent
    }

    fun inject(kotlinApplication: KotlinApplication)
}

SplashActivityModule.kt

@Module
abstract class SplashActivityModule {
    @ContributesAndroidInjector(modules = [(FragmentBuildersModule::class)])
    internal abstract fun contributeSplashActivity(): SplashActivity
}

BaseActivity.kt

abstract class BaseActivity : AppCompatActivity(), HasSupportFragmentInjector {

    @Inject
    lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>

    override fun supportFragmentInjector(): DispatchingAndroidInjector<Fragment> {
        return dispatchingAndroidInjector
    }

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

    abstract fun getLayoutResource(): Int
}

My SplashActivity extends BaseActivity.

Upvotes: 0

Views: 950

Answers (2)

Chintan Soni
Chintan Soni

Reputation: 25267

Everything was wired correctly. What I was doing wrong,

In ApplicationComponent I included AndroidInjectionModule which will only work to Inject Fragment from Android Framework.

Looking at the source of AndroidInjectionModule:

package dagger.android;

import android.app.Activity;
import android.app.Fragment;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import dagger.Module;
import dagger.internal.Beta;
import dagger.multibindings.Multibinds;
import java.util.Map;

/**
 * Contains bindings to ensure the usability of {@code dagger.android} framework classes. This
 * module should be installed in the component that is used to inject the {@link
 * android.app.Application} class.
 */
@Beta
@Module
public abstract class AndroidInjectionModule {
  @Multibinds
  abstract Map<Class<? extends Activity>, AndroidInjector.Factory<? extends Activity>>
      activityInjectorFactories();

  @Multibinds
  abstract Map<Class<? extends Fragment>, AndroidInjector.Factory<? extends Fragment>>
      fragmentInjectorFactories();

  @Multibinds
  abstract Map<Class<? extends Service>, AndroidInjector.Factory<? extends Service>>
      serviceInjectorFactories();

  @Multibinds
  abstract Map<
          Class<? extends BroadcastReceiver>, AndroidInjector.Factory<? extends BroadcastReceiver>>
      broadcastReceiverInjectorFactories();

  @Multibinds
  abstract Map<
          Class<? extends ContentProvider>, AndroidInjector.Factory<? extends ContentProvider>>
      contentProviderInjectorFactories();

  private AndroidInjectionModule() {}
}

No mentions about Fragment from Support Library.

Now, if we look at the source of AndroidSupportInjectionModule:

package dagger.android.support;

import android.support.v4.app.Fragment;
import dagger.Module;
import dagger.android.AndroidInjectionModule;
import dagger.android.AndroidInjector;
import dagger.internal.Beta;
import dagger.multibindings.Multibinds;
import java.util.Map;

/**
 * Configures bindings to ensure the usability of {@code dagger.android} and {@code
 * dagger.android.support} framework classes. This module should be installed in the root-most
 * component which will use these types.
 */
@Beta
@Module(includes = AndroidInjectionModule.class)
public abstract class AndroidSupportInjectionModule {
  @Multibinds
  abstract Map<Class<? extends Fragment>, AndroidInjector.Factory<? extends Fragment>>
      supportFragmentInjectorFactories();

  private AndroidSupportInjectionModule() {}
}

It includes support for injecting Fragment from Support Library.

If you notice, AndroidSupportInjectionModule also includes AndroidInjectionModule. Hence, just including AndroidSupportInjectionModule will work perfectly.

My BaseActivity was implementing HasSupportFragmentInjector which only works with Support Fragment. There was no Module included into ApplicationComponent that can provide Fragment dependency from Support Library, which was the main issue.

Upvotes: 0

Benjamin
Benjamin

Reputation: 7368

If you're using Fragment from the support library, you've to use the HasSupportFragmentInjector in your KotlinApplication:

class KotlinApplication : Application(), HasActivityInjector, HasSupportFragmentInjector {

    @Inject
    lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>

    @Inject
    lateinit var dispatchingFragmentInjector: DispatchingAndroidInjector<Fragment>

    override fun onCreate() {
        super.onCreate()
        initializeLogger()
        initializeAppInjector()
    }

    private fun initializeAppInjector() {
        AppInjector.init(this)
    }

    private fun initializeLogger() {
        if (BuildConfig.DEBUG) {
            Timber.plant(Timber.DebugTree())
        }
    }

    override fun activityInjector(): DispatchingAndroidInjector<Activity>? {
        return dispatchingAndroidInjector
    }

    override fun supportFragmentInjector(): AndroidInjector<Fragment>? {
        return dispatchingFragmentInjector
    }
}

also you've to install AndroidSupportInjectionModule instead of AndroidInjectionModule in your ApplicationComponent:

@Singleton
@Component(modules = [(AndroidSupportInjectionModule::class), (AppModule::class), (SplashActivityModule::class)])
interface ApplicationComponent {

    @Component.Builder
    interface Builder {

        @BindsInstance
        fun application(application: Application): Builder

        fun build(): ApplicationComponent
    }

    fun inject(kotlinApplication: KotlinApplication)
}

Upvotes: 1

Related Questions