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