Search code examples
androidkotlinsingletonandroid-contextsoundpool

Which context do i use to load resources in a singleton?


I have a SoundPool that I want to play in different fragments. So I load it within a singleton. What context do I have to use?

object PingSoundPool {

fun loadpings(note: Int) {

    val context = Application()

    val mAttributes = AudioAttributes.Builder()
        .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
        .setUsage(AudioAttributes.USAGE_GAME)
        .build()

    val mSoundPool = SoundPool.Builder()
        .setMaxStreams(9)
        .setAudioAttributes(mAttributes)
        .build()

    val cping = mSoundPool.load(context, R.raw.cping, 1)
    val dbping = mSoundPool.load(context, R.raw.dbping, 1)
    [...]

    if (note == 0) {}
    if(note == 1)
        mSoundPool.play(cping, 1f, 1f, 1, -1, 1f)
    if(note == 2)
    mSoundPool.play(dbping, 1f, 1f, 1, -1, 1f)
    [...]
    }
}

If I use it like this, loading it in my activity´s onCreate like this PingSoundPool.loadPings(0) and accessing it in an onClickListener with PingSoundPool.loadPings(1) should work, no? At runtime, I get a NullPointerExeption like this:

java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.example.soulfetch2/com.example.soulfetch2.FullscreenActivity}:
 java.lang.NullPointerException: Attempt to invoke virtual method 
'android.content.res.Resources android.content.Context.getResources()' 
on a null object reference

The exeption points out the line val cping = mSoundPool.load(context, R.raw.cping, 1) The R.raw. file exists, but isn´t accessible somehow. I think I may be using the wrong context. Or I implement the right context in the wrong way. Anyway, help is greatly apreciated.


EDIT:

The original question was solved but there is still something wrong: The code as it is reloads the SoundPool every time it tries to play a sound. Hay anyone got a good idea how to load it seperately, so that the calls of PingSoundPool(this).loadPings(Int) just play the sounds and not reload everything?

Another thing: When I do PingSoundPool(this).loadPings(Int) from an Activity, all works well. From a fragment however, I get a TypeMismatch "Required: Context, Found: MainFragment". I can work around it with PingSoundPool(this.requireContext()).loadPings(2) or PingSoundPool(this.context!!).loadPings(2) but that seems not like the best thing to do. Any suggestions?

Here´s the class I use instead of the object now:

class PingSoundPool(context: Context) {

    val mAttributes = AudioAttributes.Builder()
        .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
        .setUsage(AudioAttributes.USAGE_GAME)
        .build()

    val mSoundPool = SoundPool.Builder()
        .setMaxStreams(9)
        .setAudioAttributes(mAttributes)
        .build()

    val cping = mSoundPool.load(context, R.raw.cping, 1)
    val dbping = mSoundPool.load(context, R.raw.dbping, 1)

fun loadPings(note: Int) {

    if(note == 1)
        mSoundPool.play(cping, 1f, 1f, 1, -1, 1f)
    if(note == 2)
    mSoundPool.play(dbping, 1f, 1f, 1, -1, 1f)
[...]
}

}


Solution

  • If you call it from your activity's onCreate, why don't you pass also Context as a parameter?

    The function will be like this:

    fun loadPings(context: Context, note: Int) {
    
        //val context = Application()   //Remove this line
    
        val cping = mSoundPool.load(context, R.raw.cping, 1)    //Here it's used the parameter context
        val dbping = mSoundPool.load(context, R.raw.dbping, 1)
        [...]
    }
    

    And in your Activity's onCreate you call it in this way:

    PingSoundPool.loadPings (this, 0)
    

    EDIT:

    If you create a PingSoundPool object, it won't reload every time the files: so you can do this in your activity:

    class YourActivity ... {
    
        companion object {   //So it is accesible from other classes with YourActivity.pingSoundPool
            lateinit var pingSoundPool: PingSoundPool;
        }
    
        @Override
        ... onCreate(...) {
            pingSoundPool = PingSoundPool(this)
            ...
        }
    }
    

    Then if you need to play sound (I consuel to change the function name) you can do it with

    pingSoundPool.load(1)                 // Inside of YourActivity
    YourActivity.pingSoundPool.load(1)    // Outside of YourActivity
    

    In this way I solved also your last question, but maybe you want to know how to pass the right Context object from a Fragment: in your Fragment you can declare a Context object (or a YourActivity object, it's a child of Context) and assign it a value from the onAttach(..) method, in this way:

    class YourFragment ... {
    
        private lateinit var mContext : Context
        private lateinit var mActivity : YourActivity   // You don't need both
    
        override fun onAttach(context: Context?) {
            super.onAttach(context)
    
            mContext = context!!
    
            if (context is YourActivity)
                mActivity = context
        }
    }
    

    Then inside your Fragment you can call PingSoundPool(mContext) (or PingSoundPool(mActivity)).

    Note that onAttach is called before any other callback method, so also before onCreateView.

    You can also get the Fragment context with getContext() (just context!! in Kotlin), but I think the above solution is better and safer.