Is lifecycleScope.launchWhenStarted safe or not ? If it is not safe, for what case?
Asked Answered
A

2

7

In my application, i send request to an API using coroutine, and flow. When the request comes, i changed the value of stateFlow so that the collector in my activity see that and does its job. That's a simple scenario. In the android website ( https://developer.android.com/kotlin/flow/stateflow-and-sharedflow ), 2 approach is suggested. In this case, which approach i should prefer ?

The following is a quotation from the above link. :

StateFlows are safe to collect using the launchWhen() functions since they're scoped to ViewModels, making them remain in memory when the View goes to the background, and they do lightweight work by just notifying the View about UI states. However, the problem might come with other producers that do more intensive work.

In this quotation, it is said 1st approach is safe however, in the last sentence there is a warning. What's that warning for ? I didn't understand whether it is safe or not. Does producers continue to work in first approach? In my case, if request does not come yet, and user goes the app in background, does producers work and try to update UI ?

1st approach :

class LatestNewsActivity : AppCompatActivity() {
    private val latestNewsViewModel = // getViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // This coroutine will run the given block when the lifecycle
        // is at least in the Started state and will suspend when
        // the view moves to the Stopped state
        lifecycleScope.launchWhenStarted {
            // Triggers the flow and starts listening for values
            latestNewsViewModel.uiState.collect { uiState ->
                // New value received
                when (uiState) {
                    is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
                    is LatestNewsUiState.Error -> showError(uiState.exception)
                }
            }
        }
    }
}

2nd approach :

class LatestNewsActivity : AppCompatActivity() {
    ...
    // Coroutine listening for UI states
    private var uiStateJob: Job? = null

    override fun onStart() {
        super.onStart()
        // Start collecting when the View is visible
        uiStateJob = lifecycleScope.launch {
            latestNewsViewModel.uiState.collect { uiState -> ... }
        }
    }

    override fun onStop() {
        // Stop collecting when the View goes to the background
        uiStateJob?.cancel()
        super.onStop()
    }
}

Which approach is more suitable in this very simple case ?

Auroraauroral answered 9/4, 2021 at 14:39 Comment(2)
Give this a read, it might help medium.com/androiddevelopers/…Hoelscher
I already read that article, but it's hard to understand. It would be more understandable. In my case, since i do not use "a cold flow backed by a channel or using operators with buffers such as buffer, conflate, flowOn, or shareIn", using launchWhenStarted is safe. If i were using flowOn() it would be dangeous. Am i right ? If so, a coroutine fired with viewModelScope works in background thread too, why it is safe but the mentioned APIs working in the background is unsafe ?Auroraauroral
S
5

Update (March 2023)

Lifecycle.launchWhenX methods and Lifecycle.whenX methods have been deprecated as the use of a pausing dispatcher can lead to wasted resources in some cases. It is recommended to use Lifecycle.repeatOnLifecycle. Refer lifecycle version 2.6.0 release notes.

Original Answer

No matter what, the collect block will not work if the view is in stopped state or any state less than STARTED.

But there is something called subscription count for the flows which indicate how much subscribers are looking to collect the flow. Here comes the trick. When the view moves to a state less than STARTED state, the collect block is suspended but the subscription count of the flow remains same which keeps the flow producer in memory. But if you go with second approach by cancelling the job, the subscription count will be reduced when you cancel the job and it will prevent the flow producer to be in memory which is a waste of resource when not in need.

Now the warning they have given in last sentence is for other flow producers apart from StateFlow. For example the flow builder function flow{ //some intensive work }. The StateFlow work different because it just emits the UI state to activity/fragment and doesn't contain any block of code (expensive code). And hence it is safe to collect the StateFlow with launchWhen functions.

This is the desired working in case of UI state emission because you need the flow to emit the changes even if the fragment is in backstack and not active but should get the latest change as soon as it is restored from the backstack again. Here the StateFlow should remain in memory which is scoped to viewmodel.

Spancel answered 9/4, 2021 at 15:10 Comment(0)
M
0

As lifecycleScope.launchWhenX is deprecated. Try using creating an extension function and a simple call.

launchWhenResumed { 
    // your Code..
} 

Link(code): https://mcmap.net/q/807397/-android-lifecyclescope-launchwhenresumed-deprecated

Multilateral answered 12/6, 2023 at 4:41 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.