Non suspend function is not executed after suspend function - android

I have a suspend function userRepo.updateUserWithNewPersonalDetails(details) and after executing this function I want to execute success() function which is a call back.
but the issue is success() is not getting executed.
any suggestions on how to get this to work.
this sequence does not work
SUCCESS -> {
progress.postValue(GONE)
userRepo.updateUserWithNewPersonalDetails(details) // EXECUTED
success() // NOT EXECUTED
}
if I change it to call success() first then save to repo, it works fine. but this is not the right way of doing it I think.
could you suggest please
SUCCESS -> {
progress.postValue(GONE)
success() // EXECUTED
userRepo.updateUserWithNewPersonalDetails(details) // EXECUTED
}
Fragment call
viewModel.save(personalDetails) { activity?.onBackPressed() }
ViewModel
fun save(details: PersonalDetails, success: () -> Unit) {
viewModelScope.launch {
userRepo.savePersonalDetails(details).collect {
when (it.status) {
LOADING -> {
progress.postValue(VISIBLE)
}
SUCCESS -> {
progress.postValue(GONE)
userRepo.updateUserWithNewPersonalDetails(details)
success() // THIS IS NOT EXECUTED
}
ERROR -> {
progress.postValue(GONE)
error.postValue(ErrorResult(errorCode = SNACKBAR_ID_USER_DETAILS_SAVE_FAIL))
}
}
}
}
}
userRepository
suspend fun updateUserWithNewPersonalDetails(details: PersonalDetails) {
userDao.get().collect { cachedUser ->
val updatedCachedUser = UserDB(cachedUser.id, etc..)
userDao.save(updatedCachedUser)
}
}

Can you please show me the function that you call?, did you already use the breakpoint to make sure the function it self was called?. cause if you dont, i think you might use nullable variable and the value will retrieved after the suspend function (userRepo.blabla()) finished, if yes. maybe you can call .invokeOnCompletion { /your Success Function/ success() }

success() method isn't called because you collect Flow in updateUserWithNewPersonalDetails method:
userDao.get().collect {...}
It suspends a coroutine execution. My guess is that it is an infinite Flow, which doesn't complete until coroutine is completed. That's why userDao.get().collect suspends execution.
I don't quite understand what you are trying to achieve in the updateUserWithNewPersonalDetails method, but it seems it doesn't update the DB. If you want to update user details in the DB, you don't need to collect Flow. You should have something like this:
suspend fun updateUserWithNewPersonalDetails(details: PersonalDetails) {
userDao.update(details)
}
where userDao.update(details) is a suspend method, which updates DB:
suspend fun update(details: PersonalDetails)

Related

How to check if a suspend function has completed executing

Hello I have a suspend function updateUserWithNewPersonalDetails(), I want check if the function has been executed and once executed I want to call success() function.
I am unable to use invokeOnCompletion on the function call
How can I do this please or what is the other way I can call success() function once updateUserWithNewPersonalDetails is executed
suspend function
suspend fun updateUserWithNewPersonalDetails(details: PersonalDetails) {
userDao.get().collect { cachedUser ->
val updatedCachedUser = UserDB(cachedUser.id, ...)
userDao.save(updatedCachedUser)
}
}
from viewmodel I want to call the above function and on success or invoke successfull I want to call success function
userRepo.updateUserWithNewPersonalDetails(details). invokeOnCompletion{
success()
}
Thanks
R
Currently your function updateUserWithNewPersonalDetails never completes because the Flow returned by Room never completes (it monitors for DB changes forever).
Assuming you are interested only in the first change you can do:
suspend fun updateUserWithNewPersonalDetails(details: PersonalDetails) {
val cachedUSer = userDao.get().first() // this will get the current value from the DB
val updatedCachedUser = UserDB(cachedUser.id, ...)
userDao.save(updatedCachedUser)
}
Here first() gets the current value from the DB (or throws if it doesn't exist) and does NOT observe forever. Alternatively you can use firstOrNull().
Then your other function becomes:
updateUserWithNewPersonalDetails()
success()

How to implement Promise.all like functionality in kotlin

Am trying to get messages from twillio server using its sdk method so on calling the method it return callback to return the List of messages. I have list of conversation,i want to get all messages for conversation so am using forEach like this
allConversations.forEach { conversation ->
conversation.getMessageByIndex(conversation.lastMessageIndex){
conversationLastMessages[conversation.sid] = it
}
}
So i want to wait until all listners get executed and then want to change the state of ui.
You can make all requests in parallel and wait for all of them to finish following next steps:
Create a suspend function getMessage, which will be responsible for suspending the calling coroutine until request is executed. You can use suspendCoroutine or suspendCancellableCoroutine for that:
suspend fun getMessage(conversation: Conversation) = suspendCoroutine<Message?> { continuation ->
conversation.getMessageByIndex(conversation.lastMessageIndex, object : CallbackListener<Message> {
override fun onError(errorInfo: ErrorInfo) {
continuation.resume(null) // resume calling coroutine
// or continuation.resumeWithException() depend whether you want to handle Exception or not
}
override fun onSuccess(result: Message) {
continuation.resume(result) // resume calling coroutine
}
})
}
Run requests in parallel using async coroutine builder and Dispatchers.IO dispatcher to offload work from the Main Thread:
async(Dispatchers.IO) {
getMessage(conversation)
}
To run all this you need to use some instance of CoroutineScope to launch a coroutine. In ViewModel it can be viewModelScope, in Activity/Fragment - lifecycleScope. For example in ViewModel:
viewModelScope.launch {
val allConversations = ...
allConversations.map { conversation ->
async(Dispatchers.IO) {
getMessage(conversation)
}
}.awaitAll() // waiting for all request to finish executing in parallel
.forEach { message -> // iterate over List<Message> and fill conversationLastMessages
conversationLastMessages[message.getConversationSid()] = message
}
// here all requests are completed and UI can be updated
}

Kotlin Coroutines how to achieve to call api in right way

Hey I want to call api from object class. I am new in Coroutines. I tried some code, but i am not sure is it correct way of doing it or not.
Inside LoginHelper there is function called logout have more that one function. I want to excute api call first. then i want to excute other function inside logout.
In Mainactivity I am calling LoginHelper.logout it will finish then i need to excute other line. But i don't want to make suspend function because it's using other place as well.
Also i got a errorProcess:
com.dimen.app, PID: 12496
android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1605)
Session.kt
interface Session{
#DELETE("/session/delete")
fun deleteSession(): Call<Void>
}
SessionRepository.kt
suspend fun deleteSession(): RequestResult<Void> {
return apiCall(api.deleteSession())
}
RequestResult is a Sealed Class
sealed class RequestResult<out T : Any> {
data class Success<out T : Any>(): RequestResult<T>
data class Error(): RequestResult<Nothing>()
fun result(success: (data: T?) -> Unit),error: (error: Error) -> Unit)
}
MainActivity.kt
private fun setupLogout() {
logoutButton.setOnClickListener {
LoginHelper.logout() // need to wait untill this finish
// more logic here....
}
}
LoginHelper.kt
object LoginHelper {
fun logout() {
logD("logout")
deleteSession() // need to wait untill this finish and then excute more function....
}
private fun deleteSession() {
runBlocking{
apiCall.deleteSession().execute()
}
}
}
Never use runBlocking in an Android app unless you know exactly what you're doing. It's the wrong choice 99% of the time because it defeats the purpose of using coroutines. Blocking means the current thread waits for the coroutine to run its asynchronous code. But you cannot block the main thread because that freezes the UI.
Since your LoginHelper is an object or singleton, it needs its own CoroutineScope if it's going to launch coroutines.
You can make deleteSession() a suspend function so it can call the api.deleteSession() suspend function.
You can make logout() launch a coroutine to sequentially delete the session and subsequently perform other tasks. And you can make it return the launched Job so other classes can choose whether or not to simply start the logout, or to start and wait for the logout in a coroutine.
object LoginHelper {
private val scope = CoroutineScope(SupervisorJob() + CoroutineName("LoginHelper"))
fun logout(): Job = scope.launch {
logD("logout")
deleteSession()
// .... more functions that happen after deleteSession() is complete
}
private suspend fun deleteSession() {
Tokenclass.getToken()?.let {
logE("token ::-> $it")
apiCall.deleteSession(it).execute()
}
}
}
If you want the outside class to be able to wait for the logout to complete, it can call join() on the returned Job in its own coroutine, for example:
logoutButton.setOnClickListener {
lifecycleScope.launch {
LoginHelper.logout().join()
// more logic here....
}
}
If you don't need to wait for it in the activity, you don't need to start a coroutine, and you don't need to call join().

getting error of suspend function while using coroutine

also, I'm calling my suspend function inside a coroutine scope, I'm getting an error that Suspends functions can only be called within a coroutine body. how's that happening?
lifecycleScope.launchWhenCreated{
viewModel.tickets.observe(viewLifecycleOwner, {
it?.let { data ->
adapter.submitData(data)
}
})
}
Hi you are calling suspend function inside observe lambda.
Change the order like this:
viewModel.tickets.observe(viewLifecycleOwner, { data ->
lifecycleScope.launchWhenCreated{
data?.let { list ->
adapter.submitData(list)
}
})
}

Suspend Coroutine Hangs

Trying to get a deeper into coroutines. I have a suspendCancellableCoroutine that is supposed to fetch a network response. I can see in Charles that the network call is dispatched and returns successfully. However, my app just hangs on the network request line.
private suspend fun fetchVisualElementsFromServer(clubId: String): VisualElements {
return suspendCancellableCoroutine { cont ->
visualElementsService.fetchVisualElementsForClub(clubId)
.enqueue(object : Callback<ResultVisualElements> {
override fun onResponse(
call: Call<ResultVisualElements>,
response: Response<ResultVisualElements>
) {
if (response.isSuccessful) {
response.body()?.let {
if (it.result == RESULT_SUCCESS) {
saveVisualElementsResponseInSharedPreferences(it.visual_elements)
cont.resume (it.visual_elements)
} else {
cont.cancel() //edit
}
} ?: cont.cancel() //edit
} else {
cont.cancel(IOException("${response.code()}: ${response.errorBody()}"))
}
}
override fun onFailure(call: Call<ResultVisualElements>, t: Throwable) {
Timber.e(t, "visual elements fetch failed")
cont.cancel() // edit
}
})
}
}
This where it hangs:
VisualElementsService.kt
fun fetchVisualElementsForClub(clubId: String): Call<ResultVisualElements> {
return dataFetcherService.getVisualElementsForClub(clubId)
}
What am I missing here? I tried to make the fetchVisualElementsForClub() a suspend function, but that just makes the suspendCancellableCoroutine throw a Suspension functions can only be called within coroutine body error. But I thought that his was within a coroutine body?
Any help appreciated. Thanks.
EDIT
I response to Rene's answer below, I want to add a few things.
You are right, I am missing three cont.cancel() calls. I've modified the OP. Good points.
I have breakpoints all over the suspendCancellableCoroutine such that any possible scenario (success, failure, etc.) will be hit. But that callback never registers.
Wondering if there is something missing in fetchVisualElementsForClub() that is needed to pass the callback up to the suspendCancellableCoroutine. That seems to be where this is hanging.
You must call cont.resume() or cont.cancel() on every branch in your callback handling.
But in your example at least three cases are missing.
If the response is successful but no body is provided, you call nothing.
If the response is successful, the body is not null, but the it.result is not RESULT_SUCCESS you call nothing.
If something goes wrong in onFailure, you call nothing.
As long as neither resume or cancel is invoked, the coroutine will stay suspended, means hangs.
when you use suspend keyword your are telling that function shoud be called inside a coroutine bode, for example:
suspend fun abc(){
return
}
when you want to call above function you have to call it inside coroutines such as below:
GlobalScope.launch {
abc()
}

Categories

Resources