Josh Ribeiro
Josh Ribeiro

Reputation: 1387

Kotlin and Dagger 2: Issues using Provider class

After scouring the internet, there doesn't seem to be a similar issue and it is eating me. In the process of learning Dependency Injection using Dagger 2, I am trying to translate an example from Java to Kotlin. The project compiles fine in Java, but using Kotlin, Does not like the javax.inject.Provider class and fails to build.

What is missing? Is the use of the Provider class incorrect for Kotlin here?

Here is the error from the Gradle event log:

repositorytrends\custom_implementations\RepoTrendsAppComponent.java:8: error: java.util.Map<java.lang.Class<? extends android.app.Activity>,? extends javax.inject.Provider<dagger.android.AndroidInjector.Factory<? extends android.app.Activity>>> cannot be provided without an @Provides-annotated method.

Here is the offending file. The parameter (Map) for the internal constructor is the deciding factor in a successful build:

    class ActivityInjector
@Inject internal constructor(private val activityInjectors: Map<Class<out Activity>, Provider<AndroidInjector.Factory<out Activity>>>){
         private val cache = HashMap<String, AndroidInjector<out Activity>>()

         internal fun inject(activity: Activity) {
             if (activity !is RepoTrendActivity) {
                 throw IllegalArgumentException("Activity must extend BaseActivity")
             }

             val instanceId = activity.getInstanceID
             if (cache.containsKey(instanceId)) {

                 (cache[instanceId] as AndroidInjector<Activity>).inject(activity)
                 return
             }

             val injectorFactory = activityInjectors[activity.javaClass]?.get() as AndroidInjector.Factory<Activity>
             val injector = injectorFactory.create(activity)
             cache.put(instanceId, injector)
             injector.inject(activity)
         }

         internal fun clear(activity: Activity) {
             if (activity !is RepoTrendActivity) {
                 throw IllegalArgumentException("Activity must extend BaseActivity")
             }
             cache.remove(activity.getInstanceID)
         }

         companion object {
             internal operator fun get(context: Context): ActivityInjector{
                 return (context.applicationContext as RepoTrendsApp).activityInjector
             }
         }
}

Here are the rest of the classes that are related to the Gradle build error log:

@Singleton
@Component(modules = arrayOf(
        RepoTrendsAppModule::class
))

interface RepoTrendsAppComponent {
    fun inject(repoTrendsApp: RepoTrendsApp)
}

Custom Application file:

class RepoTrendsApp: Application(){

    @Inject lateinit var activityInjector: ActivityInjector

}

Build.gradle for good measure:

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId 'com.inviscidlabs.repositorytrends'
        minSdkVersion 21
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    productFlavors {
    }

    kapt {
        generateStubs = true
    }
}

dependencies {
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation "com.android.support:appcompat-v7:$supportLibraryVersion"
    implementation "com.android.support:design:$supportLibraryVersion"

    implementation "com.google.dagger:dagger:$daggerVersion"
    implementation "com.google.dagger:dagger-android-support:$daggerVersion"
    kapt "com.google.dagger:dagger-android-processor:$daggerVersion"
    kapt "com.google.dagger:dagger-compiler:$daggerVersion"

    implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
    implementation "com.squareup.retrofit2:converter-moshi:$retrofitVersion"
    implementation "com.squareup.moshi:moshi:$moshiVersion"
    kapt "com.ryanharter.auto.value:auto-value-moshi:$autoValueMoshiVersion"
    compileOnly "com.ryanharter.auto.value:auto-value-moshi-annotations:$autoValueMoshiVersion"

    compileOnly "com.google.auto.value:auto-value:$autoValueVersion"
    annotationProcessor "com.google.auto.value:auto-value:$autoValueVersion"

    implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
    implementation "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion"
    implementation "com.jakewharton.rxrelay2:rxrelay:$rxRelayVersion"

    //Drop in replacement for Fragments
    implementation "com.bluelinelabs:conductor:$conductorVersion"

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'

    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

}

As requested, RepoTrendsAppModule:

import android.app.Application
import android.content.Context
import dagger.Module
import dagger.Provides


@Module
class RepoTrendsAppModule(val application: Application){

    @Provides
    fun provideApplicationContext(): Context = application

}

Upvotes: 0

Views: 2543

Answers (2)

Benjamin
Benjamin

Reputation: 7368

You don't need your ActivityInjector class. Modify your application class as follow:

class RepoTrendsApp: Application(), HasActivityInjector {

    @Inject
    internal lateinit var dispatchingActivityInjector: DispatchingAndroidInjector<Activity>

    override fun onCreate() {
    super.onCreate()
        DaggerRepoTrendsAppComponent.builder()
            .repoTrendsAppModule(RepoTrendsAppModule(this))
            .build()
            .inject(this)
    }

    override fun activityInjector(): AndroidInjector<Activity>? {
        return dispatchingActivityInjector
    }
}

and your component to:

@Singleton
@Component(modules = arrayOf(
        AndroidSupportInjectionModule::class,
        RepoTrendsAppModule::class
))
interface RepoTrendsAppComponent : AndroidInjector<RepoTrendsApp>

Upvotes: 1

ianatha
ianatha

Reputation: 307

Sounds like some the @Provides annotation is missing on your module. Here's an example Kotlin/Android/Dagger 2 module definition:

@Module
class MyAndroidModule(private val application: Application) {
    @Provides
    @Singleton
    @CustomInjectionAnnotation
    fun provideApplicationContext(): Context = application

    @Provides
    @Singleton
    fun provideLocationManager(): LocationManager =
            application.getSystemService(Context.LOCATION_SERVICE) as LocationManager

    @Provides
    @Singleton
    @Named("version")
    fun provideVersionString(): String = "beta"    
}

Upvotes: 0

Related Questions