Las APIs de Task

La API de Task es la forma estándar de controlar las operaciones asíncronas en los Servicios de Google Play. Proporciona una forma potente y flexible de administrar llamadas asíncronas, lo que reemplaza el patrón PendingResult anterior. Con Task, puedes encadenar varias llamadas, controlar flujos complejos y escribir controladores de éxito y falla claros.

Controla los resultados de las tareas

Muchas APIs de los Servicios de Google Play y Firebase muestran un objeto Task para representar operaciones asíncronas. Por ejemplo, FirebaseAuth.signInAnonymously() muestra un Task<AuthResult> que representa el resultado de la operación de acceso. El Task<AuthResult> indica que, cuando la tarea se complete correctamente, mostrará un objeto AuthResult.

Para controlar el resultado de una Task, adjunta objetos de escucha que respondan a la finalización correcta, la falla o ambas:

Task<AuthResult> task = FirebaseAuth.getInstance().signInAnonymously();

Para controlar la finalización correcta de una tarea, adjunta un OnSuccessListener:

task.addOnSuccessListener(new OnSuccessListener<AuthResult>() {
    @Override
    public void onSuccess(AuthResult authResult) {
        // Task completed successfully
        // ...
    }
});

Para controlar una tarea fallida, adjunta un OnFailureListener:

task.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception e) {
        // Task failed with an exception
        // ...
    }
});

Para controlar el éxito y la falla en el mismo objeto de escucha, adjunta un OnCompleteListener:

task.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
    @Override
    public void onComplete(@NonNull Task<AuthResult> task) {
        if (task.isSuccessful()) {
            // Task completed successfully
            AuthResult result = task.getResult();
        } else {
            // Task failed with an exception
            Exception exception = task.getException();
        }
    }
});

Administra subprocesos

De forma predeterminada, los objetos de escucha adjuntos a una Task se ejecutan en el subproceso principal (IU) de la aplicación. Esto significa que debes evitar realizar operaciones de larga duración en los objetos de escucha. Si necesitas realizar una operación de larga duración, puedes especificar un Executor que se use para programar objetos de escucha en un subproceso en segundo plano.

// Create a new ThreadPoolExecutor with 2 threads for each processor on the
// device and a 60 second keep-alive time.
int numCores = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor executor = new ThreadPoolExecutor(numCores * 2, numCores *2,
        60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

task.addOnCompleteListener(executor, new OnCompleteListener<AuthResult>() {
    @Override
    public void onComplete(@NonNull Task<AuthResult> task) {
        // ...
    }
});

Usa objetos de escucha con alcance de actividad

Cuando necesites controlar los resultados de las tareas dentro de una Activity, es importante administrar el ciclo de vida de los objetos de escucha para evitar que se llamen cuando la Activity ya no esté visible. Para ello, puedes usar objetos de escucha con alcance de actividad. Estos objetos de escucha se quitan automáticamente cuando se llama al método onStop de tu Activity, de modo que no se ejecuten después de que se detenga la Activity.

Activity activity = MainActivity.this;
task.addOnCompleteListener(activity, new OnCompleteListener<AuthResult>() {
    @Override
    public void onComplete(@NonNull Task<AuthResult> task) {
        // ...
    }
});

Encadena tareas

Si usas un conjunto de APIs que muestran objetos Task en una función compleja, puedes encadenarlos con continuaciones. Esto te ayuda a evitar devoluciones de llamada anidadas y consolida el control de errores para varias tareas encadenadas.

Por ejemplo, considera una situación en la que tienes un método doSomething que muestra un Task<String>, pero requiere un AuthResult como parámetro. Puedes obtener este AuthResult de forma asíncrona desde otra Task:

public Task<String> doSomething(AuthResult authResult) {
    // ...
}

Con el método Task.continueWithTask, puedes encadenar estas dos tareas:

Task<AuthResult> signInTask = FirebaseAuth.getInstance().signInAnonymously();

signInTask.continueWithTask(new Continuation<AuthResult, Task<String>>() {
    @Override
    public Task<String> then(@NonNull Task<AuthResult> task) throws Exception {
        // Take the result from the first task and start the second one
        AuthResult result = task.getResult();
        return doSomething(result);
    }
}).addOnSuccessListener(new OnSuccessListener<String>() {
    @Override
    public void onSuccess(String s) {
        // Chain of tasks completed successfully, got result from last task.
        // ...
    }
}).addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception e) {
        // One of the tasks in the chain failed with an exception.
        // ...
    }
});

Bloquea una tarea

Si tu programa ya se está ejecutando en un subproceso en segundo plano, puedes bloquear el subproceso actual y esperar a que se complete la tarea, en lugar de usar una devolución de llamada:

try {
    // Block on a task and get the result synchronously. This is generally done
    // when executing a task inside a separately managed background thread. Doing this
    // on the main (UI) thread can cause your application to become unresponsive.
    AuthResult authResult = Tasks.await(task);
} catch (ExecutionException e) {
    // The Task failed, this is the same exception you'd get in a non-blocking
    // failure handler.
    // ...
} catch (InterruptedException e) {
    // An interrupt occurred while waiting for the task to complete.
    // ...
}

También puedes especificar un tiempo de espera cuando bloqueas una tarea para evitar que tu aplicación se bloquee de forma indefinida si la tarea tarda demasiado en completarse:

try {
    // Block on the task for a maximum of 500 milliseconds, otherwise time out.
    AuthResult authResult = Tasks.await(task, 500, TimeUnit.MILLISECONDS);
} catch (ExecutionException e) {
    // ...
} catch (InterruptedException e) {
    // ...
} catch (TimeoutException e) {
    // Task timed out before it could complete.
    // ...
}

Interoperabilidad

Task está diseñado para funcionar bien con otros patrones comunes de programación asíncrona de Android. Se puede convertir a y desde otras primitivas, como ListenableFuture y corrutinas de Kotlin, que recomienda AndroidX, lo que te permite usar el enfoque que mejor se adapte a tus necesidades.

Este es un ejemplo en el que se usa una Task:

// ...
simpleTask.addOnCompleteListener(this) {
  completedTask -> textView.text = completedTask.result
}

Corrutiina de Kotlin

Para usar corrutinas de Kotlin con Task, agrega la siguiente dependencia a tu proyecto y, luego, usa el fragmento de código para convertir desde una Task.

Gradle (nivel del módulo build.gradle, por lo general, app/build.gradle)
// Source: https://github.com/Kotlin/kotlinx.coroutines/tree/master/integration/kotlinx-coroutines-play-services
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3'
Fragmento
import kotlinx.coroutines.tasks.await
// ...
  textView.text = simpleTask.await()
}

ListenableFuture de Guava

Para usar ListenableFuture de Guava con Task, agrega la siguiente dependencia a tu proyecto y, luego, usa el fragmento de código para convertir desde una Task.

Gradle (nivel del módulo build.gradle, por lo general, app/build.gradle)
implementation "androidx.concurrent:concurrent-futures:1.2.0"
Fragmento
import com.google.common.util.concurrent.ListenableFuture
// ...
/** Convert Task to ListenableFuture. */
fun <T> taskToListenableFuture(task: Task<T>): ListenableFuture<T> {
  return CallbackToFutureAdapter.getFuture { completer ->
    task.addOnCompleteListener { completedTask ->
      if (completedTask.isCanceled) {
        completer.setCancelled()
      } else if (completedTask.isSuccessful) {
        completer.set(completedTask.result)
      } else {
        val e = completedTask.exception
        if (e != null) {
          completer.setException(e)
        } else {
          throw IllegalStateException()
        }
      }
    }
  }
}
// ...
this.listenableFuture = taskToListenableFuture(simpleTask)
this.listenableFuture?.addListener(
  Runnable {
    textView.text = listenableFuture?.get()
  },
  ContextCompat.getMainExecutor(this)
)

RxJava2 Observable

Agrega la siguiente dependencia, además de la biblioteca asíncrona relativa que elijas, a tu proyecto y, luego, usa el fragmento de código para convertir desde una Task.

Gradle (nivel del módulo build.gradle, por lo general, app/build.gradle)
// Source: https://github.com/ashdavies/rx-tasks
implementation 'io.ashdavies.rx.rxtasks:rx-tasks:2.2.0'
Fragmento
import io.ashdavies.rx.rxtasks.toSingle
import java.util.concurrent.TimeUnit
// ...
simpleTask.toSingle(this).subscribe { result -> textView.text = result }

Próximos pasos