Como usar um ListenableFuture

Um ListenableFuture representa o resultado de uma computação assíncrona, ou seja, uma computação que pode ou não ter concluído a produção de um resultado. É um tipo de Future que permite registrar callbacks que serão executados quando o cálculo for concluído ou se o cálculo já estiver concluído, imediatamente.

O ListenableFuture não faz parte do framework do Android e é fornecido pela Guava (link em inglês). Para mais informações sobre a implementação dessa classe, consulte ListenableFuture explicado.

Muitas bibliotecas atuais do Jetpack, como CameraX ou Health Services, têm métodos assíncronos em que o tipo de retorno é um ListenableFuture que representa o status da execução. Em alguns casos, pode ser necessário implementar um método que retorne uma ListenableFuture, por exemplo, para atender aos requisitos de TileService.

Bibliotecas necessárias

Groovy

dependencies {
    implementation "com.google.guava:guava:31.0.1-android"

    // To use CallbackToFutureAdapter
    implementation "androidx.concurrent:concurrent-futures:1.1.0"

    // Kotlin
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.0"
}

Kotlin

dependencies {
    implementation("com.google.guava:guava:31.0.1-android")

    // To use CallbackToFutureAdapter
    implementation("androidx.concurrent:concurrent-futures:1.1.0")

    // Kotlin
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.0")
}

Como acessar o resultado de uma ListenableFuture

Como adicionar um callback

Use o método auxiliar Futures.addCallback(...) para anexar callbacks de sucesso e falha a um ListenableFuture.

Kotlin

val future: ListenableFuture<QueryResult> = ...
Futures.addCallback(
    future,
    object : FutureCallback<QueryResult> {
        override fun onSuccess(result: QueryResult) {
            // handle success
        }

        override fun onFailure(t: Throwable) {
            // handle failure
        }
    },
    // causes the callbacks to be executed on the main (UI) thread
    context.mainExecutor
)

Java

ListenableFuture<QueryResult> future = ...
Futures.addCallback(
    future,
    new FutureCallback<QueryResult>() {
        public void onSuccess(QueryResult result) {
            // handle success
        }

        public void onFailure(@NonNull Throwable thrown) {
            // handle failure
        }
    },
    // causes the callbacks to be executed on the main (UI) thread
    context.getMainExecutor()
);

Como suspender em Kotlin

Ao usar o Kotlin, a maneira mais fácil de aguardar o resultado de um ListenableFuture é usar await().

import kotlinx.coroutines.guava.await

...

val future: ListenableFuture<QueryResult> = ...
val queryResult = future.await() // suspends awaiting success

Interoperabilidade com RxJava

Um Single do RxJava pode ser criado com um ListenableFuture registrando callbacks em um SingleEmitter.

Kotlin

val future: ListenableFuture<QueryResult> = ...
val single = Single.create<QueryResult> {
    Futures.addCallback(future, object : FutureCallback<QueryResult> {
        override fun onSuccess(result: QueryResult) {
            it.onSuccess(result)
        }

        override fun onFailure(t: Throwable) {
            it.onError(t)
        }
    }, executor)
}

Java

ListenableFuture<QueryResult> future = ...
Single<QueryResult> single = Single.create(
        e -> Futures.addCallback(future, new FutureCallback<QueryResult>() {
            @Override
            public void onSuccess(QueryResult result) {
                e.onSuccess(result);
            }

            @Override
            public void onFailure(@NonNull Throwable thrown) {
                e.onError(thrown);
            }
        }, executor));

Como criar um ListenableFuture

Criação de um futuro imediato

Se a API não for assíncrona, mas você precisar unir o resultado de uma operação concluída em um ListenableFuture, crie um ImmediateFuture. Isso pode ser feito usando o método de fábrica Futures.immediateFuture(...).

Kotlin

fun getResult(): ListenableFuture<QueryResult> {
    try {
        val queryResult = getQueryResult()
        return Futures.immediateFuture(queryResult)
    } catch (e: Exception) {
        return Futures.immediateFailedFuture(e)
    }
}

Java

public ListenableFuture<QueryResult> getResult() {
    try {
        QueryResult queryResult = getQueryResult();
        return Futures.immediateFuture(queryResult);
    } catch (Exception e) {
        return Futures.immediateFailedFuture(e);
    }
}

Como usar uma corrotina

No Kotlin, uma future{ ... } pode ser usada para converter o resultado de uma função de suspensão em um ListenableFuture.

import kotlinx.coroutines.guava.future

suspend fun getResultAsync(): QueryResult { ... }

fun getResultFuture(): ListenableFuture<QueryResult> {
    return coroutineScope.future{
        getResultAsync()
    }
}

Como converter um callback

Para converter uma API baseada em callback em uma que use ListenableFuture, use CallbackToFutureAdapter. Essa API é fornecida pelo artefato androidx.concurrent:concurrent-futures.

Consulte androidx.concurrent para mais informações.

Como converter do RxJava Single

Ao usar o RxJava, um Single pode ser convertido em um SettableFuture, que implementa ListenableFuture.

Kotlin

fun getResult(): ListenableFuture<QueryResult> {
    val single: Single<QueryResult> = ...

    val future = SettableFuture.create<QueryResult>()
    single.subscribe(future::set, future::setException)
    return future
}

Java

public ListenableFuture<QueryResult> getResult() {
    Single<QueryResult> single = ...

    SettableFuture<QueryResult> future = SettableFuture.create();
    single.subscribe(future::set, future::setException);
    return future;
}