https://kotlinlang.org logo
n

Nat Strangerweather

12/26/2022, 5:54 PM
Hi, I am a bit confused about Kotlin Coroutines Jobs. Is it the case that a job can only run once? I wanted to launch the
searchJob
every time my search parameter changed in the example below. What happens is that the
searchJob
is called successfully the first time I pass my parameter, but never again after that. Could someone please have a look ?
Copy code
fun onSearch(searchTitle: String) {
        _searchTitle.value = searchTitle
        println("on search started")
        searchJob = viewModelScope.launch {
            delay(1000L)
            println("search job started")
            getFeeds(searchTitle)
                .onEach { result ->
                    when (result) {
                        is Resource.Success -> {
                            _isRefreshing.value = false
                            _state.value = state.value.copy(
                                newsFeedItems = result.data ?: emptyList(),
                                isLoading = false,
                                isSuccess = true,
                                isError = false
                            )
                            println("is success")  }
The
onSearch()
function starts, but the
searchJob
function does not.
j

Josh Eldridge

12/26/2022, 7:48 PM
How are you using the
searchJob
variable outside of this?
n

Nat Strangerweather

12/26/2022, 8:01 PM
No, that's the only place.
j

Josh Eldridge

12/26/2022, 8:07 PM
So you define a variable somewhere named
searchJob
and only assign it here? You might want to launch the coroutine with an Coroutine exception handler passed into it to see if there is a hidden exception cancelling the coroutine
n

Nat Strangerweather

12/26/2022, 8:10 PM
Yes, I define it and use it in the viewModel:
Copy code
private var searchJob: Job? = null
I have been using a tutorial and adapting it to my own project... I'll do what you suggest. So does it mean that the searchJob needs to be cancelled properly in order to be reused?
j

Josh Eldridge

12/26/2022, 8:13 PM
I don't believe you can reuse job references, once it reaches its final state of cancelled or completed it is done. But the job reference shouldn't have any impact here as long as you aren't manually cancelling it somewhere else. Does the "search job started" not print a second time or is the contents inside of the flow not working?
n

Nat Strangerweather

12/26/2022, 8:14 PM
The "search job started" does not print a second time.
But "on search started" does...
j

Josh Eldridge

12/26/2022, 8:15 PM
That's really odd, you may want to override onCleared in the view model and see if the view model scope itself is getting cancelled. Because it looks like it should run a second time from my brief glance
n

Nat Strangerweather

12/26/2022, 8:16 PM
Ok, thanks for your help. I will check it out.
b

bezrukov

12/26/2022, 8:18 PM
what is `getFeeds`' return type? Is it flow? If yes, do you apply a terminal operator (e.g. collect) on it? I mean snippet you shared includes only onEach which is not terminal, and alone will have no effect
I would recommend to create a minimum reproducing sample of it, e.g. smth similar to
Copy code
var searchJob: Job? = null
searchJob = viewModelScope.launch { 
   println("A: before delay")
   delay(1000)
   println("A: before delay")
}
searchJob = viewModelScope.launch { 
   println("B: before delay")
   delay(1000)
   println("B: before delay")
}
I'm pretty sure when you will minimize your case to what I wrote above you should be able to find the cause.
n

Nat Strangerweather

12/26/2022, 8:25 PM
Yes, it's a flow. this is the whole snippet:
Copy code
fun onSearch(searchTitle: String) {
        _searchTitle.value = searchTitle
        println("on search started")
        searchJob = viewModelScope.launch {
            delay(1000L)
            println("search job started")
            getFeeds(searchTitle)
                .onEach { result ->
                    when (result) {
                        is Resource.Success -> {
                            _isRefreshing.value = false
                            _state.value = state.value.copy(
                                newsFeedItems = result.data ?: emptyList(),
                                isLoading = false,
                                isSuccess = true,
                                isError = false
                            )
                            println("is success")
                        }

                        is Resource.Error -> {
                            _isRefreshing.value = false
                            _state.value = state.value.copy(
                                newsFeedItems = result.data ?: emptyList(),
                                isLoading = false,
                                isSuccess = false,
                                isError = true
                            )
                        }

                        is Resource.Loading -> {
                            _isRefreshing.value = true
                            _state.value = state.value.copy(
                                newsFeedItems = result.data ?: emptyList(),
                                isLoading = true,
                                isSuccess = false,
                                isError = false
                            )
                        }
                    }
                }.launchIn(this)
        }
    }
Thanks I'll try that too
Ok, I'm getting :
Copy code
A: before delay
B: before delay
A: after delay
B: after delay
Clear
But only the first time. I assume my problem is elsewhere then. I'll keep investigating.
b

bezrukov

12/26/2022, 8:47 PM
is
viewModelScope
normal androidx' scope? If no (e.g. if it's manually created), that might happen if you have an unhandled exception somewhere. You can change
Copy code
println("on search started")
to
Copy code
println("on search started ${viewModelScope.coroutineContext.job.isActive}")
to check if VM is somehow cancelled second time
n

Nat Strangerweather

12/26/2022, 8:51 PM
Yes it's
Copy code
androidx.lifecycle.viewModelScope
The result of
Copy code
println("on search started ${viewModelScope.coroutineContext.job.isActive}")
is "True" the first time and "False" the second time.
It's a HiltViewModel, I don't know if that has any importance here?
Is it possible that another
viewModelScope
can interfere? Because I have a function just before using
viewModelScope
too.
Interesting that replacing
viewModelScope
with
GlobalScope
means my app is working as expected. However, I believe GlobalScope is not recommended, right?
b

bezrukov

12/26/2022, 9:12 PM
Yes, GlobalScope is not recommended, since the jobs created using that scope might outlive your fragment/activity. I didn't work with Hilt, so can't help there but better to find who causes viewModelScope to cancel
I don't think another usage of viewModelScope may cause it - viewModelScope uses SupervisorJob, which means cancellation of one child job (e.g. due to an exception) won't affect siblings
if onCleared is called between your onSearch functions, just debug it, and see who calls it and why
n

Nat Strangerweather

12/26/2022, 9:14 PM
Oh right, thanks, I deleted these questions because I thought they were not relevant.
On cleared is called when I navigate back to my list of options.
so the problem seems to be back navigation.
Thanks for helping me narrow it down.