Cancel file upload (retrofit) started from coroutine kotlin android - android

I'm trying to get rid of RxJava2 in my project and replace it with kotlin coroutines. 90% of my RxJava code is no longer exists but I still can`t replace one network request.
User can send photo to backend in my App (multipart form data request using retrofit). User also can cancel photo upload if it was not loaded yet. With RxJava I was able to keep Disposable object of upload request and if it is not disposed yet I could dispose it if user clicked cancel upload button. As result of this action network request was canceled too. So we could save some user traffic and battery.
Is it possible to achieve same logic with kotlin coroutines? In official docs written that coroutines cancellation is cooperative and we need some suspending function in loop (or in between file parts send) in order to stop coroutine.
So is this the case when RxJava is better choice or did I missed something?

If you are using retrofit for your network calls, you can add their coroutines call adapter from here.
You cancel a coroutine's running part by cancelling it's job. For example if you are using launch to launch your coroutine, it returns a Job object which can be cancelled.
val job = launch {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancel() // cancels the job
job.join() // waits for job's completion
println("main: Now I can quit.")
If you are not using retrofit and using another library, you can retrieve isActive inside you coroutine and cancel the request from your library.
fun main(args: Array<String>) = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val job = launch {
var nextPrintTime = startTime
var i = 0
while (isActive) { // cancellable computation loop
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
}

Related

Best way to call suspend coroutine from Executor(ThreadPoolExecutor)

I am currently developing face detection with with a camera library & mlkit.
Camera library provides a FrameProcessor in which I am getting Frame in a stream manner. I have written FrameProcessingTask inside a suspend function.
Inside cameralib FrameProcessor is called inside a ThreadPoolExecutor
mFrameProcessingExecutor.execute(new Runnable() {
#Override
public void run() {
for (FrameProcessor processor : mFrameProcessors) {
try {
processor.process(frame);
} catch (Exception e) {
LOG.w("Frame processor crashed:", e);
}
}
frame.release();
}
});
In my side I have written :
private val processor = FrameProcessor { frame ->
runBlocking { // used runBlocking to bridge suspend coroutine
frameProcessingTask(frame)
}
}
suspend fun frameProcessingTask(frame: Frame) = withContext(Dispatchers.Default)
//face detection & processing code written here.
}
My question :
is it right to use runBlocking inside Executor as it's called continuously in each frame.
what happened to suspend runBlocking part if Executor canceled the task
what happened if processor.process(frame); called again before frameProcessingTask finished it's task.
is it right to use runBlocking inside Executor as it's called continuously in each frame.
If your code is being called as part of a blocking interface that expects the work to be done when the method returns, you don't have a choice but to block the thread until the work is done. The point of runBlocking is to enable calling suspend functions from this kind of places. FrameProcessor qualifies here.
what happened to suspend runBlocking part if Executor canceled the task
If the task hasn't started yet, cancelling simply removes the task from the queue.
If the task has already completed, cancelling has no effect.
If the task is running, then the thread may be interrupted in an attempt to cancel the task (this depends on the mayInterruptIfRunning param used when cancelling the task).
In that last case, assuming the thread is already executing runBlocking, you can refer to the runBlocking documentation:
If this blocked thread is interrupted (see Thread.interrupt), then the coroutine job is cancelled and this runBlocking invocation throws InterruptedException.
So the answer is that your coroutine will be cancelled (which is probably what you expect).
what happened if processor.process(frame); called again before frameProcessingTask finished it's task
It depends on mFrameProcessingExecutor. If it's multithreaded, then another task can be run in parallel of the previous one.

Synchronize coroutine from incoming messages

I'm using Android Kotlin with the SDK 30 and Coroutine 1.4.1.
I have a function that handles incoming messages to display them on my app in a form of temperature measurement. I use CoroutineScope to process the data and save it in the database. These messages are received from a socket.io connection. The problem is that the messages are not displayed in the correct order when a bulk of data is flowing in.
Now I've looked at my nodejs logs and these messages are sent in the correct order so it can't be that.
I'm using a standard Coroutine function.
See below.
fun receiveTmps(data){
CoroutineScope(IO).launch {
val usersJob = launch {
usersBg(data)
}
}
}
Now I know that with Coroutine I can add a join to wait for the job to finish before starting the next one. But because the messages do not come in at once, but flow continuously over a period of 5 to 20 seconds, it is possible that one message is completed faster than the older one. This causes incorrect order.
My question is, is there any way to handle these tasks 1 by 1 while adding multiple jobs to the list?
Any suggestion or idea is appreciated.
Thank you in advance.
UPDATED:
From what I read from the documentation you should also cancel the channel after its done. So that's going to be tricky to close the channel when messages are flowing in and since I don't have a clear number of what's flowing in I'm having a hard time defining that to the channel. I have tested several ways but most of the examples doesnt work or are outdated.
This is the most basic working example but it always has a defined repeat.
val channel = Channel<String>(UNLIMITED)
fun receiveTmps(data:String){
CoroutineScope(Dispatchers.Default).launch {
channel.send(data)
}
}
#ExperimentalCoroutinesApi
fun main() = runBlocking<Unit> {
launch {
// while(!channel.isClosedForReceive){
// val x = channel.receive()
// Log.d("deb", "Temperature.. "+ x)
// }
repeat(3) {
val x = channel.receive()
Log.d("deb", "Temperature.. "+ x)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
receiveTmps("10")
receiveTmps("30")
// Many more...
main()
}
If we need to process some events sequentially then typical solution is to create a queue of events and start a single consumer to process them. In the case of coroutines we can use Channel as a queue and launch a coroutine running in a loop that will be our consumer.
I'm not very familiar with Android, so I may miss something, but it should be something along lines:
fun receiveTmps(data:String){
channel.trySend(data).getOrThrow()
}
fun main() {
lifecycleScope.launch(Dispatchers.Default) {
for (tmp in channel) {
...
}
}
}
My assumption is that you want to stop processing events when the activity/service will be destroyed, ignoring all temps that are still waiting in the queue.

How do I cancel a kotlin coroutine for a blocking download operation

Here is the piece of code I have running on Dispatcher.IO thread pool
suspend fun download(rquest: YoutubleDLRequest){
withContext(Dispatchers.IO){
YoutubeDL.init().execute(request) // downloads media in background
}
}
Now, when user clicks a button, the download starts - it either fails due to network error, or completes.
My question is how do I cancel this download operation?
I have another button to allow the user to cancel the download operation.
If I wrap withContext in a launch and keep a reference to the job and then try to
cancel the job on the button click, it will not work. I know, I need another suspension point, I tried calling another suspend function with yield inside a while loop. But, the while loop does not let the download code execute at all.
I don't understand, how cancel would be implemented in this scenario. Any help would be appreciated.
This answer was written before the original poster changed his question, and is therefore not on point anymore. Sorry.
There are multiple options you could consider, depending on your usecase:
Let's assume your YoutubeDownloader throws a DownloadException when it is canceled:
suspend fun download(rquest: YoutubleDLRequest){
withContext(Dispatchers.IO){
try{
YoutubeDL.init().execute(request) // downloads media in background
Log.d("myTag", "Success"
} catch ( e: DownloadException){
Log.e("myTag", "Download-Failure: $e")
}
}
}
In case you'd like to actively cancel the coroutine, you could save it as a variable, like here in the basic-docs, with more examples here in the cancellation docs:
val myDownloadJob = withContext(Dispatchers.IO){ /* your code here */}
val myDownloadJob = someScope.launch(someDispatcher){ /* your code here */ }
// somewhere else:
myDownloadJob.cancel()

Coroutines : Runblocking freeze Android UI

I'm developing an android app in Kotlin.
After validation of an entry form, I launch 3 jobs to do 3 http calls in the same time :
val jobA = CoroutineScope(IO).launch {
// saves some data in my database
}
val jobB = CoroutineScope(IO).launch {
// saves an image in my aws bucket
}
val jobC = CoroutineScope(IO).launch {
// if exists, deletes the old image in my aws bucket
}
Before launching theses 3 jobs, I start a loader animation
(I'm using this library : https://github.com/81813780/AVLoadingIndicatorView)
I need to wait completion of my 3 jobs without freezing UI (or at least the animation).
I tried with runBlocking but it freezes the UI...
runBlocking() {
jobA!!.join()
jobB!!.join()
jobC!!.join()
}
How can I wait for my 3 jobs without freezing UI ?
Thanks in advance,
Sorry for my english,
Thomas
You can try to launch another coroutine using Main CorotineContext and wait for jobs there:
CoroutineScope(Main).launch {
jobA!!.join()
jobB!!.join()
jobC!!.join()
}

Kotlin - Test inner Coroutines with delay using runBlockingTest

I have this piece of code :
fun setBlocked(blocked: Boolean) = viewModelScope.launchSafe(::handleExceptionsActive) {
/**
* Show loading after 500 ms to prevent from showing it if cloud functions are hot.
*/
val loadingJob = launch { delay(500L); _loading.value = true }
participantDataSource.setBlockedUser(contact.userId, blocked)
loadingJob.cancelAndJoin()
_loading.value = false
}
It does a Firebase Cloud Function call that might take a few seconds or just a few milliseconds depending on whether the Cloud Function is idle or not. What this code does is it triggers a loading LiveData at true after 500ms and then puts it back at false.
If the call takes a few milliseconds, then loading.value = true will never be called.
If the call takes a few seconds, then loading.value = true will be called.
I want to test the function setBlocked().
The problem with this code is that I don't know how to test these two statements. I know I can test code using delay() with runBlockingTest and advanceTimeBy() but the thing is loadingJob.cancel() will be called before I call advanceTimeBy().
How can I test it ? If you have suggestions on modifying the code to make it testable I'll be glad to here it too.
Edit : replaced cancel() by cancelAndJoin() as Marko Topolnik suggests.

Categories

Resources