Search code examples
kotlincoroutinekotlin-coroutines

Coroutines how to wait for the data and then continue process


I'm learning coroutines with kotlin, and I have problem how process can wait until process 1 finished then it continue to process 2, from my sample below I have object Network which access API server using getNews(it's running well and get the data) I called this getNews from refreshNews using asynch - await, with the purpose it wait for the data then it continue running, but "The program Not Wait", it just running process 2 then process 1 finish, so I cannot capture data from API in refresh news

// process 1 - calling api this running well can get the data see process 2

object Network {
    var status : NewsApiStatus =  NewsApiStatus.LOADING

    private var viewModelJob = Job()
    private val coroutineScope = CoroutineScope(viewModelJob + Dispatchers.Main)

    fun getNews(filter: String, page: Int =1) : newsData? {
        var allNews : newsData? = null
        coroutineScope.launch {
            RetrofitClient.instance.getAllNews(filter, page).enqueue(object: Callback<newsData>{
                override fun onFailure(call: Call<newsData>, t: Throwable) {
                    status = NewsApiStatus.ERROR
                }

                override fun onResponse(
                    call: Call<newsData>,
                    response: Response<newsData>
                ) {
                    status = NewsApiStatus.DONE
                    var listResult = response.body()
                    if (listResult != null) {
                        if (listResult.data.isNotEmpty())  {
                            allNews = listResult
                            Timber.tag(TAG).i( "process 1 total allNews = ${allNews!!.data.size}")
                        }
                    }
                }
            })
        }
        return(allNews)
    }
}

// process 2 - calling process 1 with runBlocking

fun refreshNews() = runBlocking{
    val newsData = async {
        Network.getNews("")
    }
    Timber.tag(TAG).i("proses 2 ${newsData.await()?.data?.size}")
    // here I want newsData to wait until it has data
}

// this main program that call process 2

class NewsListViewModel(application: Application) : AndroidViewModel(application) {
    init {
        refreshNews()
    }
}

Solution

  • launch returns a reference to the started job. You can use it to wait for the job to finish by calling join():

    val job = GlobalScope.launch { // launch a new coroutine and keep a reference to its Job
        // ...
    }
    runBlocking {
        job.join() // wait until child coroutine completes
    }
    

    Currently, your getNews() launches a coroutine and immediately returns. allNews isn't initialised at that point yet.

    You need to either call job.join() inside getNews() (would make it blocking), or use async inside getNews() and return its result if you want to keep it asynchronous (you'd need to take the result differently from your http client as you won't be able to initialise the variable declared outside).

    It's worth to go through the official coroutine docs:

    Note that runBlocking is a coroutine builder that bridges the non-coroutine world and the code with coroutines. It's meant to be used at the top-level of your application.

    The name of runBlocking means that the thread that runs it (in this case — the main thread) gets blocked for the duration of the call, until all the coroutines inside runBlocking { ... } complete their execution. You will often see runBlocking used like that at the very top-level of the application and quite rarely inside the real code, as threads are expensive resources and blocking them is inefficient and is often not desired.

    Also see https://stackoverflow.com/a/53536713/330267