Using Future blocks UI in application

107 views Asked by At

In my Android app I want to use java.util.concurrent tools to make operations that return value (for educational purposes). I read that CompletableFuture can be used for this. I made a project where I simulate an operation that takes some time to execute:

class MyRepository {

    private var counter = 0

    // Here we make a blocking call
    fun makeRequest(): Int {
        Thread.sleep(2000L)
        counter += 1
        return counter
    }
}

Then I call this function from UI thread:

fun getNumber(): Int {
    val completableFuture = CompletableFuture<Int>()
    Executors.newCachedThreadPool().submit<Any?> {
        val result = myRepository.makeRequest()
        completableFuture.complete(result)
        null
    }
    return completableFuture.get()
}

The problem is that UI is blocked while this operation is being executed. How can I use CompletableFuture to make operation like this without blocking UI of application?

I want to use only java.util.concurrent tools for this.

2

There are 2 answers

1
Tenfour04 On BEST ANSWER

To do asynchronous work using exclusively java.util.concurrent, you should use an ExecutorService. And to return the value from your function you need to use a callback. And if you're working with UI, you need to provide a way to cancel work so callbacks won't try to work with your UI after its gone.

I think conventional behavior is to fire the callback on the Looper of the thread that called this function, or on the main looper as a fallback if it was called from a non-Looper thread.

private val executor = Executors.newFixedThreadPool(10)

// Call in onDestroy of UI
fun cancel() {
    executor.shutdownNow()
}

fun getNumberThread(callback: (Int)->Unit) {
    val looper = Looper.myLooper ?: Looper.mainLooper
    val handler = Handler(looper)
    val myCallable = MyCallable(myRepository)
    executor.execute {
        try {
            val result = myCallable.call()
            if (Thread.currentThread().isInterrupted()) return
            handler.post { callback(result) }
        } catch {e: MyException) {
            if (Thread.currentThread().isInterrupted()) return
            handler.post { callback(-1) } // if that's how you want to return error results
        }
    }
}
0
Steve M On

You could use a ListenableFuture, that way you can attach a completion listener instead of using a blocking get() call. https://developer.android.com/guide/background/asynchronous/listenablefuture

If you must only use java.util.concurrent tools, I suppose you could poll isDone() of the FutureTask using a Handler or similar.