Obtenir un résultat depuis une activité

Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

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