O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Como ver um resultado de uma atividade

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 receber os detalhes do contato como resultado.

Embora as APIs startActivityForResult() e onActivityResult() subjacentes estejam disponíveis na classe Activity em todos os níveis de API, é altamente recomendável usar as APIs Activity Result introduzidas na Activity 1.2.0-alpha02 e no Fragment 1.3.0-alpha02 do AndroidX.

As APIs Activity Result fornecem 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 registerForActivityResult() antes da criação do fragmento ou da atividade, permitindo que ele seja usado diretamente ao declarar variáveis de membro para as instâncias ActivityResultLauncher retornadas.

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 no exemplo a seguir:

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 {
        // 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 = registerForActivityResult(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/*");
        }
    });
}

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

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 invoke(). 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> invoke(
            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> 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)
            }
    }
}

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, como mostrado no exemplo a seguir:

Kotlin

val startForResult = registerForActivityResult(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 = registerForActivityResult(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));
        }
    });
}