Google berkomitmen untuk mendorong terwujudnya keadilan rasial bagi komunitas Kulit Hitam. Lihat caranya.

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 lain 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 dan Anda akan menerima detail kontak sebagai hasilnya.

Meskipun startActivityForResult() dan onActivityResult() API yang digunakan tersedia di class Activity di semua API level, Anda sangat direkomendasikan untuk menggunakan Activity Result API yang diperkenalkan di Aktivitas 1.2.0-alpha02 dan Fragmen 1.3.0-alpha02 AndroidX.

Activity Result API menyediakan komponen untuk mendaftarkan hasil, meluncurkan hasil, dan menangani hasil setelah dikirim oleh sistem.

Mendaftarkan callback untuk Activity Result

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 di mana Anda meluncurkan aktivitas lainnya. Dengan begitu, 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 masukan 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 Anda sendiri.

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 allow you to pass
// in the mime type you'd like to allow the user to 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 yang menggunakan kontrak berbeda atau menginginkan callback terpisah, Anda dapat memanggil registerForActivityResult() beberapa kali untuk mendaftarkan beberapa instance ActivityResultLauncher. Anda harus selalu memanggil registerForActivityResult() dalam urutan yang sama pada setiap pembuatan fragmen atau aktivitas Anda untuk memastikan bahwa 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 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'd like to allow the user to 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 savedInstanceState: Bundle) {
    // ...

    Button selectButton = findViewById(R.id.select_button);

    selectButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            // Pass in the mime type you'd like to allow the user to 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 menggunakan registerForActivityResult() API, Anda juga dapat menerima hasil aktivitas di class terpisah yang tidak mengimplementasikan ActivityResultCaller dengan 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 extends 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<String>() {
                @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 = 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, sangat direkomendasikan untuk menggunakan API yang mengambil LifecycleOwner karena LifecycleOwner otomatis menghapus peluncur terdaftar Anda saat Lifecycle dihapus. Namun, jika LifecycleOwner tidak tersedia, setiap class ActivityResultLauncher memungkinkan Anda untuk memanggil unregister() secara manual sebagai alternatif.

Pengujian

Secara default, registerForActivityResult() otomatis menggunakan ActivityResultRegistry yang diberikan oleh aktivitas. Ini juga menyebabkan kelebihan muatan yang 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, memberikan ActivityResultRegistry pengujian dapat dilakukan dengan 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 invoke(). Daripada memanggil startActivityForResult(), implementasi pengujian Anda dapat memanggil dispatchResult() secara langsung yang memberikan hasil seperti yang ingin Anda gunakan dalam pengujian:

val testRegistry = object : ActivityResultRegistry() {
    override fun <I, O> invoke(
            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 melalui 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> invoke(
            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 penentuan class input dan output, dengan menggunakan Void (di Kotlin, gunakan Void? atau Unit) sebagai jenis input jika Anda tidak memerlukan input apa pun.

Setiap kontrak harus mengimplementasikan metode createIntent(), yang mengambil Context dan input, serta membuat Intent yang akan digunakan dengan startActivityForResult().

Setiap kontrak juga harus mengimplementasikan parseResult(), yang menghasilkan output dari resultCode yang ditentukan (misalnya, 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.

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