Google se compromete a impulsar la igualdad racial para las comunidades afrodescendientes. Obtén información al respecto.

Cómo obtener un resultado de una actividad

Iniciar otra actividad, ya sea dentro de tu app o desde otra app, no tiene por qué ser una operación unidireccional. También puedes iniciar otra actividad y recibir un resultado. Por ejemplo, tu app puede iniciar una app de cámara y recibir la fotografía tomada como resultado. También puedes iniciar la app de Contactos para que el usuario seleccione un contacto, y recibirás los detalles de él como resultado.

Si bien las API startActivityForResult() y onActivityResult() subyacentes están disponibles en la clase Activity, en todos los niveles de API, se recomienda usar el resultado de las API de resultados de actividad en Activity 1.2.0-alpha02 y Fragment 1.3.0-alpha02 de AndroidX.

Las API de resultados de actividad proporcionan componentes para registrar, iniciar y administrar un resultado una vez que el sistema lo envía.

Cómo prepararse para un resultado de actividad

Cuando se inicia una actividad para obtener un resultado, es posible (y, en el caso de las operaciones que consumen mucha memoria, como el uso de la cámara, casi seguro) que tu proceso y tu actividad se destruyan debido a la poca memoria.

Por este motivo, las API de resultados de actividad separan la devolución de llamada de resultados del lugar en el código donde inicias la otra actividad. Como la devolución de llamada de resultados debe estar disponible cuando se recrea el proceso y la actividad, la devolución de llamada debe registrarse incondicionalmente cada vez que se crea tu actividad, incluso si la lógica de iniciar la otra actividad solo se produce con una entrada del usuario u otra lógica empresarial.

Cuando las API de resultado de actividad se encuentran en un objeto ComponentActivity o Fragment, proporcionan una API de prepareCall() para registrar la devolución de llamada de resultados. prepareCall() toma un objeto ActivityResultContract y un objeto ActivityResultCallback y muestra un objeto ActivityResultLauncher que usarás para iniciar la otra actividad.

Un objeto ActivityResultContract define el tipo de entrada necesario para producir un resultado junto con el tipo de resultado. Las API proporcionan contratos predeterminados para acciones de intent básicas, como tomar una foto, solicitar permisos, etc. También pueden crear sus propios contratos personalizados.

ActivityResultCallback es una interfaz de método único con un método onActivityResult() que toma un objeto del tipo de salida definido en ActivityResultContract:

Kotlin

    val getContent = prepareCall(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 = prepareCall(new GetContent(),
        new ActivityResultCallback<Uri>() {
            @Override
            public void onActivityResult(Uri uri) {
                // Handle the returned Uri
            }
    });
    

Si tienes varias llamadas a resultados de actividad que usan contratos diferentes o quieres devoluciones de llamada diferentes, puedes llamar a prepareCall() varias veces a fin de preparar varias instancias de ActivityResultLauncher. Siempre debes llamar a prepareCall() en el mismo orden para cada creación de tu fragmento o actividad a fin de asegurarte de que los resultados en curso se entreguen a la devolución de llamada correcta.

Es seguro llamar a prepareCall() antes de crear tu fragmento o actividad, lo que te permite usarlo directamente cuando declaras variables de miembro para las instancias ActivityResultLauncher que se muestran.

Cómo iniciar una actividad para obtener un resultado

Si bien prepareCall() registra tu devolución de llamada, no inicia la otra actividad ni la solicitud de un resultado. Esto es responsabilidad de la instancia ActivityResultLauncher que se muestra.

Si existe una entrada, el selector toma la entrada que coincide con el tipo de ActivityResultContract. Cuando llamas a launch(), se inicia el proceso de producción del resultado. Cuando el usuario finaliza con la actividad posterior y muestra el resultado, se ejecuta onActivityResult() desde ActivityResultCallback, como se muestra en el siguiente ejemplo:

Kotlin

    val getContent = prepareCall(GetContent()) { uri: Uri? ->
        // Handle the returned Uri
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...

        val selectButton = findViewById<Button>(R.id.select_button)

        selectButton.setOnClickListener {
            // Use the Kotlin extension in activity-ktx
            // passing it the mime type you'd like to allow the user to select
            getContent("image/*")
        }
    }
    

Java

    ActivityResultLauncher<String> mGetContent = prepareCall(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/*");
            }
        });
    }
    

Una versión sobrecargada de launch() te permite pasar un elemento ActivityOptionsCompat además de la entrada.

Cómo recibir un resultado de actividad en una clase separada

Si bien las clases ComponentActivity y Fragment implementan la interfaz ActivityResultCaller para permitirte usar las API de prepareCall(), también puedes recibir el resultado de la actividad en una clase por separado que no implementa ActivityResultCaller directamente con ActivityResultRegistry.

Por ejemplo, tal vez quieras implementar un LifecycleObserver que se encargue del registro de un contrato junto con el inicio del selector:

Kotlin

    class MyLifecycleObserver(private val registry : ActivityResultRegistry)
            : DefaultLifecycleObserver {
        lateinit var getContent : ActivityResultLauncher<String>

        fun onCreate(owner: LifecycleOwner) {
            getContent = registry.register("key", owner, GetContent()) { uri ->
                // Handle the returned Uri
            }
        }

        fun selectImage() {
            getContent("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();
                }
            });
        }
    }
    

Cuando se usan las API de ActivityResultRegistry, se recomienda usar las API que toman un elemento LifecycleOwner, ya que LifecycleOwner quita automáticamente el selector registrado cuando se destruye el Lifecycle. Sin embargo, en los casos en los que LifecycleOwner no está disponible, cada clase ActivityResultLauncher te permite llamar a unregister() de forma manual como alternativa.

Prueba

De forma predeterminada, prepareCall() usa automáticamente el elemento ActivityResultRegistry proporcionado por la actividad. También proporciona una sobrecarga que te permite pasar tu propia instancia de ActivityResultRegistry, que se puede usar para probar tus llamadas a resultados de actividad sin iniciar realmente otra actividad.

Cuando pruebas los fragmentos de tu app, puedes proporcionar una prueba ActivityResultRegistry mediante un FragmentFactory para pasar el elemento ActivityResultRegistry al constructor del fragmento.

Por ejemplo, un fragmento que usa el contrato TakePicturePreview para obtener una miniatura de la imagen podría escribirse de la siguiente manera:

Kotlin

    class MyFragment(
        private val registry: ActivityResultRegistry
    ) : Fragment() {
        val thumbnailLiveData = MutableLiveData<Bitmap?>

        val takePicture = prepareCall(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 =
            prepareCall(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;
        }

        // ...
    }
    

Cuando crees una prueba específica ActivityResultRegistry, deberás implementar el método invoke(). En lugar de llamar a startActivityForResult(), la implementación de prueba puede llamar directamente a dispatchResult() y proporcionar los resultados exactos que deseas usar en la prueba:

val testRegistry = object : ActivityResultRegistry() {
        override fun <I, O> invoke(
                requestCode: Int,
                contract: ActivityResultContract<I, O>,
                input: I,
                options: ActivityOptionsCompat?
        ) {
            dispatchResult(requestCode, expectedResult)
        }
    }
    

La prueba completa creará el resultado esperado, construirá una prueba ActivityResultRegistry, la pasará al fragmento, activará el selector (ya sea directamente o mediante otras API de prueba, como Espresso) y luego verificará los resultados:

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

Cómo crear un contrato personalizado

Si bien ActivityResultContracts contiene una serie de clases ActivityResultContract compiladas previamente que puedes usar, puedes proporcionar tus propios contratos que ofrezcan el tipo seguro de API que necesitas.

Cada ActivityResultContract requiere definir las clases de entrada y salida, con Void (en Kotlin, usa Void? o Unit) como el tipo de entrada si no necesitas ninguna entrada.

Cada contrato debe implementar el método createIntent(), que toma un elemento Context y la entrada, y construye el elemento Intent que se usará con startActivityForResult().

Cada contrato también debe implementar parseResult(), que produce la salida del elemento resultCode determinado (p. ej., Activity.RESULT_OK o Activity.RESULT_CANCELED) y el Intent.

De manera opcional, los contratos pueden implementar getSynchronousResult() (si es posible establecer el resultado de una entrada determinada sin necesidad de llamar a createIntent()), iniciar la otra actividad y usar parseResult() para compilar el resultado.

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 no necesitas un contrato personalizado, puedes usar el contrato StartActivityForResult. Este es un contrato genérico que toma cualquier elemento Intent como entrada y muestra un elemento ActivityResult, lo que te permite extraer resultCode y Intent como parte de tu devolución de llamada, como se muestra en el siguiente ejemplo:

Kotlin

    val startForResult = prepareCall(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(Intent(this, ResultProducingActivity::class.java))
        }
    }
    

Java

    ActivityResultLauncher<Intent> mStartForResult = prepareCall(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));
            }
        });
    }