Receber o resultado de uma atividade

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