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)
[...]
}
}
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.