Cancelling a callback inside a suspendCancellableCoroutine - android

I have wrapped a callback in suspendCancellableCoroutine to convert it to a suspend function:
suspend fun TextToSpeech.speakAndWait(text: String) : Boolean {
val uniqueUtteranceId = getUniqueUtteranceId(text)
speak(text, TextToSpeech.QUEUE_FLUSH, null, uniqueUtteranceId)
return suspendCancellableCoroutine { continuation ->
this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener() {
override fun onDone(utteranceId: String?) {
if(utteranceId == uniqueUtteranceId) {
Timber.d("word is read, resuming with the next word")
continuation.resume(true)
}
}
})
}
}
I'm calling this function with the lifecycleScope coroutine scope of the fragment and I was assuming that it was cancelled when fragment is destroyed. However, LeakCanary reported that my fragment was leaking because of this listener and I verified with logs that the callback was called even after the coroutine is cancelled.
So it seems that wrapping with suspendCancellableCoroutine instead of suspendCoroutine does not suffice to cancel the callback. I guess I should actively check whether the job is active, but how? I tried coroutineContext.ensureActive() and checking coroutineContext.isActive inside the callback but IDE gives an error saying that "suspension functions can be called only within coroutine body" What else can I do to ensure that it doesn't resume if the job is cancelled?

LeakCanary reported that my fragment was leaking because of this listener and I verified with logs that the callback was called even after the coroutine is cancelled.
Yes, the underlying async API is unaware of Kotlin coroutines and you have to work with it to explicitly propagate cancellation. Kotlin provides the invokeOnCancellation callback specifically for this purpose:
return suspendCancellableCoroutine { continuation ->
this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener() {
/* continuation.resume() */
})
continuation.invokeOnCancellation {
this.setOnUtteranceProgressListener(null)
}
}

If you want to remove your JeLisUtteranceProgressListener regardless of result (success, cancellation or other errors) you can instead use a classic try/finally block:
suspend fun TextToSpeech.speakAndWait(text: String) : Boolean {
val uniqueUtteranceId = getUniqueUtteranceId(text)
speak(text, TextToSpeech.QUEUE_FLUSH, null, uniqueUtteranceId)
return try {
suspendCancellableCoroutine { continuation ->
this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener() {
override fun onDone(utteranceId: String?) {
if(utteranceId == uniqueUtteranceId) {
Timber.d("word is read, resuming with the next word")
continuation.resume(true)
}
}
})
} finally {
this.setOnUtteranceProgressListener(null)
}
}

In addition to the accepted answer, I recognized that continuation object has an isActive property as well. So alternatively we can check whether coroutine is still active inside the callback before resuming:
return suspendCancellableCoroutine { continuation ->
this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener()
{
override fun onDone(utteranceId: String?) {
if(utteranceId == uniqueUtteranceId) {
if (continuation.isActive) {
continuation.resume(true)
}
}
}
})
continuation.invokeOnCancellation {
this.setOnUtteranceProgressListener(null)
}
}

Related

Switch from Main dispatcher to IO dispatcher from non lifecycle class

I m using coroutines in my android app, and i have this function that need to communicate with UI and Main thread.
private suspend fun init() : RequestProcessor<LocalData, ApiResult, ApiError>
{
#Suppress("LeakingThis")
_localData = listener.databaseCall()
withContext(Dispatchers.IO) {
if (_localData == null)
{
checkIfShouldFetch(null, null)
}
else
{
withContext(Dispatchers.Main) {
mediatorLiveData.addSource(_localData!!) { newLocalData ->
mediatorLiveData.removeSource(_localData!!)
// I want to call this from the IO thread.
checkIfShouldFetch(newLocalData, _localData)
}
}
}
}
return this
}
My question is, how to come back to the root context (IO) from the nested context (Main)?
when i call again withContext(Dispatchers.IO) this error is displayed : Suspension functions can be called only within coroutine body
I need to call the function checkIfShouldFetch(newLocalData, _localData) from the IO context and i didn't find how to do it.
You would need to launch a coroutine to call withContext in that place. What you can try to do without launching a coroutine is to use suspendCoroutine or suspendCancellableCoroutine to suspend execution until callback is fired:
withContext(Dispatchers.Main) {
val newLocalData = addSource()
checkIfShouldFetch(newLocalData, _localData)
}
suspend fun addSource(): LiveData<...> = suspendCoroutine { continuation ->
mediatorLiveData.addSource(_localData) { newLocalData ->
mediatorLiveData.removeSource(_localData)
continuation.resumeWith(newLocalData)
}
}
suspend fun checkIfShouldFetch(newLocalData: ..., _localData: ...) = withContext(Dispatchers.IO) {
// ...
}

SuspendCoroutine code not reachable when using with Firebase auth

I have a suspendCoroutine in my repository with which I want to send data back to my ViewModel -
suspend fun sendPasswordResetMail(emailId: String): Boolean {
return withContext(Dispatchers.IO) {
suspendCoroutine { cont ->
firebaseAuth?.sendPasswordResetEmail(emailId)
?.addOnCompleteListener {
cont.resume(it.isSuccessful)
}
?.addOnFailureListener {
cont.resumeWithException(it)
}
}
}
}
However, neither of the listeners are called. Debugger says no executable code found at line where 'cont.resume(it.isSuccessful)' or 'cont.resumeWithException(it)' are.
I tried 'Dispatchers.IO', 'Dispatchers.Main' and 'Dispatchers.Default' but none of them seem to work. What could I be doing wrong?
My ViewModel code -
isEmailSent : LiveData<Boolean> = liveData {
emit(firebaseAuthRepo.sendPasswordResetMail(emailId))
}
and
fragment -
viewModel.isEmailSent.observe(viewLifecycleOwner, { flag ->
onResetMailSent(flag)
})
I believe you are calling
isEmailSent : LiveData<Boolean> = liveData {
emit(firebaseAuthRepo.sendPasswordResetMail(emailId))
}
this piece of code everytime for sending email
and
viewModel.isEmailSent.observe(viewLifecycleOwner, { flag ->
onResetMailSent(flag)
})
this piece only once.
Assuming that's true what you are essentially observing is the initial live data that was created with the model while it is being replaced everytime when resent is called. Instead call
isEmailSent.postValue(firebaseAuthRepo.sendPasswordResetMail(emailId))
from inside of a coroutine.
Also for the debugger not showing anything try adding a log above the cont.resume call and cont.resumeWithException call since it has worked for me in the past.
I think the easier way to achieve this is by using firebase-ktx and the await() function (which does what you are trying under the hood):
suspend fun sendPasswordResetMail(emailId: String): Boolean {
try {
firebaseAuth?.sendPasswordResetEmail(emailId).await()
return true
} catch(e: Exception) {
return false
}
}
Another way would be to use flow:
suspend fun sendPasswordResetMail(emailId: String): Boolean = flow<Boolean {
firebaseAuth?.sendPasswordResetEmail(emailId).await()
emit(true)
}.catch { e: Exception -> handleException(e) }
You could then observe this in your fragment by putting the code inside your viewmodel and calling .asLiveData()

how to use Coroutine in kotlin to call a function every second

i just created an app where my function getdata() call every second to fetch new data from server and updateui() function will update view in UI i don't use any asynctask or coroutine in my app i wants to do this please tell me how i can do that.
here's my code...
private fun getdata(){
try {
val api = RetroClient.getApiService()
call = api.myJSON
call!!.enqueue(object : Callback<ProductResponse> {
override fun onResponse(
call: Call<ProductResponse>,
response: Response<ProductResponse>
) {
if (response.isSuccessful) {
productList = response.body()!!.data
for (list in productList) {
if (list.BB.equals("AAA")) {
aProductList.add(list)
}
}
if (recyclerView.adapter != null) {
eAdapter!!.updatedata(aProductList)
}
updateui()
}
}
override fun onFailure(call: Call<ProductResponse>, t: Throwable) {
println("error")
}
})
} catch (ex: Exception) {
} catch (ex: OutOfMemoryError) {
}
Handler().postDelayed({
getdata()
}, 1000)
}
private fun updateui() {
try {
//some code to handel ui
} catch (e: NumberFormatException) {
} catch (e: ArithmeticException) {
} catch (e: NullPointerException) {
} catch (e: Exception) {
}
}
To run a function every second with coroutines:
val scope = MainScope() // could also use an other scope such as viewModelScope if available
var job: Job? = null
fun startUpdates() {
stopUpdates()
job = scope.launch {
while(true) {
getData() // the function that should be ran every second
delay(1000)
}
}
}
fun stopUpdates() {
job?.cancel()
job = null
}
However, if getData() only starts a network request and doesn't wait for its completion, this might not be a very good idea. The function will be called a second after it finished, but because the network request is done asynchronously it may be scheduled way too much.
For example if the network request takes 5 seconds, it will have been started 4 more times before the first one even finished!
To fix this, you should find a way to suspend the coroutine until the network request is done.
This could be done by using a blocking api, then pass Dispatchers.IO to the launch function to make sure it's done on a background thread.
Alternatively you could use suspendCoroutine to convert a callback-based api to a suspending one.
Update - Lifecycle scope
Inside a component with a Android Lifecycle you could use the following code to automate repeating ui updates:
fun startUpdates() {
val lifecycle = this // in Activity
val lifecycle = viewLifecycleOwner // in Fragment
lifecycle.lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
// this block is automatically executed when moving into
// the started state, and cancelled when stopping.
while (true) {
getData() // the function to repeat
delay(1000)
}
}
}
}
This code requires the current androidx.lifecycle:lifecycle-runtime-ktx dependency.
The above remark about async, blocking or suspending code inside getData() still applies.
it's not advisable to hit the server every second. if you need to get data continuously try the socket. Because some times your server takes more than a few seconds to respond to your request. Then all your requests will be in a queue..if you still need to try with this.
fun repeatFun(): Job {
return coroutineScope.launch {
while(isActive) {
//do your network request here
delay(1000)
}
}
}
//start the loop
val repeatFun = repeatRequest()
//Cancel the loop
repeatFun.cancel()
For those who are new to Coroutine
add Coroutine in Build.gradle
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'
To create a repeating Job
/**
* start Job
* val job = startRepeatingJob()
* cancels the job and waits for its completion
* job.cancelAndJoin()
* Params
* timeInterval: time milliSeconds
*/
private fun startRepeatingJob(timeInterval: Long): Job {
return CoroutineScope(Dispatchers.Default).launch {
while (NonCancellable.isActive) {
// add your task here
doSomething()
delay(timeInterval)
}
}
}
To start:
Job myJob = startRepeatingJob(1000L)
To Stop:
myJob .cancel()
I ended up doing like this with an extension function:
fun CoroutineScope.launchPeriodicAsync(repeatMillis: Long, action: () -> Unit) = this.async {
while (isActive) {
action()
delay(repeatMillis)
}
}
then call it like:
val fetchDatesTimer = CoroutineScope(Dispatchers.IO)
.launchPeriodicAsync(TimeUnit.MINUTES.toMillis(1)) {
viewModel.fetchDeliveryDates()
}
and cancel it like:
fetchDatesTimer.cancel()
My solution in Kotlin inside MainViewModel
fun apiCall() {
viewModelScope.launch(Dispatchers.IO) {
while(isActive) {
when(val response = repository.getServerData()) {
is NetworkState.Success -> {
getAllData.postValue(response.data)
}
is NetworkState.Error -> this#MainViewModel.isActive = false
}
delay(1000)
}
}
}
sealed class NetworkState<out R> {
data class Success<out T>(val data: T): NetworkState<T>()
data class Error(val exception: String): NetworkState<Nothing>()
object Loading: NetworkState<Nothing>()
}
My solution for one time running a code after check for something is successful and checking for that periodically, function is:
fun CoroutineScope.launchPeriodic(repeatMillis: Long, action: () -> Unit) : Job {
return launch {
while (!enabled) {
action()
delay(repeatMillis)
}
}
}
and start periodic function here (in which action runs every 2 seconds), which automatically ends up when something is enabled and some code run:
CoroutineScope(Dispatchers.IO).launchPeriodic(TimeUnit.SECONDS.toMillis(2)) {
if(checkIfSomethingIsEnabledCodeIsHere) {
enabled = true
//some code here to run when it is enabled
}
}

Kotlin Coroutine wait for Retrofit Response

I'm trying to use the Android MVVM pattern with a repository class and Retrofit for network calls. I have the common problem that I can't get the coroutine to wait for the network response to return.
This method is in my ViewModel class:
private fun loadConfigModel() {
val model = runBlocking {
withContext(Dispatchers.IO) {
configModelRepository.getConfigFile()
}
}
configModel.value = model
}
In ConfigModelRepository, I have this:
suspend fun getConfigFile(): ConfigModel {
val configString = prefs.getString(
ConfigViewModel.CONFIG_SHARED_PREF_KEY, "") ?: ""
return if (configString.isEmpty() || isCacheExpired()) {
runBlocking { fetchConfig() }
} else {
postFromLocalCache(configString)
}
}
private suspend fun fetchConfig(): ConfigModel {
return suspendCoroutine { cont ->
dataService
.config() // <-- LAST LINE CALLED
.enqueue(object : Callback<ConfigModel> {
override fun onResponse(call: Call<ConfigModel>, response: Response<ConfigModel>) {
if (response.isSuccessful) {
response.body()?.let {
saveConfigResponseInSharedPreferences(it)
cont.resume(it)
}
} else {
cont.resume(ConfigModel(listOf(), listOf()))
}
}
override fun onFailure(call: Call<ConfigModel>, t: Throwable) {
Timber.e(t, "config fetch failed")
cont.resume(ConfigModel(listOf(), listOf()))
}
})
}
}
My code runs as far as dataService.config(). It never enters onResponse or onFailure. The network call goes and and returns properly (I can see this using Charles), but the coroutine doesn't seem to be listening for the callback.
So, my question is the usual one. How can I get the coroutines to block such that they wait for this callback from Retrofit? Thanks.
The problem must be that response.body() returns null since that is the only case that is missing a call to cont.resume(). Make sure to call cont.resume() also in that case and your code should at least not get stuck.
But like CommonsWare points out, even better would be to upgrade to Retrofit 2.6.0 or later and use native suspend support instead of rolling your own suspendCoroutine logic.
You should also stop using runBlocking completely. In the first case, launch(Dispatchers.Main) a coroutine instead and move configModel.value = model inside of it. In the second case you can just remove runBlocking and call fetchConfig() directly.

Kotlin coroutine doesn't wait to be done

I have a legacy project where I want to use coroutines when contacting the backend. The backend have been handle by a sdk delivered by Hybris. It use volley for instance, and with some callbacks. What I want is to wrap those callbacks with a coroutine. But the problem I have is that the coroutine doesn't wait to be done, it start the coroutine, and keep going to next lines, and method returns a value, and long after that the coroutine finish.
My code:
suspend fun ServiceHelper.getList(): ListOfWishes {
return suspendCancellableCoroutine { continuation ->
getAllLists(object : ResponseReceiver<ListOfWishes> {
override fun onResponse(response: Response<ListOfWishes>?) {
continuation.resume(response?.data!!)
}
override fun onError(response: Response<ErrorList>?) {
val throwable = Throwable(Util.getFirstErrorSafe(response?.data))
continuation.resumeWithException(throwable)
}
}, RequestUtils.generateUniqueRequestId(), false, null, object : OnRequestListener {
override fun beforeRequest() {}
override fun afterRequestBeforeResponse() {}
override fun afterRequest(isDataSynced: Boolean) {}
})
}
}
The helper method:
suspend fun ServiceHelper.wishLists(): Deferred<ListOfWishes> {
return async(CommonPool) {
getWishList()
}
}
And where the coroutine is called:
fun getUpdatedLists(): ListOfWishes? {
val context = Injector.getContext()
val serviceHelper = Util.getContentServiceHelper(context)
var list = ListOfWishLists()
launch(Android) {
try {
list = serviceHelper.wishLists().await()
} catch (ex: Exception){
Timber.d("Error: $ex")
}
}
return list
So instead of waiting for serviceHelper.wishLists().await() is done, it return list. I have also tried to make the method return a runBlocking{}, but that only block the UI thread and doesn't end the coroutine.
Coroutines don't work like this. getUpdatedLists() method can't wait a coroutine to finish its execution without being a suspend method itself. If method getUpdatedLists() defined in Activity or Fragment, you can launch a coroutine and do something, e.g. update UI, in it after serviceHelper.wishLists().await() is executed. It will look something like this:
fun loadUpdatedLists() {
val context = Injector.getContext()
val serviceHelper = Util.getContentServiceHelper(context)
lifecycleScope.launch {
try {
val list = serviceHelper.wishLists().await()
// use list object, for example Update UI
} catch (ex: Exception){
Timber.d("Error: $ex")
}
}
}
lifecycleScope - CoroutineScope tied to LifecycleOwner's Lifecycle. To use it add dependency:
androidx.lifecycle:lifecycle-runtime-ktx:2.4.0 or higher.

Categories

Resources