Iniciar outra atividade, seja dentro do seu app ou de outro, não precisa ser uma operação de mão única. Você também pode iniciar uma atividade e receber um resultado. Por exemplo, é possível iniciar um app de câmera e receber a foto capturada como resultado. Ou você pode iniciar o app Contatos para o usuário selecionar um contato e receber os detalhes dele como resultado.
Embora
startActivityForResult()
e
onActivityResult()
estejam disponíveis na classe Activity
em todos os níveis de API, o Google recomenda
usar as APIs Activity Result introduzidas nas classes
Activity
e Fragment
do AndroidX.
As APIs Activity Result fornecem componentes para registrar um resultado. iniciando a atividade que produz o resultado e manipulando o resultado assim que ele são enviadas pelo sistema.
Registrar um callback para um resultado de atividade
Ao iniciar uma atividade para um resultado, é possível que seu processo e sua atividade sejam destruídos devido à pouca memória. Em casos de operações que consomem muita memória, como o uso da câmera, isso é quase certo.
Por isso, as APIs Activity Result dissociam o callback de resultado do local onde você inicia a outra atividade. Como o callback do resultado precisa estar disponível quando seu processo e atividade são recriados, é necessário que esse callback seja registrado incondicionalmente sempre que a atividade for criada, mesmo se a lógica de iniciar a outra atividade ocorrer apenas com base na entrada do usuário ou em outra lógica de negócios.
Em uma classe
ComponentActivity
ou
Fragment
, as APIs Activity Result
fornecem uma
API registerForActivityResult()
para registrar o resultado do callback. A registerForActivityResult()
recebe uma classe
ActivityResultContract
e uma
ActivityResultCallback
e retorna uma
ActivityResultLauncher
que você usará para iniciar a outra atividade.
Uma ActivityResultContract
define o tipo de entrada necessário para produzir um resultado
com o tipo de saída do resultado. As APIs fornecem
contratos padrão
para ações de intent básicas, como tirar uma foto, pedir permissões e
assim por diante. Você também pode
criar um contrato personalizado.
Um ActivityResultCallback
é uma interface de método única com um método
onActivityResult()
que usa um objeto do tipo de saída definido no
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 } });
Se você tem várias chamadas de resultado de atividade e usa
contratos
diferentes ou quer callbacks separados, é possível chamar registerForActivityResult()
várias
vezes para registrar diversas instâncias ActivityResultLauncher
. É necessário
chamar registerForActivityResult()
na mesma ordem a cada criação do
fragmento ou da atividade para que os resultados em trânsito sejam entregues ao
callback correto.
É seguro chamar a API registerForActivityResult()
antes da criação do fragmento ou da atividade,
permitindo que ela seja usada diretamente ao declarar variáveis de membro para
as instâncias de ActivityResultLauncher
retornadas.
Iniciar uma atividade para o resultado
Enquanto a API registerForActivityResult()
registra seu callback, ela não inicia a outra atividade e a solicitação de um resultado. Essa é a responsabilidade da instância ActivityResultLauncher
retornada.
Se a entrada existir, a tela de início usará a entrada que corresponde ao tipo de ActivityResultContract
. Chamar launch()
inicia o processo de produção do resultado. Quando o usuário termina a
atividade subsequente e retorna, o onActivityResult()
do
ActivityResultCallback
é executado, conforme mostrado neste exemplo:
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/*"); } }); }
Uma versão sobrecarregada de
launch()
permite transmitir uma
ActivityOptionsCompat
junto à entrada.
Receber um resultado de atividade em uma classe separada
Embora as classes ComponentActivity
e Fragment
implementem a
interface ActivityResultCaller
para permitir o uso das APIs registerForActivityResult()
, também é possível
receber o resultado da atividade em uma classe separada que não implementa
ActivityResultCaller
usando a
ActivityResultRegistry
diretamente.
Por exemplo, pode ser interessante implementar um
LifecycleObserver
que processe o registro de um contrato ao abrir a tela de início:
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(); } }); } }
Ao usar as APIs ActivityResultRegistry
, o Google recomenda
aquelas que recebem um LifecycleOwner
, já que o LifecycleOwner
remove automaticamente
a tela de início registrada quando o Lifecycle
é destruído. No entanto,
nos casos em que um LifecycleOwner
não está disponível, cada
classe ActivityResultLauncher
permite chamar um método
unregister()
manualmente como alternativa.
Testar
Por padrão, a API registerForActivityResult()
usa automaticamente a
ActivityResultRegistry
fornecida pela atividade. Ela também fornece uma sobrecarga que permite que você transmita
sua instância de ActivityResultRegistry
, que pode ser usada para testar as
chamadas de resultado sem precisar iniciar outra atividade.
Ao testar os fragmentos do app,
um teste ActivityResultRegistry
é fornecido usando uma classe
FragmentFactory
para transmitir a
ActivityResultRegistry
ao construtor do fragmento.
Por exemplo, um fragmento que usa o contrato TakePicturePreview
para receber uma
miniatura
da imagem pode ser escrito da seguinte maneira:
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; } // ... }
Quando for criar uma ActivityResultRegistry
específica para testes, você precisará implementar
o
método
onLaunch()
. Em vez de chamar startActivityForResult()
, a
implementação de teste pode chamar
dispatchResult()
diretamente, fornecendo os resultados exatos que você quer usar:
val testRegistry = object : ActivityResultRegistry() {
override fun <I, O> onLaunch(
requestCode: Int,
contract: ActivityResultContract<I, O>,
input: I,
options: ActivityOptionsCompat?
) {
dispatchResult(requestCode, expectedResult)
}
}
O teste completo cria o resultado esperado, constrói uma
ActivityResultRegistry
de teste, transmite essa API para o fragmento, aciona a tela de início
diretamente ou usando outras APIs de teste (como Espresso) e verifica os 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)
}
}
}
Criar um contrato personalizado
Embora ActivityResultContracts
contenha uma série de classes ActivityResultContract
predefinidas para uso, você pode
fornecer seus próprios contratos com a API de tipo seguro de que você precisa.
Cada ActivityResultContract
requer classes de entrada e saída definidas,
usando Void
como o tipo de entrada se você
não precisar de nenhuma. No Kotlin, use Void?
ou Unit
.
Cada contrato precisa implementar o método
createIntent()
,
que leva um Context
e a entrada, e constrói a Intent
que será usada
com startActivityForResult()
.
Os contratos também precisam implementar
parseResult()
,
que produz a saída do resultCode
especificado (como
Activity.RESULT_OK
ou Activity.RESULT_CANCELED
) e a Intent
.
Além disso, os contratos poderão implementar
getSynchronousResult()
se for possível determinar o resultado de uma entrada sem
precisar chamar createIntent()
, iniciar a outra atividade e usar
parseResult()
para criar o resultado.
O exemplo a seguir mostra como criar uma 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); } }
Se você não precisa de um contrato personalizado,
pode usar o
StartActivityForResult
. Esse é um contrato genérico que considera qualquer Intent
como uma entrada e
retorna uma classe
ActivityResult
,
o que permite que você extraia o resultCode
e a Intent
como parte do callback,
conforme mostrado neste exemplo:
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)); } }); }