androidkotlindependency-injectiondagger-2dagger-hilt

Android + Kotlin + Hilt: Error building project


I'm new to Hilt, and I'm struggling my head in order to implement it in my multi-module app. After infinite attempts the build error varies from "error a" to "error b", then "error c" and so.

This is how I've started implementing Hilt into my multi-module app:

Project build.gradle:

buildscript {
    ext.kotlin_version = '1.7.10'
    repositories {
        jcenter()
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:7.2.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath("com.google.dagger:hilt-android-gradle-plugin:2.42")
    }
    ext.java_version = JavaVersion.VERSION_1_8
}

allprojects {
    repositories {
        jcenter()
        mavenCentral()
        maven { url "https://jitpack.io" }
        maven { url 'https://repository-achartengine.forge.cloudbees.com/snapshot/' }
        maven { url 'def androidHome = System.getenv("ANDROID_HOME")' }
        maven { url "/Home/Diego/Android/Sdk/extras/android/m2repository/" }
        google()
    }
}

app build.gradle:

plugins {
    id('dagger.hilt.android.plugin')
    id('com.android.application')
    id('kotlin-android')
    id('kotlin-kapt')
}

android {
    compileSdkVersion 32
    def code
    Properties versionProps = new Properties()
    def versionPropsFile = file('version.properties')
    if (versionPropsFile.exists())
        versionProps.load(new FileInputStream(versionPropsFile))
    code = (versionProps['VERSION_CODE'] ?: "0").toInteger() + 1
    versionProps['VERSION_CODE'] = code.toString()
    versionProps.store(versionPropsFile.newWriter(), null)
    packagingOptions {
        resources {
            pickFirsts += ['META-INF/LICENSE.txt']
            excludes += ['META-INF/NOTICE.md', 'META-INF/LICENSE.md', 'META-INF/INDEX.LIST', 'META-INF/DEPENDENCIES', 'META-INF/io.netty.versions.properties']
        }
    }
    defaultConfig {
        applicationId 'com.xxx.xxx'
        minSdkVersion 23
        targetSdkVersion 32
        versionCode code
        versionName "2.0." + code
        // next ndk abifilters have to be disabled if spli apk is enabled.
        // ndk.abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'//testing
        multiDexEnabled true
        compileOptions {
            sourceCompatibility java_version
            targetCompatibility java_version
        }
    }
    compileOptions {
        sourceCompatibility java_version
        targetCompatibility java_version
    }
    splits {
        // Configures multiple APKs based on ABI.
        abi {
            // Enables building multiple APKs per ABI.
            enable true
            // By default all ABIs are included, so use reset() and include to specify that we only
            // want APKs for x86 and x86_64.
            // Resets the list of ABIs that Gradle should create APKs for to none.
            reset()
            // Specifies a list of ABIs that Gradle should create APKs for.
            include "armeabi-v7a"
            include "arm64-v8a"
            include "x86"
            include "x86_64"
            // Specifies that we do not want to also generate a universal APK that includes all ABIs.
            universalApk false
        }
    }
    buildTypes {
        release {
            /*signingConfig signingConfigs.release*/
            minifyEnabled false
            shrinkResources false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            // This next piece of code is used by apk Split
            applicationVariants.all { variant ->
                variant.outputs.all { output ->
                    project.ext { appName = 'xxx' }
                    def newName = 'xxx_' + output.getFilter(com.android.build.OutputFile.ABI) + '.apk'
                    outputFileName = new File("./", newName)
                }
                // assign different version code for each output
                variant.outputs.each { output ->
                    output.versionCodeOverride =
                            //project.ext.versionCodes.get(output.getFilter(OutputFile.ABI)) * 1000 + android.defaultConfig.versionCode
                            project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI)) * 1000 + code - 1000
                }
            }
        }
        debug {
        }
    }
    allprojects {
        repositories {
            jcenter()
            mavenCentral()
            def androidHome = System.getenv("ANDROID_HOME")
            maven {
                url "$androidHome/extras/android/m2repository/"
            }
            maven {
                url "https://maven.java.net/content/groups/public/"
            }
        }
        /*tasks.withType(JavaCompile) {
            options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
        }*/
    }
    productFlavors {
    }
    androidResources {
        ignoreAssetsPattern '!*ffprobe'
    }
    lint {
        abortOnError false
        checkReleaseBuilds false
    }
    dataBinding{
        enabled = true
    }
    namespace 'com.xxx.xxx'
}

// This next piece of code is used by apk Split
// map for the version code that gives each ABI a value. make sure to list all ABIs mentioned in splits block, an keep the order.
ext.versionCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]
//import com.android.build.OutputFile

dependencies {
    //P7Zip
    //implementation 'com.hzy:libp7zip:1.7.0'
    //Apache Commons
    //implementation 'commons-io:commons-io:2.6'
    //ffmpeg
    implementation 'com.arthenica:mobile-ffmpeg-min-gpl:4.2.2.LTS'
    //implementation 'com.arthenica:mobile-ffmpeg-full:4.3.1'
    //Google Guava
    api 'com.google.guava:guava:31.1-jre'
    //volley
    api 'com.android.volley:volley:1.2.1'
    //spotify
    api 'com.github.kaaes:spotify-web-api-android:0.4.1'
    //mail API 19
    //api 'com.sun.mail:android-mail:1.6.4'
    //api 'com.sun.mail:android-activation:1.6.4'
    //mail API 16
    //api 'com.sun.mail:android-mail:1.6.7'
    //api 'com.sun.mail:android-activation:1.6.7'
    //apache commons lang
    //ppppapi 'org.apache.commons:commons-lang3:3.12.0'
    implementation group: 'org.apache.commons', name: 'commons-text', version: '1.9'
    //Font Selector List Preference
    api 'com.vanniktech:vntfontlistpreference:1.0.0'
    //Rate my app
    api 'com.github.hotchemi:android-rate:1.0.1'
    //Support
    api 'androidx.appcompat:appcompat:1.4.2'
    api 'androidx.legacy:legacy-support-v4:1.0.0'
    //AlertDialog
    implementation 'com.github.d-max:spots-dialog:1.1@aar'
    //glide animated gifs
    implementation 'com.github.bumptech.glide:glide:4.13.2'
    implementation 'androidx.viewpager2:viewpager2:1.0.0'
    implementation project(path: ':Common')
    annotationProcessor 'com.github.bumptech.glide:compiler:4.13.2'
    implementation 'androidx.recyclerview:recyclerview:1.2.1'
    //preference
    implementation 'androidx.preference:preference-ktx:1.2.0'
    //
    api 'com.rockerhieu:rv-adapter-endless:1.2'
    implementation 'com.squareup.picasso:picasso:2.71828'
    //flat-dialog
    implementation 'com.github.mejdi14:Flat-Dialog-Android:1.0.5'
    //Hilt
    //implementation('com.google.dagger:hilt-android:2.40')
    //annotationProcessor('com.google.dagger:hilt-android-compiler:2.40')

    implementation 'com.google.dagger:hilt-android:2.42'
    //implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0'
    //implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'
    //annotationProcessor 'androidx.hilt:hilt-compiler:1.0.0'
    kapt 'com.google.dagger:hilt-android-compiler:2.42'
    kapt('org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.5.0')

    implementation project(':Common')
    implementation project(':DTO')
    implementation project(':Core')
}

// Allow references to generated code
kapt {
    correctErrorTypes = true
}

Given that, I have to mention that I have an AppSettings class where I store all app configuration (now I've created AppSettings1 just for testing) and to start with something I'm trying to inject that class into the main activity so all its variables are available.

MainActivity:

@AndroidEntryPoint
class MainActivity @Inject constructor(private val appSettings: AppSettings1): FragmentActivity(), IActionListeners, IImageListeners, OnListFragmentInteractionListener
{
val color2 = appSettings.infoAlertBkgColor
...
}

MainViewModel:

@HiltViewModel
@SuppressLint("StaticFieldLeak")
class MainViewModel
@Inject constructor(@ApplicationContext var context: Context, private val appSettings: AppSettings1) : ViewModel() {
...
}

AppSettings:

@HiltAndroidApp
class AppSettings1 : MultiDexApplication() {

    val infoAlertBkgColor1 = "#60a69e"

    override fun onCreate() {
        super.onCreate()
        //instance = this
        resourses = applicationContext.resources
        outputPathCache = cacheDir.absolutePath
    }
}

And when I build the app the next error is thrown:

/Users/diego/StudioProjects/artandwords/app/build/generated/hilt/component_sources/debug/com/artandwords/thoughtoftheday/app/AppSettings1_HiltComponents.java:127: error: [Dagger/MissingBinding] com.xxx.xxx.app.AppSettings1 cannot be provided without an @Inject constructor or an @Provides-annotated method. public abstract static class SingletonC implements AppSettings1_GeneratedInjector, ^ A binding for com.xxx.xxx.app.AppSettings1 exists in com.xxx.xxx.app.AppSettings1_HiltComponents.SingletonC: com.xxx.xxx.app.AppSettings1 is injected at [com.xxx.xxx.app.AppSettings1_HiltComponents.ViewModelC] com.xxx.xxx.activities.main.MainViewModel(…, appSettings) com.xxx.xxx.activities.main.MainViewModel is injected at [com.xxx.xxx.app.AppSettings1_HiltComponents.ViewModelC] com.xxx.xxx.activities.main.MainViewModel_HiltModules.BindsModule.binds(arg0) @dagger.hilt.android.internal.lifecycle.HiltViewModelMap java.util.Map<java.lang.String,javax.inject.Provider<androidx.lifecycle.ViewModel>> is requested at [com.xxx.xxx.app.AppSettings1_HiltComponents.ViewModelC] dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.ViewModelFactoriesEntryPoint.getHiltViewModelMap() [com.xxx.xxx.app.AppSettings1_HiltComponents.SingletonC → com.xxx.xxx.app.AppSettings1_HiltComponents.ActivityRetainedC → com.xxx.xxx.app.AppSettings1_HiltComponents.ViewModelC]

Incidentally, my app is a multi-module one, so I guess I have to install Hilt dependencies and plugin on every module (as I've already done), right?

I'm new to Hilt, so it's most likely I'm doing something wrong. What can I try next?


Solution

  • @AndroidEntryPoint
    class MainActivity 
    /*Not allowed*/
    @Inject constructor(private val appSettings: AppSettings1): FragmentActivity()
    ...
    }
    

    First, for android classes like Activity, you can't use constructor injection for custom parameter/dependency. You can use field injection instead, if needed.

    @AndroidEntryPoint
    class MainActivity (): FragmentActivity(){
        //Sample field injection
        @Inject lateinit var dependencyClass: DependencyClass
    ..
    }
    

    Second, you don't need to inject Application class to Activity, you can access it using application or getApplication(), example:

    /*Cast `application` to the
     * actual application class to access its field.
     */
     val color2 = (application as AppSettings1).infoAlertBkgColor1