Le démarrage d'une autre activité, que ce soit dans votre application ou dans une autre, ne doit pas nécessairement être unidirectionnel. Vous pouvez également lancer une autre activité et recevoir un résultat. Par exemple, votre application peut lancer une application d'appareil photo et obtenir la photo prise. Vous pouvez également lancer l'application Contacts pour que l'utilisateur sélectionne un contact. Vous recevez alors les coordonnées du contact.
Même si les API de startActivityForResult()
et onActivityResult()
sous-jacents sont disponibles dans la classe Activity
pour tous les niveaux d'API, il est vivement recommandé d'utiliser les API de résultat d'activité introduits dans AndroidX Activité et Fragment.
Les API de résultat d'activité fournissent des composants permettant d'enregistrer un résultat, de le lancer et de le traiter une fois qu'il a été envoyé par le système.
Enregistrer un rappel pour un résultat d'activité
Lorsque vous lancez une activité pour un résultat, il se peut (et c'est même presque certain en cas d'opérations exigeantes en mémoire, comme l'utilisation de l'appareil photo) que votre processus et votre activité soient détruits en raison d'une mémoire insuffisante.
Pour cette raison, les API de résultat d'activité dissocient le rappel du résultat de l'endroit du code où vous lancez l'autre activité. Étant donné que le rappel du résultat doit être disponible lorsque votre processus et votre activité sont recréés, le rappel doit être enregistré sans condition chaque fois que votre activité est créée, même si la logique de lancer l'autre activité n'intervient qu'en fonction de l'entrée utilisateur ou d'une autre logique métier.
Lorsqu'ils se trouvent dans un élément ComponentActivity
ou Fragment
, les API de résultat d'activité fournissent un API registerForActivityResult()
pour enregistrer le rappel des résultats. registerForActivityResult()
prend un élément ActivityResultContract
et un élément ActivityResultCallback
et renvoie un élément ActivityResultLauncher
que vous utiliserez pour lancer l'autre activité.
Un élément ActivityResultContract
définit le type d'entrée nécessaire pour produire un résultat avec le type de sortie du résultat. Les API fournissent des contrats par défaut pour les actions d'intent de base comme la capture de photos, les demandes d'autorisations, etc. Vous pouvez également créer vos propres contrats personnalisés.
ActivityResultCallback
est une interface à méthode unique avec une méthode onActivityResult()
qui accepte un objet du type de sortie défini dans l'élément 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 } });
Si plusieurs appels de résultats d'activité utilisent des contrats différents ou souhaitent des rappels distincts, vous pouvez appeler registerForActivityResult()
plusieurs fois pour enregistrer plusieurs instances de ActivityResultLauncher
. Vous devez toujours appeler registerForActivityResult()
dans le même ordre pour chaque fragment et activité créés afin de vous assurer que les résultats en cours de transfert sont distribués au rappel approprié.
Vous pouvez appeler registerForActivityResult()
avant de créer votre fragment ou votre activité afin de les utiliser directement lors de la déclaration de variables de membre pour les instances de ActivityResultLauncher
renvoyées.
Lancer une activité pour le résultat
Bien que registerForActivityResult()
enregistre votre rappel, il ne lance pas l'autre activité, et la requête de résultat est rejetée. Il s'agit de la responsabilité de l'instance de ActivityResultLauncher
renvoyée.
Si une entrée existe, le lanceur d'applications accepte l'entrée correspondant au type de ActivityResultContract
. Appeler launch()
lance le processus de production du résultat. Une fois que l'utilisateur a terminé l'activité et renvoie le résultat, onActivityResult()
de ActivityResultCallback
est exécuté, comme l'illustre l'exemple suivant :
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 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'd like to allow the user to select // as the input mGetContent.launch("image/*"); } }); }
Une version surchargée de launch()
vous permet de transmettre un ActivityOptionsCompat
en plus de l'entrée.
Recevoir un résultat d'activité dans une autre classe
Bien que les classes ComponentActivity
et Fragment
installent l'interface de ActivityResultCaller
pour vous permettre d'utiliser les API de registerForActivityResult()
, vous pouvez également recevoir le résultat de l'activité dans une classe distincte qui n'installe pas ActivityResultCaller
en utilisant ActivityResultRegistry
directement.
Par exemple, vous pouvez installer un élément LifecycleObserver
capable de gérer l'enregistrement d'un contrat tout en démarrant le lanceur d'applications :
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(); } }); } }
Lorsque vous utilisez les API de ActivityResultRegistry
, nous vous recommandons vivement d'utiliser les API qui utilisent LifecycleOwner
, car LifecycleOwner
supprime automatiquement votre lanceur d'applications enregistré lorsque Lifecycle
est détruit. Toutefois, dans les cas où LifecycleOwner
n'est pas disponible, chaque classe de ActivityResultLauncher
vous propose l'alternative d'appeler manuellement unregister()
.
Tests
Par défaut, registerForActivityResult()
utilise automatiquement l'élément ActivityResultRegistry
fourni par l'activité. Il propose également une surcharge qui vous permet de transmettre votre propre instance de ActivityResultRegistry
, qui peut être utilisée pour tester les appels de résultats d'activité sans lancer d'autres activités.
Lors du test des fragments de votre application, vous pouvez fournir un ActivityResultRegistry
de test en utilisant un FragmentFactory
pour transmettre le ActivityResultRegistry
au constructeur du fragment.
Par exemple, un fragment qui utilise le contrat TakePicturePreview
pour obtenir une miniature de l'image peut être écrit comme suit :
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; } // ... }
Lorsque vous créez un élément ActivityResultRegistry
pour un test, vous devez mettre en œuvre la méthode onLaunch()
. Au lieu d'appeler startActivityForResult()
, votre mise en œuvre de test peut appeler dispatchResult()
directement, ce qui vous permet d'obtenir les résultats exacts que vous souhaitez utiliser dans votre test :
val testRegistry = object : ActivityResultRegistry() {
override fun <I, O> onLaunch(
requestCode: Int,
contract: ActivityResultContract<I, O>,
input: I,
options: ActivityOptionsCompat?
) {
dispatchResult(requestCode, expectedResult)
}
}
Le test complet va créer le résultat attendu, construire un test ActivityResultRegistry
, le transmettre au fragment, déclencher le lanceur d'applications (que ce soit directement ou via d'autres API de test comme Espresso), puis vérifier le résultats :
@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)
}
}
}
Créer un contrat personnalisé
Bien que l'élément ActivityResultContracts
contienne un certain nombre de classes de ActivityResultContract
prédéfinies à utiliser, vous pouvez fournir vos propres contrats pour obtenir l'API sécurisée dont vous avez besoin.
Chaque ActivityResultContract
nécessite de définir les classes d'entrée et de sortie en utilisant Void
(dans Kotlin, utilisez Void?
ou Unit
) comme type d'entrée si vous n'avez pas besoin d'entrée.
Chaque contrat doit mettre en œuvre la méthode createIntent()
, qui accepte un Context
et l'entrée, puis construit l'Intent
qui sera utilisé avec startActivityForResult()
.
Chaque contrat doit également mettre en œuvre parseResult()
, qui génère le résultat de la propriété resultCode
donnée (par exemple,
Activity.RESULT_OK
ou Activity.RESULT_CANCELED
) et l'élément Intent
.
Les contrats peuvent éventuellement mettre en œuvre getSynchronousResult()
s'il est possible de déterminer le résultat pour une entrée donnée sans devoir appeler createIntent()
, démarrer l'autre activité et utiliser parseResult()
pour créer le résultat.
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); } }
Si vous n'avez pas besoin de contrat personnalisé, vous pouvez utiliser le contrat StartActivityForResult
. Il s'agit d'un contrat générique qui accepte n'importe quel Intent
et renvoie un élément ActivityResult
, ce qui vous permet d'extraire le resultCode
et l'élément Intent
dans votre rappel, comme l'illustre l'exemple suivant :
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)); } }); }