Ottenere un risultato da un'attività

L'avvio di un'altra attività, che sia un'attività all'interno della tua app o un'altra app, non deve necessariamente essere un'operazione unidirezionale. Puoi anche avviare un'attività e ricevere un risultato a tua disposizione. Ad esempio, l'app può avviare un'app fotocamera e ricevere di conseguenza la foto scattata. In alternativa, puoi avviare l'app Contatti per consentire all'utente di selezionare un contatto e ricevere di conseguenza i dettagli del contatto.

Sebbene le API sottostanti startActivityForResult() e onActivityResult() sono disponibili nella classe Activity a tutti i livelli API, Google consiglia vivamente di utilizzare le API Activity Result introdotte nelle classi AndroidX Activity e Fragment.

Le API Activity Result forniscono i componenti per la registrazione di un risultato, l'avvio e la gestione del risultato una volta inviato dal sistema.

Registra un callback per un risultato di un'attività

Quando avvii un'attività per un risultato, è possibile e, in caso di operazioni ad alta intensità di memoria, come l'utilizzo della fotocamera, è quasi certo che il tuo processo e la tua attività vengano eliminati a causa della memoria insufficiente.

Per questo motivo, le API dei risultati delle attività disaccoppiano il callback dei risultati dalla posizione del codice in cui avvii l'altra attività. Poiché il callback del risultato deve essere disponibile quando vengono ricreati il processo e l'attività, il callback deve essere registrato incondizionatamente ogni volta che viene creata l'attività, anche se la logica di avvio dell'altra attività avviene solo in base all'input utente o a un'altra logica di business.

Quando ti trovi in un elemento ComponentActivity o Fragment, le API Activity Result forniscono un'API registerForActivityResult() per la registrazione del callback dei risultati. registerForActivityResult() prende un ActivityResultContract e un ActivityResultCallback e restituisce un ActivityResultLauncher, che utilizzerai per avviare l'altra attività.

Un ActivityResultContract definisce il tipo di input necessario per produrre un risultato insieme al tipo di output del risultato. Le API forniscono contratti predefiniti per azioni intent di base come scattare una foto, richiedere autorizzazioni e così via. Puoi anche creare un contratto personalizzato.

ActivityResultCallback è un'interfaccia a metodo singolo con un metodo onActivityResult() che accetta un oggetto del tipo di output definito nel 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 hai più chiamate dei risultati di attività e utilizzi contratti diversi o vuoi callback separati, puoi chiamare registerForActivityResult() più volte per registrare più istanze ActivityResultLauncher. Devi chiamare registerForActivityResult() nello stesso ordine per ogni creazione del frammento o dell'attività, in modo che i risultati in corso vengano inviati al callback corretto.

Puoi chiamare in sicurezza registerForActivityResult() prima della creazione del frammento o dell'attività, in modo da poterlo utilizzare direttamente durante la dichiarazione delle variabili membro per le istanze ActivityResultLauncher restituite.

Avvia un'attività per il risultato

Anche se registerForActivityResult() registra il tuo callback, non avvia l'altra attività e avvia la richiesta di un risultato. Questa è invece responsabilità dell'istanza ActivityResultLauncher restituita.

Se esiste un input, Avvio app accetta quello che corrisponde al tipo di ActivityResultContract. La chiamata a launch() avvia il processo di produzione del risultato. Quando l'utente ha terminato l'attività successiva e il ritorno, viene eseguito il onActivityResult() della ActivityResultCallback, come mostrato nell'esempio seguente:

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 versione sovraccarico di launch() ti consente di passare un ActivityOptionsCompat oltre all'input.

Ricevi il risultato di un'attività in un corso a parte

Sebbene le classi ComponentActivity e Fragment implementino l'interfaccia ActivityResultCaller per consentirti di utilizzare le API registerForActivityResult(), puoi anche ricevere il risultato dell'attività in una classe separata che non viene implementata ActivityResultCaller utilizzando direttamente ActivityResultRegistry.

Ad esempio, potresti voler implementare un elemento LifecycleObserver che gestisca la registrazione di un contratto e l'avvio di Avvio app:

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

Se utilizzi le API ActivityResultRegistry, Google consiglia vivamente di utilizzare le API che utilizzano LifecycleOwner, poiché LifecycleOwner rimuove automaticamente l'Avvio app registrato quando viene eliminato Lifecycle. Tuttavia, nei casi in cui LifecycleOwner non è disponibile, ogni corso ActivityResultLauncher ti consente di chiamare manualmente unregister() come alternativa.

Test

Per impostazione predefinita, registerForActivityResult() utilizza automaticamente ActivityResultRegistry fornito dall'attività. Fornisce inoltre un sovraccarico che ti consente di passare alla tua istanza di ActivityResultRegistry, utilizzabile per testare le chiamate dei risultati dell'attività senza avviare effettivamente un'altra attività.

Quando testi i frammenti della tua app, fornisci un test ActivityResultRegistry utilizzando un FragmentFactory per passare ActivityResultRegistry al costruttore del frammento.

Ad esempio, un frammento che utilizza il contratto TakePicturePreview per ottenere una miniatura dell'immagine potrebbe essere scritto in modo simile al seguente:

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 crei un elemento ActivityResultRegistry specifico per il test, devi implementare il metodo onLaunch(). Anziché chiamare startActivityForResult(), l'implementazione di test può chiamare direttamente dispatchResult(), fornendo i risultati esatti che vuoi utilizzare nel test:

val testRegistry = object : ActivityResultRegistry() {
    override fun <I, O> onLaunch(
            requestCode: Int,
            contract: ActivityResultContract<I, O>,
            input: I,
            options: ActivityOptionsCompat?
    ) {
        dispatchResult(requestCode, expectedResult)
    }
}

Il test completo crea il risultato previsto, costruisce un test ActivityResultRegistry, lo passa al frammento, attiva Avvio app direttamente o utilizzando altre API di test come Espresso, quindi verifica i risultati:

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

Crea un contratto personalizzato

Sebbene ActivityResultContracts contenga una serie di classi ActivityResultContract predefinite da utilizzare, puoi fornire i tuoi contratti che forniscono l'API precisa a prova di tipo di cui hai bisogno.

Ogni ActivityResultContract richiede classi di input e di output definite, utilizzando Void come tipo di input se non è richiesto alcun input (in Kotlin, utilizza Void? o Unit).

Ogni contratto deve implementare il metodo createIntent(), che prende un Context e l'input e crea il Intent utilizzato con startActivityForResult().

Ogni contratto deve implementare anche parseResult(), che produce l'output dal resultCode specificato, come Activity.RESULT_OK o Activity.RESULT_CANCELED, e il Intent.

I contratti possono facoltativamente implementare getSynchronousResult() se è possibile determinare il risultato per un determinato input senza dover chiamare createIntent(), avviare l'altra attività e utilizzare parseResult() per creare il risultato.

L'esempio seguente mostra come creare 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);
    }
}

Se non hai bisogno di un contratto personalizzato, puoi utilizzare il contratto di StartActivityForResult. Si tratta di un contratto generico che prende qualsiasi Intent come input e restituisce un ActivityResult, consentendoti di estrarre resultCode e Intent come parte del callback, come mostrato nell'esempio seguente:

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