简体   繁体   中英

Using Global Variables with Coroutines in Android

I have the following code in which I launch a coroutine to handle the retrieving of data and storing them into the local database:

private var lastRequestedPage = 1
private var isRequestInProgress = false
private val viewModelScope = CoroutineScope(viewModelJob + Dispatchers.Main)

// function that is called from several places
private fun requestAndSaveData(){
    if(isRequestInProgress) return

    isRequestInProgress = true
    viewModelScope.launch{
        withContext(Dispatchers.IO){
            // 2 heavyweight operations here:
            // retrieve some data via Retrofit & place it into the local data via Room Persistence Library
        }
    }

    lastRequestedPage++
    isRequestInProgress = false
}

Description of the code snippet

The network call and database operation is done based on a boolean value called isRequestInProgress . When that is false, it is set to true, the network & database operations can be started by the coroutine and after that the lastRequestedPage is incremented before we set isRequestInProgress again to false so that the whole process can be started again by the program from any place. Note that lastRequestedPage is passed to the Retrofit network call function as argument since the web service from which the data comes uses Pagination (for the sake of brevity I left that out).

My question

Can I assume that this logic works with a coroutine like this ? Can I have some bad threading issues with a solution like this ? I am asking because I am new to the concept of coroutines and I was adapting this logic from another project of mine where I used listeners&callbacks with Retrofit to perform asynchronous work(whenever the Retrofit#onResponse method was called I incremented lastRequestedPage variable and set the isRequestInProgress to back to false).

Short answer: No, this won't work. It's incorrect.

When you call viewModelScope.launch { } or for that matter GlobalScope.launch { } , the block inside the launch gets suspended. And the program flow moves on to the next statement.

In your case, viewModelScope.launch { } will suspend the call to withContext(...) and move on to lastRequestedPage++ statement.

It will immediately increment the lastRequestedPage and toggle isRequestInProgress flag before even actually starting the request.

What you want to do is move those statements inside the launch { } block.

This is how the flow works.

  • Main thread
  • Suspend the block (launch call)
  • Proceed forward - Don't care about the suspended block - Make UI changes, etc.

To get a better sense of how it works, try this code.

    Log.d("cor", "Hello, world!") // Main thread
    GlobalScope.launch {
        // Suspend this block. Wait for Main thread to be available
        Log.d("cor", "I am inside the launch block") // Main thread
        withContext(Dispatchers.IO) {
            delay(100L) // IO thread
            Log.d("cor", "I am inside the io block") // IO thread
        }
        Log.d("cor", "IO work is done") // Back to main thread
    }
    Log.d("cor", "I am outside the launch block") // Main thread

The output is

D/cor: Hello, world!
D/cor: I am outside the launch block
D/cor: I am inside the launch block
D/cor: I am inside the io block
D/cor: IO work is done

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM