Iniciar outra atividade, seja dentro do próprio app ou em outro app, não precisa ser uma operação de mão única. Você também pode iniciar outra atividade e receber um resultado. Por exemplo, o app pode iniciar um app de câmera e receber a foto capturada como resultado. Ou você pode iniciar o app Contatos para que o usuário selecione um contato e envie automaticamente os detalhes dele como resultado.
Embora as APIs
startActivityForResult()
e
onActivityResult()
estejam disponíveis na classe Activity
em todos os níveis da API, é altamente recomendável
usar as APIs Activity Result introduzidas nas APIs
Activity
e Fragment do AndroidX.
As APIs Activity Result oferecem componentes para registrar um resultado, iniciá-lo e processá-lo depois dele ser enviado pelo sistema.
Como registrar um callback para um Activity Result
Ao iniciar uma atividade para um resultado, é possível (e em casos de operações de muita memória, como muito provavelmente o uso da câmera) que seu processo e sua atividade sejam destruídos devido à pouca memória.
Por isso, as APIs Activity Result dissociam o callback do resultado no 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 o callback seja registrado incondicionalmente sempre que sua 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 um ComponentActivity
ou em um Fragment
, as APIs Activity Result fornecem uma API registerForActivityResult()
para registrar o resultado do callback. Um registerForActivityResult()
recebe um ActivityResultContract
e um ActivityResultCallback
e retorna um ActivityResultLauncher
que você usará para iniciar a outra atividade.
Um ActivityResultContract
define o tipo de entrada necessário para produzir um resultado junto com o tipo de saída do resultado. As APIs fornecem contratos padrão para ações básicas de intent como tirar uma foto, solicitar permissões e assim por diante. Você também pode criar seus próprios contratos personalizados.
Um ActivityResultCallback
é uma interface de método único 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 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 } });
Se você tiver várias chamadas de resultado de atividade que usem contratos diferentes ou quiserem retornos de chamada separados, é possível chamar registerForActivityResult()
várias vezes para registrar várias instâncias ActivityResultLauncher
. É necessário sempre chamar registerForActivityResult()
na mesma ordem a cada criação do seu fragmento ou atividade para garantir que os resultados pendentes 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 ActivityResultLauncher
retornadas.
Como iniciar uma atividade para o resultado
Enquanto o registerForActivityResult()
registra seu callback, ele não iniciará 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'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/*"); } }); }
Uma versão sobrecarregada de launch()
permite que você transmita um ActivityOptionsCompat
além da 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()
, você
também pode receber o resultado da atividade em uma classe separada que não implementa
ActivityResultCaller
usando
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
, é altamente recomendável usar aquelas com um LifecycleOwner
, já que o LifecycleOwner
remove de forma automática 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 que você chame manualmente unregister()
como alternativa.
Teste
Por padrão, o registerForActivityResult()
automaticamente usa o ActivityResultRegistry
fornecido pela atividade. Ele também fornece uma sobrecarga que permite que você transmita sua própria instância de ActivityResultRegistry
que pode ser usada para testar suas chamadas de resultado de atividade sem precisar iniciar outra atividade.
Ao testar os fragmentos do app, o fornecimento de um teste ActivityResultRegistry
pode ser feito usando um FragmentFactory
que será transmitido no 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 um teste específico ActivityResultRegistry
, você precisa implementar o método onLaunch()
. Em vez de chamar startActivityForResult()
, sua implementação de teste pode chamar dispatchResult()
diretamente, fornecendo os resultados exatos que você quer usar no teste:
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 um teste ActivityResultRegistry
, transmite-o para o fragmento, aciona a tela de início (seja de forma direta ou por meio de 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 a definição das classes de entrada e saída, usando Void
(em Kotlin, use Void?
ou Unit
) como o tipo de entrada se você não precisar de nenhuma.
Cada contrato precisa implementar o método createIntent()
, que leva um Context
e a entrada, e constrói o Intent
que será usado com startActivityForResult()
.
Cada contrato também precisa implementar parseResult()
, que produz a saída do resultCode
especificado (por exemplo,
Activity.RESULT_OK
ou Activity.RESULT_CANCELED
) e a Intent
.
Os contratos também podem implementar getSynchronousResult()
se for possível determinar o resultado de uma certa entrada sem precisar chamar createIntent()
, iniciar a outra atividade e usar parseResult()
para criar o 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); } }
Se você não precisar de um contrato personalizado, poderá usar o contrato StartActivityForResult
. Esse é um contrato genérico que considera qualquer Intent
como uma entrada e
retorna um
ActivityResult
,
o que permite que você extraia resultCode
e 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)); } }); }