I want to bind a button click to a suspended function inside viewmodel.
this is my code:
RegistrationActivityViewModel.kt
suspend fun register() {
if (validateFields()) {
val result = SourceplusplusApiService.invoke().registerUser(username.value!!, email.value!!, password.value!!).await()
isRegistrationCompleted.value = getResultValue(result)
}
}
activity_registration.xml
<Button
android:text="Register"
android:onClick="#{()->viewmodel.register()}"
android:textSize="16sp" />
i get a databinding error that says ActivityRegistrationBindingImpl not generated. after searching a lot i realized that when i remove the suspend keyword and comment the code inside, it works fine but it has to be a suspended function.
Does anyone know how to fix it?
You cannot data bind to a suspend function, and IMHO a viewmodel should not be exposing a suspend function in the first place. I recommend:
Step #1: Remove the suspend keyword from register()
Step #2: Rewrite register() to run your code in a suitable coroutine scope, so any suspend functions that it calls are handled properly:
fun register() {
viewModelScope.launch(Dispatchers.Main) {
if (validateFields()) {
val result = SourceplusplusApiService.invoke().registerUser(username.value!!, email.value!!, password.value!!).await()
isRegistrationCompleted.value = getResultValue(result)
}
}
}
Here, I am using the viewModelScope option provided by androidx.lifecycle:lifecycle-viewmodel-ktx version 2.1.0-alpha01 and newer. Alternatively, manage your own coroutine scope. Dispatchers.Main will ensure that any results of that work is available to you on the Android main application thread.
Now, your data binding expression can refer to register(), while you still have a coroutine scope for calling downstream suspend functions.
Related
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:
Line 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
Initially I want to return 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?
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
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
}
}
I'm developing in MVP. In my Presenter, I call my Repository thanks to the suspend function. In this suspend function, I launch a Coroutine - not on the main thread.
When this coroutine is finished, I want to execute some code: I am using withContext() in order to do so.
In my Repository, I am launching a Coroutine (and maybe I am wrong) to insert my data, using DAO, in my Room database.
When I debug my application, it goes into my Presenter but crashes before going into my Repository.
Presenter
override suspend fun insertUserResponse(activity: Activity, data: UserResponse) {
scope.launch(Dispatchers.IO) {
try {
userResponseRepository.insertUserResponse(data)
withContext(Dispatchers.Main) {
redirectToClientMainPage(activity)
}
} catch (error: Exception) {
parentJob.cancel()
Log.e("ERROR PRESENTER", "${error.message}")
}
}
}
Repository
override suspend fun insertUserResponse(userResponse: UserResponse) {
GlobalScope.launch(Dispatchers.IO) {
try {
val existingUser: UserResponse? =
userResponseDAO.searchUserByID(userResponse.profilePOJO.uniqueID)
existingUser?.let {
userResponseDAO.updateUser(userResponse)
} ?: userResponseDAO.insertUser(userResponse)
} catch (error: Exception) {
Log.e("ERROR REPOSITORY", "${error.message}")
}
}
}
I have no error shown in my logcat.
EDIT:
Scope initialization
private var parentJob: Job = Job()
override val coroutineContext: CoroutineContext
get() = uiContext + parentJob
private val scope = CoroutineScope(coroutineContext)
val uiContext: CoroutineContext = Dispatchers.Main (initialized in my class constructor)
Stack trace
I finally found the answer!
Thanks to #sergiy I read part II of this article https://medium.com/androiddevelopers/coroutines-on-android-part-ii-getting-started-3bff117176dd and it mentions that you can't catch error except Throwable and CancellationException.
So instead of catching Exception, I traded it for Throwable. And finally I had an error shown in my logcat.
I am using Koin to inject my repository and all. I was missing my androidContext() in my Koin application.
That's it.
Without stacktrace it's hard to help you.
Here is an article, that might be helpful. Replacing ViewModel mentioned in the article with Presenter in your case you can get several general recommendations in using coroutines:
As a general pattern, start coroutines in the ViewModel (Read: Presenter)
I don't see the need to launch one more coroutine in your case in Repository.
As for switching coroutine's context for Room:
Room uses its own dispatcher to run queries on a background thread. Your code should not use withContext(Dispatchers.IO) to call suspending room queries. It will complicate the code and make your queries run slower.
I couldn't find the same recommendation in official docs, but it's mentioned in one of the Google code labs.
Both Room and Retrofit make suspending functions main-safe.
It's safe to call these suspend funs from Dispatchers.Main, even though they fetch from the network and write to the database.
So you can omit launch (as well as withContext) in Repository, since Room guarantees that all methods with suspend are main-safe. That means that they can be called even from the main thread. Also you can not to define Dispatchers.IO explicitly in Presenter.
One more thing. If Presenter's method insertUserResponse is suspend, then you call it from another launched coroutine, don't you? In that case, why you launch one more coroutine inside this method? Or maybe this method shouldn't be suspend?
In my viewmodel i've a function which uses coroutine to call an API.
fun loadPosts(){
GlobalScope.launch(coroutineContext){
changeState(load = true)
val list= withContext(Dispatchers.IO) {
apiService.getProfile(AUTH)
}
changeState(false,false,null)
showResult(list)
}
}
Everytime i click on a button, this function is fired, API is called and I get valid response. But once my api get Exception like 500 or Http 401 Unauthorized, then again when I hit the button, Coroutine is never called and seems it returns the again error message from cache.
For a use case:
I clicked on button -> Api is called -> got success response
Again I clicked on -> Api is called -> got success response
I disconnected my internet from phone
I clicked on button -> Api is called -> got exception something like ConnectionError
I connected my phone to internet
I clicked on button -> Api is not called -> got exception something like ConnectionError
Now even my phone has valid internet connection, I press on button, instead of calling api and wait for response, It gives me previous failed response again and again.
Earlier I was using Rxjava and I didn't face any such issue in that. I am new to coroutines so if anyone have any suggestion you are most welcome
Whenever you start a coroutine with a coroutine builder such as launch, you need to start it in a given CoroutineScope - this is enforced by the function being defined as an extension on CoroutineScope. This scope contains a CoroutineContext, which will define how the coroutine is executed.
Based on the comment above, I assume you are using roughly this setup:
abstract class BaseViewModel : ViewModel(), CoroutineScope {
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCleared() {
coroutineContext.cancel()
}
}
By using GlobalScope.launch(coroutineContext), you're actually overriding everything provided by GlobalScope with the context parameter. Plus, since your ViewModel itself is a scope already, you don't need to launch in GlobalScope in the first place. You can simply write down launch within the ViewModel with no scope specified (essentially this.launch {}) and no context passed to it, as it will get the one from the scope anyway.
The other issue is that you're using a regular Job as a part of your CoroutineContext. This Job becomes a parent for every coroutine you start, and whenever a child coroutine fails, such as on a network error, the parent Job gets cancelled too - meaning that any further children you attempt to start will fail immediately as well, as you can't start a new child under an already failed Job (see the Job documentation for more details).
To avoid this, you can use a SupervisorJob instead, which can also group your coroutines together as children, and cancel them when the ViewModel is cleared, but won't get cancelled if one of its children fail.
So to sum up the fixes to make at the code level quickly:
Use a SupervisorJob in the ViewModel (and while you're there, create this combined CoroutineContext just once, by directly assigning it, and not placing it in a getter):
override val coroutineContext: CoroutineContext = Dispatchers.Main + SupervisorJob()
Launch your coroutines in the scope defined in the ViewModel instead of in GlobalScope:
abstract class BaseViewModel : ViewModel(), CoroutineScope {
// ...
fun load() {
launch { // equivalent to this.launch, because `this` is a CoroutineScope
// do loading
}
}
}
You may also want to consider having your ViewModel contain a CoroutineScope instead of implementing the interface itself, as described here an discussed here.
There's a lot of reading to do on this topic, here are some articles I usually recommend:
https://medium.com/#elizarov/coroutine-context-and-scope-c8b255d59055
https://medium.com/#elizarov/the-reason-to-avoid-globalscope-835337445abc
https://proandroiddev.com/demystifying-coroutinecontext-1ce5b68407ad
... and everything else by Roman Elizarov on his blog, really :)
One more option.
If you use androidx.lifecycle:lifecycle-extensions:2.1.0, ViewModel now has viewModelScope extension property use SupervisorJob default. And it will be cleared automatically when ViewModel cleared.
I am learning Kotlin coroutines. I've read that runBlocking is the way to bridge synchronous and asynchronous code. But what is the performance gain if the runBlocking stops the UI thread?
For example, I need to query a database in Android:
val result: Int
get() = runBlocking { queryDatabase().await() }
private fun queryDatabase(): Deferred<Int> {
return async {
var cursor: Cursor? = null
var queryResult: Int = 0
val sqlQuery = "SELECT COUNT(ID) FROM TABLE..."
try {
cursor = getHelper().readableDatabase.query(sqlQuery)
cursor?.moveToFirst()
queryResult = cursor?.getInt(0) ?: 0
} catch (e: Exception) {
Log.e(TAG, e.localizedMessage)
} finally {
cursor?.close()
}
return#async queryResult
}
}
Querying the database would stop the main thread, so it seems that it would take the same amount of time as synchronous code? Please correct me if I am missing something.
runBlocking is the way to bridge synchronous and asynchronous code
I keep bumping into this phrase and it's very misleading.
runBlocking is almost never a tool you use in production. It undoes the asynchronous, non-blocking nature of coroutines. You can use it if you happen to already have some coroutine-based code that you want to use in a context where coroutines provide no value: in blocking calls. One typical use is JUnit testing, where the test method must just sit and wait for the coroutine to complete.
You can also use it while playing around with coroutines, inside your main method.
The misuse of runBlocking has become so widespread that the Kotlin team actually tried to add a fail-fast check which would immediately crash your code if you call it on the UI thread. By the time they did this, it was already breaking so much code that they had to remove it.
Actually you use runBlocking to call suspending functions in "blocking" code that otherwise wouldn't be callable there or in other words: you use it to call suspend functions outside of the coroutine context (in your example the block passed to async is the suspend function). Also (more obvious, as the name itself implies already), the call then is a blocking call. So in your example it is executed as if there wasn't something like async in place. It waits (blocks interruptibly) until everything within the runBlocking-block is finished.
For example assume a function in your library as follows:
suspend fun demo() : Any = TODO()
This method would not be callable from, e.g. main. For such a case you use runBlocking then, e.g.:
fun main(args: Array<String>) {
// demo() // this alone wouldn't compile... Error:() Kotlin: Suspend function 'demo' should be called only from a coroutine or another suspend function
// whereas the following works as intended:
runBlocking {
demo()
} // it also waits until demo()-call is finished which wouldn't happen if you use launch
}
Regarding performance gain: actually your application may rather be more responsive instead of being more performant (sometimes also more performant, e.g. if you have multiple parallel actions instead of several sequential ones). In your example however you already block when you assign the variable, so I would say that your app doesn't get more responsive yet. You may rather want to call your query asynchronously and then update the UI as soon as the response is available. So you basically just omit runBlocking and rather use something like launch. You may also be interested in Guide to UI programming with coroutines.
Within an Android app, I'm trying to use Fuel to make an HTTP request within a Kotlin coroutine. My first try is to use the synchronous mode inside a wrapper like this:
launch(UI) {
val token = getToken()
println(token)
}
suspend fun getToken(): String? {
var (request, response, result = TOKEN_URL.httpGet().responseString()
return result.get()
}
But that is returning an android.os.NetworkOnMainThreadException. The Fuel documentation mentions .await() and .awaitString() extensions but I haven't figured it out.
What is the best way to make a Fuel http request within a Kotlin coroutine from the main UI thread in an Android application? Stuck on this - many thanks...
Calling blocking code from a suspend fun doesn't automagically turn it into suspending code. The function you call must already be a suspend fun itself. But, as you already noted, Fuel has first-class support for Kotlin coroutines so you don't have to write it yourself.
I've studied Fuel's test code:
Fuel.get("/uuid").awaitStringResponse().third
.fold({ data ->
assertTrue(data.isNotEmpty())
assertTrue(data.contains("uuid"))
}, { error ->
fail("This test should pass but got an error: ${error.message}")
})
This should be enough to get you going. For example, you might write a simple function as follows:
suspend fun getToken() = TOKEN_URL.httpGet().awaitStringResponse().third
From the documentation "to start a coroutine, there must be at least one suspending function, and it is usually a suspending lambda"
Try this:
async {
val token = getToken()
println(token)
}