Search code examples
androidkotlinviewmodelkotlin-coroutinesjob-scheduling

Kotlin Coroutines: Issue with job-scheduling.(invokeOnCompletion)


I am fairly new to this kotlin-coroutine thing and i have an issue with job-scheduling.In this code below, first i fetch topic names from user's cache in the fragment.(topicsList) And then, i need to fetch these topics from API one by one. What i want to do is loop through the topicsList, make a request for each topic and get all the responses once at the completion of all requests. In order to achieve that, in getEverything() method(which fires up a request), i am adding the responses into an arraylist for every time.(responseList) In for loop, i am firing up all the requests. After the completion of the job, job.invokeOnCompletion{} is called and i set my liveData to responseList. However, this approach doesn't work. Problem is, i am updating the liveData before the setting the responseList. I don't know how can it be possible. Could anybody help me about this?

Here is my CoroutineScope in myFragment:

val topicsList = dataMap["topics"] // GOT THE TOPICS
topicsList?.let {
    var job: Job
    CoroutineScope(Dispatchers.Main).launch {
        job = launch {
            for (topic in topicsList) {
                mViewModel.getEverything(topic, API_KEY)
            }
        }
        job.join()
        job.invokeOnCompletion {
        mViewModel.updateLiveData()
        }
    }
} ?: throw Exception("NULL")

getEverything() method in viewModel:

 suspend fun getEverything(topic: String, apiKey: String) {

        viewModelScope.launch {
            _isLoading.value = true
            withContext(Dispatchers.IO) {
                val response = api.getEverything(topic, apiKey)
                withContext(Dispatchers.Main) {
                    if (response.isSuccessful) {
                        if (response.body() != null) {
                            responseList.add(response.body()!!)
                            println("Response is successful: ${response.body()!!}")
                            _isLoading.value = false
                            _isError.value = false
                        }
                    }
                    else {
                        Log.d(TAG, "getEverything: ${response.errorBody()}")
                        _isError.value = true
                        _isLoading.value = false
                    }
                }
            }
        }

    }

And, updateLiveData method:

fun updateLiveData() {
        _newsResponseList.value = responseList
        println("response list : ${responseList.size}")
        responseList.clear()
}

And this is how it looks in the logs: Logs

Logs for you who cannot open the image :

I/System.out: response list : 0
I/System.out: Response is successful: NewsResponse(articleList=[Article(source=Source(id=wired, ...
I/System.out: Response is successful: NewsResponse(articleList=[Article(source=Source(id=techcrunch, ...
I/System.out: Response is successful: NewsResponse(articleList=[Article(source=Source(id=wired, ...
I/System.out: Response is successful: NewsResponse(articleList=[Article(source=Source(id=the-verge, ...

Btw data is fetched without an error and its correct. I've no issue with that.


Solution

  • The issue is that getEverything uses launch to create a background job, then returns before it knows the job is complete.

    To fix this, have getEverything return the data directly:

    suspend fun getEverything(topic: String, apiKey: String): Response? {
        _isLoading.value = true
        val response = withContext(Dispatchers.IO) {
            api.getEverything(topic, apiKey)
        }
        _isLoading.value = false
    
        return response.takeIf { it.isSuccessful }?.body()?.let { body ->
            println("Response is successful: $body")
        }.also {
            _isError.value = it == null
        }
    }
    

    In your Fragment, request the results and assign them:

    lifecycleScope.launch {
        _responseList.value = topicsList.mapNotNull { topic ->
            model.getResponse(topic, apiKey)
        }
    }