Mendapatkan hasil dari aktivitas

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