Le lancement d'une autre activité, qu'il s'agisse d'une activité dans votre application ou dans une autre, ne doit pas nécessairement être une opération à sens unique. Vous pouvez également lancer une activité et recevoir un résultat. Par exemple, votre application peut lancer une application d'appareil photo et recevoir la photo prise comme résultat. Vous pouvez également démarrer l'application Contacts pour que l'utilisateur sélectionne un contact, puis reçoive ses coordonnées en conséquence.
Bien que les API startActivityForResult()
et onActivityResult()
sous-jacentes soient disponibles dans la classe Activity
sur tous les niveaux d'API, Google recommande vivement d'utiliser les API de résultat d'activité introduites dans les classes AndroidX Activity
et Fragment
.
Les API de résultat d'activité fournissent des composants pour l'enregistrement d'un résultat, lancer l'activité qui génère le résultat et gérer ce dernier une fois qu'il est 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 il 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 dans le 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 lancement de l'autre activité n'intervient qu'en fonction de l'entrée utilisateur ou d'une autre logique métier.
Lorsqu'elles 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 un contrat personnalisé.
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 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 } });
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 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 obtenir un 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 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/*"); } }); }
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
Alors que les classes ComponentActivity
et Fragment
implémentent l'interface ActivityResultCaller
pour vous permettre d'utiliser les API registerForActivityResult()
, vous pouvez également recevoir le résultat de l'activité dans une classe distincte qui n'implémente pas ActivityResultCaller
en utilisant directement ActivityResultRegistry
.
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(); } }); } }
Avec les API ActivityResultRegistry
, Google vous recommande vivement d'opter pour les API qui utilisent LifecycleOwner
, car LifecycleOwner
supprime automatiquement le lanceur d'applications enregistré lorsque Lifecycle
est détruit. Toutefois, si aucun LifecycleOwner
n'est disponible, chaque classe ActivityResultLauncher
vous permet d'appeler manuellement unregister()
en tant qu'alternative.
Test
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 devez fournir un ActivityResultRegistry
de test à l'aide d'un élément 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 crée le résultat attendu, construit un ActivityResultRegistry
de test, le transmet au fragment, déclenche le lanceur d'applications directement ou à l'aide d'autres API de test comme Espresso, puis vérifie les 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 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 des classes d'entrée et de sortie définies, en utilisant Void
comme type d'entrée si vous n'avez pas besoin d'entrée (en Kotlin, utilisez Void?
ou Unit
).
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 implémenter parseResult()
, qui génère la sortie de la propriété resultCode
donnée, telle qu'Activity.RESULT_OK
ou Activity.RESULT_CANCELED
. et 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.
L'exemple suivant montre comment construire un 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); } }
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)); } }); }