简体   繁体   中英

How to update UI using coroutines after network call

I have a function with a network call using Dispatchers.IO , then I want to access and display the value as a Toast in my activity, notice that value of valueVariable is initialised to an empty string before the try-catch block so as to make it accessible and not scoped, but even after changing the value, when I call it withContext in the main dispatcher I only get an empty string .

Function:

fun calculateDocs() = CoroutineScope(Dispatchers.IO).launch {
    var valueVariable = ""
    try {
         db.collection("room1").get().addOnCompleteListener { task ->
            if (task.isSuccessful) {
                valueVariable = task.result?.size().toString()
                Log.d("TAG", valueVariable)
            } 
            else {
                Log.d("TAG", "Error getting documents: ", task.exception)
            }
         }
         withContext(Dispatchers.Main) {
              Toast.makeText(this@MainActivity, valueVariable, Toast.LENGTH_LONG).show()
         }
     } catch(e: Exception) {
         withContext(Dispatchers.Main) {
            Toast.makeText(this@MainActivity, e.message, Toast.LENGTH_LONG).show()
         }
     }
}

Your withContext block gets executed before the network call finishes hence the empty string in Toast , there are many ways to fix this but I think use of LiveData will require less code change on your end.

to achieve this first you need to define a LiveData property in the class which contains calculateDocs function

val networkResponse: MutableLiveData<String> = MutableLiveData()

Now in task.isSuccessful if block post the value to LiveData

if (task.isSuccessful) {
   valueVariable=task.result?.size().toString()
   Log.d("TAG", valueVariable)
   networkResponse.postValue(valueVariable) // Post value to LiveData
} 
else {
    Log.d("TAG", "Error getting documents: ", task.exception)
}

Finally observe this LiveData object in your Activity

networkResponse.observe(this, Observer{
   if(!it.isNullOrEmpty){
      Toast.makeText(this@MainActivity, it, Toast.LENGTH_LONG).show()
   }
}

The way in which you're using coroutines is pointless, because you don't call any blocking or suspend functions. You are calling an asynchronous API queueing function that has a callback, so it is non-blocking. The code that you want to happen after the asynchronous work is done has to go inside the callback. You can read more about this in this question . The answers also explain why you have a null response.

One of the main features of coroutines is that you can avoid callback-based code and instead write your code in order from top to bottom, instead of nesting sequentially-later code inside callbacks. However, if you just call synchronous callback-based code in your coroutine, you don't get any of this benefit.

If you want to use a coroutine effectively, you should call suspend function alternatives to the asynchronous callback-based functions. Provided you've imported the -ktx version(s) of whatever Firebase library(ies) you're using, there should be a Task.await() suspend function that is the coroutine alternative to adding an OnCompleteListener.

Note that you should be using viewModelScope or lifecycleScope , and usually you should not need to specify a dispatcher except where you're calling blocking code. The official recommendation from Jetbrains is to use a CoroutineScope that's supplied by your framework (Android Jetpack in this case) and to not change the Dispatcher from the default of Main at the launch call since it is most convenient to be able to safely call any non-blocking function, and suspend functions don't need dispatchers specified either.

The correct coroutine implementation of your code should look something like this:

fun calculateDocs() = viewModelScope.launch {
    val valueVariable = try {
        db.collection("room1").get().await().size().toString()
    } catch(e: Exception) {
        Log.d("TAG", "Error getting documents: ", e)
        Toast.makeText(this@MainActivity, e.message, Toast.LENGTH_LONG).show()
        return@launch
    }
    // Do something with successfully retrieved valueVariable
}

If you want to do it without coroutines, take your code out of the launch block and move your Toast call inside the callback (without the withContext wrapper). The callback will be called on the main thread anyway.

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