free geoip
57

Fix Coroutine Not Cancelled on Lifecycle Destroyed

In Android development, Kotlin Coroutines have become a powerful tool to handle asynchronous operations more efficiently than traditional callbacks or…

In Android development, Kotlin Coroutines have become a powerful tool to handle asynchronous operations more efficiently than traditional callbacks or RxJava.
However, many developers encounter a common issue: coroutines are not cancelled when the lifecycle is destroyed.
This problem may lead to memory leaks, crashes, and unnecessary background work.
In this article, we will explore why this happens, common mistakes, and how to fix coroutine cancellation properly with lifecycle-aware components.

Coroutine Not Cancelled on Lifecycle Destroyed

Why Coroutines Are Not Cancelled Automatically

By default, launching a coroutine using GlobalScope.launch or other non-lifecycle aware scopes does not bind it to the lifecycle of an Activity or Fragment.
This means that even if the UI component is destroyed, the coroutine will continue running in the background.
If the coroutine tries to update a destroyed UI, it may cause crashes such as IllegalStateException.

Common Mistake Example

Here’s a simple example of a coroutine running in an Activity without lifecycle cancellation:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Wrong: Coroutine not bound to lifecycle
        GlobalScope.launch {
            delay(5000)
            Log.d("CoroutineTest", "This will run even if activity is destroyed")
        }
    }
}

In this case, if the user navigates away from the activity before 5 seconds, the coroutine still continues running.
This may lead to memory leaks or UI crashes.

How to Fix Coroutine Cancellation

The correct approach is to use lifecycle-aware scopes such as lifecycleScope or viewLifecycleOwner.lifecycleScope in Fragments.
These automatically cancel coroutines when the lifecycle is destroyed.

Using lifecycleScope in Activity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Correct: Coroutine tied to Activity lifecycle
        lifecycleScope.launch {
            delay(5000)
            Log.d("CoroutineTest", "This will be cancelled if activity is destroyed")
        }
    }
}

Using viewLifecycleOwner.lifecycleScope in Fragment

class ExampleFragment : Fragment(R.layout.fragment_example) {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Correct: Coroutine tied to Fragment view lifecycle
        viewLifecycleOwner.lifecycleScope.launch {
            delay(5000)
            Log.d("CoroutineTest", "Cancelled automatically when view is destroyed")
        }
    }
}

Using Lifecycle.repeatOnLifecycle

If you want to collect Flow or execute tasks only when the UI is visible,
repeatOnLifecycle is the recommended pattern.
This ensures coroutines only run when the lifecycle is at a certain state (e.g., STARTED).

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.dataFlow.collect { data ->
            Log.d("CoroutineFlow", "Received data: $data")
        }
    }
}

Comparison: GlobalScope vs lifecycleScope

ScopeLifecycle AwarenessCancellationUse Case
GlobalScopeNoNot automatically cancelledBackground jobs not tied to UI
lifecycleScopeYesCancelled when lifecycle destroyedUI-related coroutines
viewModelScopeYesCancelled when ViewModel clearedLong-running UI logic in ViewModel

Best Practices

  • Never use GlobalScope for coroutines tied to UI lifecycle.
  • Use lifecycleScope in Activities and viewLifecycleOwner.lifecycleScope in Fragments.
  • Use repeatOnLifecycle when collecting Flows.
  • For ViewModel, always use viewModelScope instead of launching from Activity/Fragment.

Conclusion

When coroutines are not cancelled after the lifecycle is destroyed,
it can cause memory leaks and crashes in your Android app.
By using lifecycleScope, viewLifecycleOwner.lifecycleScope,
or viewModelScope, you ensure proper cancellation and safe execution of asynchronous tasks.
Always prefer lifecycle-aware scopes to avoid issues in production apps.

rysasahrial

Leave a Reply

Your email address will not be published. Required fields are marked *