Reputation: 4578
I'm trying to use coroutines
in my code since in one of my function, I need to do multiple network calls and wait for its result. Below is a portion of my activity code:
class SellerDeliveryRegister : AppCompatActivity() {
lateinit var sellerDeliveryVM:SellerDeliveryVM
sellerDeliveryVM = ViewModelProviders.of(this).get(SellerDeliveryVM::class.java)
var uploadVehicleImageResult = sellerDeliveryVM.uploadVehiclesImages(uploadMotorImage1Url,uploadMotorImage2Url)
}
And below is a portion of my sellerDeliveryVM
ViewModel code:
class SellerDeliveryVM: ViewModel() {
suspend fun uploadVehiclesImages(uploadMotorImage1Url: String, uploadMotorImage2Url: String): Unit = withContext(Dispatchers.IO){
var uploadMotorImage1Result = "-1"; var uploadMotorImage2Result = "-1";
viewModelScope.launch(Dispatchers.IO) {
uploadMotorImage1Result = withContext(Dispatchers.Default) {
NetworkRepository.instance!!.uploadFile(uploadMotorImage1Url)
}
uploadMotorImage2Result = withContext(Dispatchers.Default) {
NetworkRepository.instance!!.uploadFile(uploadMotorImage2Url)
}
return@launch;
}
return@withContext
}
}
Please take note that previously uploadVehiclesImages
is a 'normal' function that doesn't use coroutine, so now I'm converting it to use coroutine.
Below are the problems I'm facing:
var uploadVehicleImageResult = sellerDeliveryVM.uploadVehiclesImages(uploadMotorImage1Url,uploadMotorImage2Url)
inside my SellerDeliveryRegister
class returns this error:Suspend function 'uploadVehiclesImages' should be called only from a coroutine or another suspend function
Boolean
from uploadVehiclesImages
, so I have return true
in place of the return@launch
and return false
in place of the return@withContext
, but then I will get the error return is not allowed here
, and Android Studio suggested me to make the changes above, although I really have no idea what the changes meant there.So what should I do to fix this problem 1, and can anyone enlighten me more on what's really happening on the problem 2?
Upvotes: 1
Views: 1667
Reputation: 93659
For part 1, you cannot use a coroutine to initialize a property. Coroutines return some time in the future, but properties have to be initialized immediately at class instantiation time. You'll have to change the strategy so you launch a coroutine that calls the suspend function, and then does something with the result when it arrives.
For part 2, you have an awkwardly composed suspend function. A proper suspend function typically isn't launching other coroutines unless it is using them to break down multiple simultaneous asynchronous actions and then waiting for them.
The convention for a suspend function is that it is safe to call from any Dispatcher. It's not proper to be sending off these background actions by launching a coroutine in a specific coroutine scope. Usually, a coroutine that calls a suspend function should not have to worry that the suspend function is going to launch some other coroutine in another scope, because this breaks support for cancellation.
Also, you can use async
instead of launch
to run suspend functions that you need a result from. That will avoid the awkward variables you've created to store the results (and you neglected to wait for).
Assuming you want to return both of these image results, you'll have to wrap them in another class, such as List. So your function could look like below. It returns something, not Unit. It uses aysnc
to run the two requests simultaneously.
suspend fun uploadVehiclesImages(uploadMotorImage1Url: String, uploadMotorImage2Url: String): List<ImageUploadResult> {
return listOf(uploadMotorImage1Url, uploadMotorImage2Url)
.map { aysnc { NetworkRepository.instance!!.uploadFile(it) }
.awaitAll()
}
I just put ImageUploadResult
to stand in for whatever this uploadFile
function returns. Maybe it's just Boolean
.
Whenever you do want to call it, you would use either lifecycleScope
(from an Activity or Fragment) or viewModelScope
(from a ViewModel) to launch a coroutine that calls it. For example:
fun doUpload(url1: String, url2: String) {
lifecycleScope.launch {
val results = uploadVehiclesImages(url1, url2)
// do something with results here
}
}
Upvotes: 1
Reputation: 1006944
So what should I do to fix this problem 1
Remove the property. uploadVehiclesImages()
returns Unit
; there is no value in having Unit
in a property. If your objective is to call uploadVehiclesImages()
when the viewmodel is created, put a call to it in an init
block, wrapped in a suitable coroutine launcher (e.g., viewModelScope.launch {}
).
This assumes that you are going to keep the function in its current form — your next question suggests that this function may not be the right solution.
Initially I want to return Boolean from uploadVehiclesImages,
More importantly, you seem to want it to return values more than once. That is not how functions work in Kotlin. One call to uploadVehiclesImages()
can return one Boolean
value, but not one now and one sometime in the future.
If you want to be emitting a stream of results, a suspend
function on its own is not the correct solution. For example, you could:
Use LiveData
, with the suspend
function updating the backing MutableLiveData
, or
Use a function that returns a StateFlow
or SharedFlow
Upvotes: 1