Android + Kotin + Hilt: "Lateinit property has not been initialised" when doing field injection

Viewed 64

I have an object where I need to inject context as follows (lets focus on playButtonSound method):

object AWSound {
    private var soundPool: SoundPool? = null
    private var soundPoolMap: SparseIntArray? = null
    private const val S2 = R.raw.button_press
    //private lateinit var appContext: Context

    private fun playSound(oContext: Context?, soundID: Int, soundType: Int) {
        val sharedPref = PreferenceManager.getDefaultSharedPreferences(
            oContext!!
        )
        val strButtonSoundOnOff = sharedPref.getString("buttonsound", "off")
        val strPageFlipSoundOnOff = sharedPref.getString("pageflipsound", "off")
        val strEndTestSoundOnOff = sharedPref.getString("endtestsound", "off")
        var playSound = false
        when (soundType) {
            0 -> if (strButtonSoundOnOff != null && strButtonSoundOnOff == "on") {
                playSound = true
            }
            1 -> if (strPageFlipSoundOnOff != null && strPageFlipSoundOnOff == "on") {
                playSound = true
            }
            2 -> if (strEndTestSoundOnOff != null && strEndTestSoundOnOff == "on") {
                playSound = true
            }
            else -> {}
        }
        if (playSound) {
            val volume = 1.0.toFloat()
            if (soundPool == null || soundPoolMap == null) {
                initSounds(oContext)
                soundPool!!.play(soundPoolMap!![soundID], volume, volume, 1, 0, 1f)
            }
            soundPool!!.play(soundPoolMap!![soundID], volume, volume, 1, 0, 1f)
        }
    }

    /***
     * Put sounds from raw into SoundPool
     */
    private fun initSounds(oContext: Context?) {
        soundPool = SoundPool.Builder()
            .setMaxStreams(2)
            .build()
        soundPoolMap = SparseIntArray(2)
        soundPoolMap!!.put(S2, soundPool!!.load(oContext, R.raw.button_press, 2))
    }

    fun playButtonSound() {
        playSound(appContext, R.raw.button_press, 0) //0=>Button press sound
    }

    fun playPageFlipSound(context: Context?) {
        playSound(context, R.raw.page_flip, 1) //1=>Page flip sound
    }
}

As I know Hilt cannot @Inject into Kotlin objects (don't know why btw) I decided to try an approach I've found on SO, so my code ended like the next:

object AWSound : BaseClass() {
    private var soundPool: SoundPool? = null
    private var soundPoolMap: SparseIntArray? = null
    private const val S2 = R.raw.button_press
    //private lateinit var appContext: Context

    private fun playSound(oContext: Context?, soundID: Int, soundType: Int) {
        val sharedPref = PreferenceManager.getDefaultSharedPreferences(
            oContext!!
        )
        val strButtonSoundOnOff = sharedPref.getString("buttonsound", "off")
        val strPageFlipSoundOnOff = sharedPref.getString("pageflipsound", "off")
        val strEndTestSoundOnOff = sharedPref.getString("endtestsound", "off")
        var playSound = false
        when (soundType) {
            0 -> if (strButtonSoundOnOff != null && strButtonSoundOnOff == "on") {
                playSound = true
            }
            1 -> if (strPageFlipSoundOnOff != null && strPageFlipSoundOnOff == "on") {
                playSound = true
            }
            2 -> if (strEndTestSoundOnOff != null && strEndTestSoundOnOff == "on") {
                playSound = true
            }
            else -> {}
        }
        if (playSound) {
            val volume = 1.0.toFloat()
            if (soundPool == null || soundPoolMap == null) {
                initSounds(oContext)
                soundPool!!.play(soundPoolMap!![soundID], volume, volume, 1, 0, 1f)
            }
            soundPool!!.play(soundPoolMap!![soundID], volume, volume, 1, 0, 1f)
        }
    }

    /***
     * Put sounds from raw into SoundPool
     */
    private fun initSounds(oContext: Context?) {
        soundPool = SoundPool.Builder()
            .setMaxStreams(2)
            .build()
        soundPoolMap = SparseIntArray(2)
        soundPoolMap!!.put(S2, soundPool!!.load(oContext, R.raw.button_press, 2))
    }

    fun playButtonSound() {
        playSound(appContext, R.raw.button_press, 0) //0=>Button press sound
    }

    fun playPageFlipSound(context: Context?) {
        playSound(context, R.raw.page_flip, 1) //1=>Page flip sound
    }
}

abstract class BaseClass {
    @Inject lateinit var appContext: Context
}

And the error I get when calling playButtonSound is "lateinit property appContext has not been initialised".

Well, maybe with a slight modification it might work, I thought:

@AndroidEntryPoint
abstract class BaseClass : Service() {
    @Inject lateinit var appContext: Context
}

Of course I don't need to inherit "Service()", but this way I can add the "@AndroidEntryPoint" decorator, but again, to no avail and the exact same error.

I thought "maybe I could try constructor injection", so BaseClass ended like this:

@AndroidEntryPoint
abstract class BaseClass 
@Inject constructor (val appContext: Context) : Service() {
}

But this way ": BaseClass()" in AWSound complains that a constructor parameter is missing, and I don't know how to deal with this.

Lastly I have to mention that I have a "Dependencies" module where I provide context (among other dependencies):

@Module
@InstallIn(SingletonComponent::class)
class Dependencies {

    @Provides
    @Singleton
    fun provideContext(@ApplicationContext appContext: Context): Context {
        return appContext
    }
}

Of course I can create an init function in AWSound like this:

private lateinit var context: Context

fun init(context: Context) {
    this.context = context
}

And initialize it in ApplicationClass like this:

@HiltAndroidApp
class AWApplication  : MultiDexApplication() {

    override fun onCreate() {
        super.onCreate()
        
        AWSound.init(this)
    }
}

And I'm sure it'll work (as I've done it in other places), but this way I'm not taking any benefit of Hilt, this is just a simple Kotlin singleton property initialisation. How is Hilt helping me this way?

I'm stuck, and I have to admit Hilt is driving me nuts. What it was supposed to simplify our programming life making our app maintainable, ended up being a nightmare (at least for me).

So, any help on injecting context (or whatever) in my objects.

1 Answers

sometimes I also faced with this problem too! if you are not have to use only Hilt library take a look at Koin this is a di library that use service locator pattern. so you can access dependency graph whenever you want

access it in any line of code directly

appContainer.get<Context>()

or like hilt as a field injection, by marking object as KoinComponent

object MyObject : KoinComponent{
  val ctx by inject<Context>()
}
Related