I have created function as below :
fun getPercentage(id:String): String {
var percentage=""
scope.launch {
percentage=repo.getPercentage(id)?.get(0)?.percent.toString()
Log.e("$$$ value >>","$$$ value >>"+percentage)
}
Log.e("$$$ value outside >>","$$$ value >>"+percentage)
return percenatge
}
Here, I can not return the updated value using the varibale : percentage.
Logs I am getting is as below :
$$$ value outside >>
$$$ value >> 50
means I can't return the latest value. something going wrong with the flow.
Someone suggested me to use async{} and await(). But I don't know How it will be helpful here?
Please guide. Thanks.
The launch function launches the coroutine in the background, and then continues. Your "outside" code is therefore running before the "inner" coroutine completes.
Use the async function instead to return a Deferred value from the coroutine:
fun getPercentage(id:String): Deferred<String> {
return scope.async {
percentage=repo.getPercentage(id)?.get(0)?.percent.toString()
Log.e("$$$ value >>","$$$ value >>"+percentage)
}
}
Note of course that its more likely you want to make getPercentage a suspend function, and then call await directly:
suspend fun getPercentage(id:String): String {
val percentageDeferred = scope.async {
percentage=repo.getPercentage(id)?.get(0)?.percent.toString()
Log.e("$$$ value >>","$$$ value >>"+percentage)
}
val percentage = percentageDeferred.await()
Log.e("$$$ value outside >>","$$$ value >>"+percentage)
return percentage
}
It's also likely you want to do something else before the await otherwise you're probably just better off making repo.getPercentage a suspend function as well, and calling it directly:
suspend fun getPercentage(id:String): String {
// if repo.getPercentage is a suspend function, this call suspends
// like the await in the previous example
val percentage = repo.getPercentage(id)?.get(0)?.percent.toString()
Log.e("$$$ value outside >>","$$$ value >>"+percentage)
return percentage
}
See Concurrent using async in the Kotlin docs.
I don't believe you necessarily need to use async in this case, particularly. You only need to be aware that whatever is inside launch { ... } is executed asynchronously. So by the time getPercentage returns, your coroutine may not have even started yet.
Keeping that in mind, I believe you may want to change the way your code works. The only way you can make fun getPercentage(id: String): String work without changing that signature is by replacing scope.launch { ... } with scope.runBlocking { ... }, but you probably don't want to do that, because it would block your thread.
Instead, you might change getPercentage to be a suspend method:
suspend fun getPercentage(id: String): String {
return repo.getPercentage(id)?.get(0)?.percent.toString()
}
However, suspend methods can only be called from inside a coroutine. So you would need to call it like this:
scope.launch {
val percentage = getPercentage("some ID")
// Now you can use `percentage` for whatever you need.
}
Related
I use livedata that collect item data, after it in onClicked() I get this data from livedata. What could be better approach for this?
lifecycleScope.launch {
lifecycleScope.async {
viewModel.fetchItem(args.workId)
}.await()
onClicked()
}
variables in viewModel
val item = _item.immutable()
private val _item = MutableLiveData<ViewState<Item?>>()
[...]
// I wait here for data from first code than I collect it because item is liveData as above
private fun onClicked() {
val item = viewModel.item.value?.dataOrNull
[...]
fetchItem method
fun fetchItem(id: Int) {
viewModelScope.launch {
_item.postAsyncValue { repository.getItem(id) }
}
}
Currently, I think your code is not doing what you think it does. fetchItem is not a suspend function. It launches a coroutine and immediately returns without waiting for the coroutine to finish. So, your async coroutine that calls it doesn't wait for that result either. There's no point in using async at all here since the code returns almost immediately.
I'm guessing that what you're trying to accomplish is wait for postAsyncValue to finish setting that new value. To do this, you need to make fetchItem a suspend function that waits for its work to be done.
I am not familiar with this postAsyncValue, but my best guess is that it is a helper extension function on MutableLiveData that takes a suspend lambda, calls it, and then sets the value to the LiveData on the main thread. If this is the case, you should just do this instead so the function actually waits for the task to be done before returning:
suspend fun fetchItem(id: Int) = withContext(Dispatchers.Main) {
_item.value = repository.getItem(id) // assuming getItem is a suspend function
}
//...
lifecycleScope.launch {
viewModel.fetchItem(args.workId)
onClicked()
}
If this isn't right, please add your source code for postAsyncValue and let me know.
Regarding your literal question, instead of using async followed immediately by await, you can use withContext(Dispatchers.IO) { }, but you would only do this if you are calling blocking code. You only need async when you're working with parallelism, so the compiler warns you that you're doing something silly if you immediately call await() after async { }.
I have created below function :
suspend fun isBatteryExistsInLocal(batteryId: String): Boolean {
val count = appDatabase.userBatteriesDao().checkBatteryExists(batteryId)
if (count > 0) {
return true
} else {
return false
}
}
which checks the particular record is exists in database or not.
checkBatteryExists is the dao method as below :
#Query("SELECT COUNT(*) FROM " + DatabaseConstant.mUserBatteriesTable + " WHERE isDeleted = 0 and batteryId= :batteryId")
suspend fun checkBatteryExists(batteryId:String): Int
The Method isBatteryExistsInLocal is called from below function in my view model class.
fun isBatteryExistsInLocal(batteryId:String): Boolean {
var isBatteryExistsInLocal = false
scope.launch {
isBatteryExistsInLocal =batteryRepository.isBatteryExistsInLocal(batteryId)
}
return isBatteryExistsInLocal
}
and the above method is calling from my fragment as below :
if (viewModel.isBatteryExistsInLocal(listNotifications[adapterPosition].batteryId)) {
But here, I am always getting false WHY ?
I have debugged and check that a record is exists in local database and also count variable is returns 1.
Then Why am getting false in above if condition ?
Please guide. What I am doing wrong.
Thanks.
Contrary to what you may have expected the function in ViewModel executes in following manner
fun isBatteryExistsInLocal(batteryId:String): Boolean {
var isBatteryExistsInLocal = false
scope.launch {
// Everything inside will be executed async
// outer function may already have returned by the time this completes
}
return isBatteryExistsInLocal
}
One way to fix this is to put the fragment code (viewModel.isBatteryExistsInLocal(listNotifications[adapterPosition].batteryId)) inside a coroutine, which can be done as
lifecycleScope.launch{
(viewModel.isBatteryExistsInLocal(listNotifications[adapterPosition].batteryId))
// other code
}
And change your ViewModel function to be suspending as
suspend fun isBatteryExistsInLocal(batteryId:String): Boolean {
return batteryRepository.isBatteryExistsInLocal(batteryId)
}
When you launch a coroutine, it is launched asynchronously, just like when you call a function that takes a callback. The coroutine is queued to start running, but the current function will possibly complete and return before that happens. Explanations of this issue are in the answers of this question even though they are about API callbacks. It's exactly the same issue when launching a coroutine.
The only way to convert a suspend function into one you can call from outside a coroutine to get a return value synchronously is to do something like use runBlocking or calling join on your launched Job. But this is not an acceptable solution because it will block your main thread and cause stutters or the ANR error.
The correct solution is to use a coroutine higher up in your workflow so you can freely use suspend functions wherever you need to. For instance, if isBatteryExistsInLocal is something you need as part of a response to some button press, you should launch a coroutine in your button's click listener, and then your entire sequence of actions can freely include suspend function calls.
By the way, just a tip, you can simplfy your suspend function. Instead of using if/else to return true or false, you could simply put return count > 0.
repository.callPermissionRemove(permissionID) is a suspending function which requires a viewModelScope to call within a viewModel. After calling the suspending function in the repository, I would like to reset the MutableLiveData back to null.
May I know what's the difference between these two sets of code and which one should I implement?
Code A
fun callPermissionRemove(permissionID: Int) {
viewModelScope.launch {
permissionRemoveAPIResponse.value = repository.callPermissionRemove(permissionID)
}
permissionRemoveAPIResponse.value = null
}
Code B
fun callPermissionRemove(permissionID: Int) {
viewModelScope.launch {
permissionRemoveAPIResponse.value = repository.callPermissionRemove(permissionID)
permissionRemoveAPIResponse.value = null
}
}
In your Code A example, line, setting response value to null will get called before repository.callPermissionRemove() will execute, whereas in the Code B example, lines will execute in the same order as written.
That being said, when resetting MutableLiveData field to null after use, you might want to consider using SingleLiveEvent. Example of it you can find here, and some more explanation of why and how here.
Let me start with example code snippets
suspend fun executeLive(result: MutableLiveData<Person>) {
val response = ... //suspend api request
mediatorLiveData.removeSource(response)
mediatorLiveData.addSource(response) {
result.value = sortData(it) // sortData is also suspend function which sortData at Dispatcher.Default
}
}
In this example, sortData can't call under lambda function(in this case addSource).And also I already declare executeLive as suspend, that why suspend api request can start at first. But sortData function show compile time error
Suspend function can only be called from a coroutine body
So how do I change my code structure to solve this problems?
Update: Is there any article about this?
A lambda is generally a callback function. Callback functions are so called because we wrap a block of code in a function, and pass it to someone else (or some place else) to be executed. It is a basic inversion of control where the code is not for you to execute, but someone else to do it (example the framework).
For example when you set a onClickListener on a button, we don't know when it will get called, we pass a lambda for the framework which takes care of the user interaction to call the specified action.
In your case similarly the suspend function is not calling the sortdata, it is passing it to the mediatorLiveData object to call it in its own context. It is not necessary the lambda you passed would be called from a coroutine body, as such this is not allowed.
You can solve this by converting the mediatorLiveData.addSource call into a suspending call itself with suspendCoroutine:
suspend fun executeLive(result: MutableLiveData<Person>) {
val response = ... //suspend api request
mediatorLiveData.removeSource(response)
val data = suspendCoroutine<TypeOfData> { cont ->
mediatorLiveData.addSource(response) { cont.resume(it) }
}
result.value = sortData(data)
}
I've used TypeOfData as a placeholder for whatever the type of data emitted by response is. Note that this will only work if the you're intending for a single emission to happen, though.
If you need to track multiple values, you can experiment with callbackFlow:
suspend fun executeLive(result: MutableLiveData<Person>) {
val response = ... //suspend api request
mediatorLiveData.removeSource(response)
callbackFlow<TypeOfData> {
mediatorLiveData.addSource(response) { offer(it) }
awaitClose { mediatorLiveData.removeSource(response) }
}
.collect { result.value = sortData(it) }
}
I am using a callback function when async ends. but it doesn't work well :(
my case:
fun function1(callback : (obj1: List<ObjT1>,obj2: List<ObjT1>)-> Unit?){
doAsync {
//long task
uiThread { callback(result1, result2) }
}
}
the callback is called but result1 and result2(lists) are empty. I checked previous the content of the list.
EDIT:
PROBLEM: my callback is a function that receives 2 objects result 1 and result2, the problem is the function callback sometimes receives the results empty, i check their content and is not empty.
It may be because you've declared return type as Unit? but are returning two values. A quick fix would be to put result1 and result2 in an array.
Now this question is about deprecated Kotlin library.
I recommend use coroutines.
Consider using Kotlin's coroutines. Coroutines is a newer feature in Kotlin. It is still technically in it's experimental phase, but JetBrains has told us that it is very stable.
Read more here: https://kotlinlang.org/docs/reference/coroutines.html
Here is some sample code:
fun main(args: Array<String>) = runBlocking { // runBlocking is only needed here because I am calling join below
val job = launch(UI) { // The launch function allows you to execute suspended functions
val async1 = doSomethingAsync(250)
val async2 = doSomethingAsync(50)
println(async1.await() + async2.await()) // The code within launch will
// be paused here until both async1 and async2 have finished
}
job.join() // Just wait for the coroutines to finish before stopping the program
}
// Note: this is a suspended function (which means it can be "paused")
suspend fun doSomethingAsync(param1: Long) = async {
delay(param1) // pause this "thread" (not really a thread)
println(param1)
return#async param1 * 2 // return twice the input... just for fun
}