Memulai aktivitas lain, baik dalam aplikasi Anda maupun dari aplikasi lain, tidak harus berupa operasi satu arah. Anda juga dapat memulai aktivitas dan menerima hasilnya kembali. Misalnya, aplikasi Anda dapat memulai aplikasi kamera dan menerima foto yang diambil sebagai hasilnya, atau, Anda dapat memulai Aplikasi kontak agar pengguna dapat memilih kontak, lalu menerima detail kontak sebagai hasilnya.
Meskipun
startActivityForResult()
dan
onActivityResult()
API dasar tersedia di class Activity
, di semua API level, Google sangat
menyarankan penggunaan Activity Result API yang diperkenalkan di class
Activity
dan Fragment
AndroidX.
Activity Result API menyediakan komponen untuk mendaftar hasil, meluncurkan aktivitas yang memberikan hasil, dan menangani hasilnya setelah aktivitas dikirimkan oleh sistem.
Mendaftarkan callback untuk hasil aktivitas
Saat memulai aktivitas untuk suatu hasil, terdapat kemungkinan, dan hampir pasti dalam operasi yang memerlukan banyak memori seperti penggunaan kamera, bahwa proses dan aktivitas Anda akan dihapus karena memori terlalu kecil.
Oleh karena itu, Activity Result API memisahkan callback hasil dari tempat di kode tempat Anda meluncurkan aktivitas lainnya. Karena callback hasil harus tersedia saat proses dan aktivitas Anda dibuat kembali, callback harus terdaftar tanpa syarat setiap kali aktivitas Anda dibuat meskipun logika peluncuran aktivitas lainnya hanya terjadi berdasarkan input pengguna atau logika bisnis lainnya.
Saat dalam
ComponentActivity
atau
Fragment
, Activity Result
API menyediakan
registerForActivityResult()
API untuk mendaftarkan callback hasil. registerForActivityResult()
mengambil
ActivityResultContract
dan
ActivityResultCallback
,
lalu menampilkan
ActivityResultLauncher
yang akan Anda gunakan untuk meluncurkan aktivitas lainnya.
ActivityResultContract
mendefinisikan jenis input yang diperlukan untuk membuat hasil
bersama dengan jenis output hasil. API memberikan
kontrak default untuk tindakan intent dasar seperti mengambil gambar, meminta izin, dan
sebagainya. Anda juga dapat
membuat kontrak kustom.
ActivityResultCallback
adalah antarmuka satu metode dengan
metode onActivityResult()
yang mengambil objek dari jenis output yang ditentukan dalam
ActivityResultContract
:
Kotlin
val getContent = registerForActivityResult(GetContent()) { uri: Uri? -> // Handle the returned Uri }
Java
// GetContent creates an ActivityResultLauncher<String> to let you pass // in the mime type you want to let the user select ActivityResultLauncher<String> mGetContent = registerForActivityResult(new GetContent(), new ActivityResultCallback<Uri>() { @Override public void onActivityResult(Uri uri) { // Handle the returned Uri } });
Jika memiliki beberapa panggilan hasil aktivitas dan menggunakan kontrak
berbeda
atau menginginkan callback terpisah, Anda dapat memanggil registerForActivityResult()
beberapa
kali untuk mendaftarkan beberapa instance ActivityResultLauncher
. Anda harus
memanggil registerForActivityResult()
dalam urutan yang sama pada setiap pembuatan
fragmen atau aktivitas Anda sehingga hasil yang dikirimkan mengarah ke
callback yang benar.
registerForActivityResult()
aman untuk dipanggil sebelum fragmen atau aktivitas Anda
dibuat sehingga dapat digunakan secara langsung saat mendeklarasikan variabel anggota
untuk instance ActivityResultLauncher
yang ditampilkan.
Meluncurkan aktivitas untuk hasil
Meskipun mendaftarkan callback Anda, registerForActivityResult()
tidak akan
meluncurkan aktivitas lainnya dan memulai permintaan untuk hasil. Sebaliknya, ini
adalah tanggung jawab instance ActivityResultLauncher
yang ditampilkan.
Jika ada, input yang sesuai dengan jenis ActivityResultContract
akan diambil oleh
peluncur. Memanggil
launch()
akan memulai proses pembuatan hasil. Setelah pengguna selesai dengan
aktivitas berikutnya dan kembali, onActivityResult()
dari
ActivityResultCallback
kemudian dijalankan, seperti yang ditunjukkan dalam contoh berikut:
Kotlin
val getContent = registerForActivityResult(GetContent()) { uri: Uri? -> // Handle the returned Uri } override fun onCreate(savedInstanceState: Bundle?) { // ... val selectButton = findViewById<Button>(R.id.select_button) selectButton.setOnClickListener { // Pass in the mime type you want to let the user select // as the input getContent.launch("image/*") } }
Java
ActivityResultLauncher<String> mGetContent = registerForActivityResult(new GetContent(), new ActivityResultCallback<Uri>() { @Override public void onActivityResult(Uri uri) { // Handle the returned Uri } }); @Override public void onCreate(@Nullable Bundle savedInstanceState) { // ... Button selectButton = findViewById(R.id.select_button); selectButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { // Pass in the mime type you want to let the user select // as the input mGetContent.launch("image/*"); } }); }
Versi kelebihan muatan
launch()
memungkinkan Anda meneruskan
ActivityOptionsCompat
selain input.
Menerima hasil aktivitas di class terpisah
Meskipun class ComponentActivity
dan Fragment
mengimplementasikan antarmuka
ActivityResultCaller
agar Anda dapat menggunakan registerForActivityResult()
API, Anda juga dapat
menerima hasil aktivitas di class terpisah yang tidak mengimplementasikan
ActivityResultCaller
menggunakan
ActivityResultRegistry
secara langsung.
Misalnya, Anda mungkin ingin mengimplementasikan
LifecycleObserver
yang menangani pendaftaran kontrak bersama dengan meluncurkan peluncur:
Kotlin
class MyLifecycleObserver(private val registry : ActivityResultRegistry) : DefaultLifecycleObserver { lateinit var getContent : ActivityResultLauncher<String> override fun onCreate(owner: LifecycleOwner) { getContent = registry.register("key", owner, GetContent()) { uri -> // Handle the returned Uri } } fun selectImage() { getContent.launch("image/*") } } class MyFragment : Fragment() { lateinit var observer : MyLifecycleObserver override fun onCreate(savedInstanceState: Bundle?) { // ... observer = MyLifecycleObserver(requireActivity().activityResultRegistry) lifecycle.addObserver(observer) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val selectButton = view.findViewById<Button>(R.id.select_button) selectButton.setOnClickListener { // Open the activity to select an image observer.selectImage() } } }
Java
class MyLifecycleObserver implements DefaultLifecycleObserver { private final ActivityResultRegistry mRegistry; private ActivityResultLauncher<String> mGetContent; MyLifecycleObserver(@NonNull ActivityResultRegistry registry) { mRegistry = registry; } public void onCreate(@NonNull LifecycleOwner owner) { // ... mGetContent = mRegistry.register(“key”, owner, new GetContent(), new ActivityResultCallback<Uri>() { @Override public void onActivityResult(Uri uri) { // Handle the returned Uri } }); } public void selectImage() { // Open the activity to select an image mGetContent.launch("image/*"); } } class MyFragment extends Fragment { private MyLifecycleObserver mObserver; @Override void onCreate(Bundle savedInstanceState) { // ... mObserver = new MyLifecycleObserver(requireActivity().getActivityResultRegistry()); getLifecycle().addObserver(mObserver); } @Override void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { Button selectButton = findViewById(R.id.select_button); selectButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { mObserver.selectImage(); } }); } }
Saat menggunakan ActivityResultRegistry
API, Google sangat merekomendasikan penggunaan
API yang mengambil LifecycleOwner
karena LifecycleOwner
akan otomatis
menghapus peluncur terdaftar Anda saat Lifecycle
dihancurkan. Namun,
jika LifecycleOwner
tidak tersedia, setiap
class ActivityResultLauncher
memungkinkan Anda memanggil
unregister()
secara manual sebagai alternatif.
Pengujian
Secara default, registerForActivityResult()
akan otomatis menggunakan
ActivityResultRegistry
yang diberikan oleh aktivitas. Tindakan ini juga mengakibatkan overload sehingga memungkinkan Anda meneruskan
instance ActivityResultRegistry
Anda sendiri yang dapat digunakan untuk menguji
panggilan hasil aktivitas tanpa benar-benar meluncurkan aktivitas lain.
Saat menguji fragmen aplikasi Anda, Anda
dapat menyediakan ActivityResultRegistry
pengujian menggunakan
FragmentFactory
untuk meneruskan
ActivityResultRegistry
ke konstruktor fragmen.
Misalnya, fragmen yang menggunakan kontrak TakePicturePreview
untuk mendapatkan
thumbnail
gambar mungkin ditulis seperti berikut:
Kotlin
class MyFragment( private val registry: ActivityResultRegistry ) : Fragment() { val thumbnailLiveData = MutableLiveData<Bitmap?> val takePicture = registerForActivityResult(TakePicturePreview(), registry) { bitmap: Bitmap? -> thumbnailLiveData.setValue(bitmap) } // ... }
Java
public class MyFragment extends Fragment { private final ActivityResultRegistry mRegistry; private final MutableLiveData<Bitmap> mThumbnailLiveData = new MutableLiveData(); private final ActivityResultLauncher<Void> mTakePicture = registerForActivityResult(new TakePicturePreview(), mRegistry, new ActivityResultCallback<Bitmap>() { @Override public void onActivityResult(Bitmap thumbnail) { mThumbnailLiveData.setValue(thumbnail); } }); public MyFragment(@NonNull ActivityResultRegistry registry) { super(); mRegistry = registry; } @VisibleForTesting @NonNull ActivityResultLauncher<Void> getTakePicture() { return mTakePicture; } @VisibleForTesting @NonNull LiveData<Bitmap> getThumbnailLiveData() { return mThumbnailLiveData; } // ... }
Saat membuat ActivityResultRegistry
khusus pengujian, Anda harus
mengimplementasikan
metode
onLaunch()
. Daripada memanggil startActivityForResult()
, implementasi
pengujian Anda dapat memanggil
dispatchResult()
secara langsung sehingga dapat memberikan hasil seperti yang ingin Anda gunakan dalam pengujian:
val testRegistry = object : ActivityResultRegistry() {
override fun <I, O> onLaunch(
requestCode: Int,
contract: ActivityResultContract<I, O>,
input: I,
options: ActivityOptionsCompat?
) {
dispatchResult(requestCode, expectedResult)
}
}
Pengujian lengkap akan membuat hasil yang diharapkan, membuat ActivityResultRegistry
pengujian, meneruskannya ke fragmen, memicu peluncur
secara langsung atau menggunakan API pengujian lain seperti Espresso, lalu memverifikasi
hasilnya:
@Test
fun activityResultTest {
// Create an expected result Bitmap
val expectedResult = Bitmap.createBitmap(1, 1, Bitmap.Config.RGBA_F16)
// Create the test ActivityResultRegistry
val testRegistry = object : ActivityResultRegistry() {
override fun <I, O> onLaunch(
requestCode: Int,
contract: ActivityResultContract<I, O>,
input: I,
options: ActivityOptionsCompat?
) {
dispatchResult(requestCode, expectedResult)
}
}
// Use the launchFragmentInContainer method that takes a
// lambda to construct the Fragment with the testRegistry
with(launchFragmentInContainer { MyFragment(testRegistry) }) {
onFragment { fragment ->
// Trigger the ActivityResultLauncher
fragment.takePicture()
// Verify the result is set
assertThat(fragment.thumbnailLiveData.value)
.isSameInstanceAs(expectedResult)
}
}
}
Membuat kontrak kustom
Meskipun ActivityResultContracts
berisi sejumlah class ActivityResultContract
bawaan untuk digunakan, Anda dapat
memberikan kontrak Anda sendiri yang menyediakan jenis API aman yang diperlukan.
Setiap ActivityResultContract
memerlukan class input dan output yang ditentukan
menggunakan Void
sebagai jenis input jika Anda
tidak memerlukan input apa pun (di Kotlin, gunakan Void?
atau Unit
).
Setiap kontrak harus mengimplementasikan metode
createIntent()
yang mengambil Context
dan input, serta menyusun Intent
yang
akan digunakan
dengan startActivityForResult()
.
Setiap kontrak juga harus mengimplementasikan
parseResult()
,
yang menghasilkan output dari resultCode
tertentu, seperti
Activity.RESULT_OK
atau Activity.RESULT_CANCELED
, dan Intent
.
Secara opsional, kontrak dapat mengimplementasikan
getSynchronousResult()
jika memungkinkan untuk menentukan hasil input yang diberikan tanpa perlu
memanggil createIntent()
, memulai aktivitas lainnya, dan menggunakan
parseResult()
untuk membuat hasil.
Contoh berikut menunjukkan cara membuat ActivityResultContract
:
Kotlin
class PickRingtone : ActivityResultContract<Int, Uri?>() { override fun createIntent(context: Context, ringtoneType: Int) = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply { putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType) } override fun parseResult(resultCode: Int, result: Intent?) : Uri? { if (resultCode != Activity.RESULT_OK) { return null } return result?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) } }
Java
public class PickRingtone extends ActivityResultContract<Integer, Uri> { @NonNull @Override public Intent createIntent(@NonNull Context context, @NonNull Integer ringtoneType) { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType.intValue()); return intent; } @Override public Uri parseResult(int resultCode, @Nullable Intent result) { if (resultCode != Activity.RESULT_OK || result == null) { return null; } return result.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); } }
Jika tidak memerlukan kontrak kustom, Anda dapat menggunakan
kontrak
StartActivityForResult
. Ini adalah kontrak umum yang menggunakan Intent
sebagai input
dan menampilkan
ActivityResult
sehingga Anda dapat mengekstrak resultCode
dan Intent
sebagai bagian dari callback,
seperti yang ditunjukkan pada contoh berikut:
Kotlin
val startForResult = registerForActivityResult(StartActivityForResult()) { result: ActivityResult -> if (result.resultCode == Activity.RESULT_OK) { val intent = result.data // Handle the Intent } } override fun onCreate(savedInstanceState: Bundle) { // ... val startButton = findViewById(R.id.start_button) startButton.setOnClickListener { // Use the Kotlin extension in activity-ktx // passing it the Intent you want to start startForResult.launch(Intent(this, ResultProducingActivity::class.java)) } }
Java
ActivityResultLauncher<Intent> mStartForResult = registerForActivityResult(new StartActivityForResult(), new ActivityResultCallback<ActivityResult>() { @Override public void onActivityResult(ActivityResult result) { if (result.getResultCode() == Activity.RESULT_OK) { Intent intent = result.getData(); // Handle the Intent } } }); @Override public void onCreate(@Nullable savedInstanceState: Bundle) { // ... Button startButton = findViewById(R.id.start_button); startButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { // The launcher with the Intent you want to start mStartForResult.launch(new Intent(this, ResultProducingActivity.class)); } }); }