Task API

Task API adalah cara standar untuk menangani operasi asinkron di layanan Google Play. Cara ini memberikan cara yang efektif dan fleksibel untuk mengelola panggilan asinkron, menggantikan pola PendingResult yang lebih lama. Dengan Task, Anda dapat menggabungkan beberapa panggilan, menangani alur yang kompleks, dan menulis handler keberhasilan dan kegagalan yang jelas.

Menangani hasil tugas

Banyak API di layanan Google Play dan Firebase menampilkan objek Task untuk merepresentasikan operasi asinkron. Misalnya, FirebaseAuth.signInAnonymously() menampilkan Task<AuthResult> yang merepresentasikan hasil operasi login. Task<AuthResult> menunjukkan bahwa saat tugas selesai dengan berhasil, tugas akan menampilkan objek AuthResult.

Anda dapat menangani hasil Task dengan melampirkan pemroses yang merespons penyelesaian yang berhasil, kegagalan, atau keduanya:

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

Untuk menangani penyelesaian tugas yang berhasil, lampirkan OnSuccessListener:

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

Untuk menangani tugas yang gagal, lampirkan OnFailureListener:

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

Untuk menangani keberhasilan dan kegagalan dalam pemroses yang sama, lampirkan 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();
        }
    }
});

Mengelola rangkaian pesan

Secara default, pemroses yang terpasang ke Task dijalankan di thread utama (UI) aplikasi. Artinya, Anda harus menghindari melakukan operasi yang berjalan lama di listener. Jika perlu melakukan operasi yang berjalan lama, Anda dapat menentukan Executor yang digunakan untuk menjadwalkan pemroses pada thread latar belakang.

// 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) {
        // ...
    }
});

Menggunakan pemroses cakupan aktivitas

Saat Anda perlu menangani hasil tugas dalam Activity, penting untuk mengelola siklus proses pendengar agar tidak dipanggil saat Activity tidak lagi terlihat. Untuk melakukannya, Anda dapat menggunakan pemroses yang tercakup dalam aktivitas. Listener ini akan otomatis dihapus saat metode onStop dari Activity Anda dipanggil, sehingga tidak akan dieksekusi setelah Activity dihentikan.

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

Menautkan tugas

Jika Anda menggunakan sekumpulan API yang menampilkan objek Task dalam fungsi yang kompleks, Anda dapat merantainya menggunakan kelanjutan. Hal ini membantu Anda menghindari callback yang bertingkat dalam dan menggabungkan penanganan error untuk beberapa tugas yang dirangkai.

Misalnya, pertimbangkan skenario saat Anda memiliki metode doSomething yang menampilkan Task<String>, tetapi memerlukan AuthResult sebagai parameter. Anda dapat memperoleh AuthResult ini secara asinkron dari Task lain:

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

Dengan metode Task.continueWithTask, Anda dapat merangkai kedua tugas ini:

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.
        // ...
    }
});

Memblokir tugas

Jika program Anda sudah dieksekusi di thread latar belakang, Anda dapat memblokir thread saat ini dan menunggu hingga tugas selesai, alih-alih menggunakan callback:

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.
    // ...
}

Anda juga dapat menentukan waktu tunggu saat memblokir tugas untuk mencegah aplikasi macet tanpa batas jika tugas memerlukan waktu terlalu lama untuk diselesaikan:

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.
    // ...
}

Interoperabilitas

Task dirancang agar berfungsi baik dengan pola pemrograman asinkron Android umum lainnya. Objek ini dapat dikonversi ke dan dari primitif lain seperti ListenableFuture dan coroutine Kotlin, yang direkomendasikan oleh AndroidX, sehingga Anda dapat menggunakan pendekatan yang paling sesuai dengan kebutuhan Anda.

Berikut adalah contoh yang menggunakan Task:

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

Coroutine Kotlin

Untuk menggunakan coroutine Kotlin dengan Task, tambahkan dependensi berikut ke project Anda, lalu gunakan cuplikan kode untuk mengonversi dari Task.

Gradle (level modul build.gradle, biasanya 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'
Cuplikan
import kotlinx.coroutines.tasks.await
// ...
  textView.text = simpleTask.await()
}

Jambu Biji ListenableFuture

Untuk menggunakan Guava ListenableFuture dengan Task, tambahkan dependensi berikut ke project Anda, lalu gunakan cuplikan kode untuk mengonversi dari Task.

Gradle (level modul build.gradle, biasanya app/build.gradle)
implementation "androidx.concurrent:concurrent-futures:1.2.0"
Cuplikan
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

Selain library async relatif pilihan, tambahkan dependensi berikut ke project Anda, lalu gunakan cuplikan kode untuk mengonversi dari Task.

Gradle (level modul build.gradle, biasanya app/build.gradle)
// Source: https://github.com/ashdavies/rx-tasks
implementation 'io.ashdavies.rx.rxtasks:rx-tasks:2.2.0'
Cuplikan
import io.ashdavies.rx.rxtasks.toSingle
import java.util.concurrent.TimeUnit
// ...
simpleTask.toSingle(this).subscribe { result -> textView.text = result }

Langkah berikutnya