Iniciar otra actividad, ya sea dentro de tu app o desde otra, 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 foto tomada como resultado. También puedes iniciar la App de Contactos para que el usuario seleccione un contacto y, luego, recibir los detalles correspondientes como resultado.
Si bien las APIs subyacentes de startActivityForResult()
y onActivityResult()
están disponibles en la clase Activity
en todos niveles de API, Google recomienda usar las APIs de Activity Result que se introdujeron en las clases Activity
y Fragment
de AndroidX.
Las APIs de Activity Result proporcionan componentes para registrar un resultado, iniciar la actividad que produce el resultado, y manejar el resultado una vez son enviados por el sistema.
Cómo registrar una devolución de llamada para un resultado de actividad
Cuando se inicia una actividad para obtener un resultado, es posible —y casi seguro en el caso de las operaciones que consumen mucha memoria, como el uso de la cámara— que se destruyan tu proceso y tu actividad debido a la poca memoria.
Por este motivo, las APIs de Activity Result 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 APIs de Activity Result se encuentran en un objeto ComponentActivity
o Fragment
, proporcionan una API de registerForActivityResult()
para registrar la devolución de llamada de resultados. registerForActivityResult()
toma un objeto ActivityResultContract
y un objeto ActivityResultCallback
, y muestra un elemento ActivityResultLauncher
que usas para iniciar la otra actividad.
Un objeto ActivityResultContract
define el tipo de entrada necesario para producir un resultado junto con el tipo de salida del resultado. Las APIs proporcionan contratos predeterminados para acciones de intent básicas, como tomar una foto, solicitar permisos, etc. También puedes crear un contrato personalizado.
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 = 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 tienes varias llamadas a resultados de actividad y usas contratos diferentes o quieres devoluciones de llamada diferentes, puedes llamar a registerForActivityResult()
varias veces para registrar varias instancias de ActivityResultLauncher
. Debes llamar a registerForActivityResult()
en el mismo orden para cada creación de tu fragmento o actividad, de manera que los resultados en tránsito se entreguen a la devolución de llamada correcta.
Es seguro llamar a registerForActivityResult()
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 registerForActivityResult()
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 = 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/*"); } }); }
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 que puedas usar las APIs de registerForActivityResult()
, también puedes recibir el resultado de la actividad en una clase separada que no implementa ActivityResultCaller
usando directamente ActivityResultRegistry
.
Por ejemplo, tal vez quieras implementar un elemento 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> 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(); } }); } }
Cuando usas las APIs de ActivityResultRegistry
, Google recomienda usar las APIs 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 que un LifecycleOwner
no está disponible, cada clase ActivityResultLauncher
te permite llamar manualmente a unregister()
como alternativa.
Cómo realizar pruebas
De forma predeterminada, registerForActivityResult()
usa automáticamente el elemento ActivityResultRegistry
que proporciona la actividad. También proporciona una sobrecarga que te permite pasar tu propia instancia de ActivityResultRegistry
, que puedes usar para probar las llamadas de resultados de actividad sin iniciar otra actividad.
Al probar los fragmentos de tu app, debes proporcionar una ActivityResultRegistry
de prueba con un FragmentFactory
para pasar los datos 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 = 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; } // ... }
Cuando crees un ActivityResultRegistry
específico de una prueba, deberás implementar el método onLaunch()
. 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> onLaunch(
requestCode: Int,
contract: ActivityResultContract<I, O>,
input: I,
options: ActivityOptionsCompat?
) {
dispatchResult(requestCode, expectedResult)
}
}
La prueba completa crea el resultado esperado, construye un ActivityResultRegistry
de prueba, lo pasa al fragmento, activa el selector directamente o usando otras APIs de prueba como Espresso y, luego, verifica 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> 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)
}
}
}
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 clases de entrada y salida definidas, y usa Void
como el tipo de entrada si no necesitas ninguna entrada (en Kotlin, usa Void?
o Unit
).
Cada contrato debe implementar el método createIntent()
, que toma un elemento Context
y la entrada, y construye el objeto Intent
que se usará con startActivityForResult()
.
Cada contrato también debe implementar parseResult()
, que produce la salida del resultCode
determinado, como Activity.RESULT_OK
o Activity.RESULT_CANCELED
. y la 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.
En el siguiente ejemplo, se muestra cómo construir 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 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 objeto 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 = 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)); } }); }