I have a code something like the following in my app
class MyFragment : Fragment(), CoroutineScope by MainScope() {
override fun onDestroy() {
cancel()
super.onDestroy()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
doSomething()
}
private fun doSomething() = launch {
val data = withContext(Dispathers.IO) {
getData()
}
val pref = context!!.getSharedPreferences("mypref", MODE_PRIVATE)
pref.edit().putBoolean("done", true).apply()
}
}
In production, I get many NPEs in doSomething() while accessing context.
My assumption was that the coroutine gets cancelled after calling cancel() in onDestroy(), so I didn't bother to check the context for null value. But it looks like coroutine continues to execute even after cancel() is called. I think it happens if the cancel() is called after completing the withContext and before resuming the coroutine.
So I replaced doSomething() with following.
private fun doSomething() = launch {
val data = withContext(Dispathers.IO) {
getData()
}
if (isActive) {
val pref = context!!.getSharedPreferences("mypref", MODE_PRIVATE)
pref.edit().putBoolean("done", true).apply()
}
}
This fixes the crash.
However, Is this an expected behaviour or am I doing something wrong? Kotlin documentation aren't very clear about this. And most of the examples online are like my original code.
Your code assumes that the withContext()
will stop execution when it returns, if the scope is cancelled, but actually it didn't, till version 1.3.0 of kotlin coroutines. Here is GitHub issue. I guess that you are using an earlier version of the library.
I also recommend you use LifecycleScope instead of a custom scope. It's part of lifecycle-runtime-ktx
library. So, simplified solution looks like:
// build.gradle
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-rc02"
}
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
doSomething()
}
private fun doSomething() = viewLifecycleOwner.lifecycleScope.launch {
val data = withContext(Dispathers.IO) {
getData()
}
val pref = context!!.getSharedPreferences("mypref", MODE_PRIVATE)
pref.edit().putBoolean("done", true).apply()
}
}
There are other helpful utilities which simplifies coroutines usage, take a look at Use Kotlin coroutines with Architecture components documentation section.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With