Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Question about Kotlin Coroutine Cancellation

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.

like image 356
adarsha Avatar asked Oct 17 '25 13:10

adarsha


1 Answers

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.

like image 103
Valeriy Katkov Avatar answered Oct 20 '25 03:10

Valeriy Katkov



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!