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]
BTW, 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 insist I'm new to Hilt, so it's most likely I'm doing something wrong, but cannot figure out what.
Any help?
CodePudding user response:
@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()
CodePudding user response:
Your AppSettings1 is a class which is inherited from the Application class and Hilt doesn't know what exactly that is and how to inject it - it's like an unknown entity. You have to either create a module which will tell Hilt how to coop with that (not possible in this case because you cannot remove @HiltAndroidApp) and thereby the only way is next
@HiltAndroidApp
class AppSettings1 @Inject constructor : MultiDexApplication()
After that you can inject it in your VM
@Inject constructor(@ApplicationContext var context: Context, private val app: AppSettings1) : ViewModel() {
...
But a couple of things you should think of
- you can get any resources you want from the applicationContext, you don't need specifically the App class itself. Your
@ApplicationContext var context: Context
is more than enough. - retrieving resources from the app context/app is a bad idea. For example, you're switching themes and all your activities are recreated - but not the app class, so you will still have the old references of the resources and all the UI stuff will have wrong color theme.