Ottenere un risultato da un'attività

Avvio di un'altra attività, all'interno dell'app o da un'altra app, non deve essere necessariamente un'operazione unidirezionale. Puoi anche avviare un'attività e ricevere un risultato. Ad esempio, l'app può avviare un'app Fotocamera e ricevere la foto acquisita di conseguenza. In alternativa, puoi avviare l'app Contatti per consentire all'utente di selezionare un contatto e di riceverlo dettagli.

Anche se la parte startActivityForResult() e onActivityResult() Le API sono disponibili nel corso Activity a tutti i livelli API, Google consiglia di utilizzare le API Activity Result introdotte in AndroidX Activity e Fragment corsi.

Le API Activity Result forniscono i componenti per la registrazione di un risultato, avviando l'attività che produce il risultato e gestendolo una volta viene inviato dal sistema.

Registrare un callback per un risultato di attività

Quando si avvia un'attività per un risultato, è possibile e, in caso di per le operazioni che richiedono molta memoria, come l'utilizzo della fotocamera, e l'attività verrà eliminata a causa della memoria insufficiente.

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

Quando si trova in una ComponentActivity o un Fragment, il risultato dell'attività Le API offrono un registerForActivityResult() API per la registrazione del callback dei risultati. registerForActivityResult() prende un ActivityResultContract e un ActivityResultCallback e restituisce un ActivityResultLauncher, che usi per avviare l'altra attività.

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

ActivityResultCallback è un'interfaccia a singolo metodo con un onActivityResult() che prende 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 sui risultati di attività e utilizzi contratti o se vuoi ricevere callback separati, puoi chiamare registerForActivityResult() per registrare più istanze ActivityResultLauncher. Devi chiama registerForActivityResult() nello stesso ordine per ogni creazione dei tuoi frammento o attività in modo che i risultati in transito vengano inviati il callback corretto.

registerForActivityResult() può essere chiamato in sicurezza prima del frammento o dell'attività viene creato, in modo che possa essere utilizzato direttamente durante la dichiarazione delle variabili dei membri per le istanze ActivityResultLauncher restituite.

Avvia un'attività per il risultato

Mentre registerForActivityResult() registra il callback, non avvia l'altra attività e avvia la richiesta per un risultato. Invece, è responsabilità dell'istanza ActivityResultLauncher restituita.

Se esiste un input, Avvio app accetta l'input che corrisponde al tipo di ActivityResultContract. Chiamata in corso launch() avvia il processo di produzione del risultato. Quando l'utente ha terminato l'attività successiva e i resi, il onActivityResult() del Viene quindi eseguito 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 sovraccarica launch() ti permette di passare ActivityOptionsCompat oltre all'input.

Ricevere un risultato dell'attività in un corso separato

Mentre le classi ComponentActivity e Fragment implementano il metodo ActivityResultCaller: per consentirti di usare le API registerForActivityResult(), puoi anche ricevere il risultato dell'attività in una classe separata che non implementa ActivityResultCaller utilizzando ActivityResultRegistry .

Ad esempio, potresti voler implementare una LifecycleObserver che gestisce la registrazione di un contratto e il lancio 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();
            }
        });
    }
}

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

Test

Per impostazione predefinita, registerForActivityResult() utilizza automaticamente ActivityResultRegistry forniti dall'attività. Fornisce anche un sovraccarico che ti consente di passare nella tua istanza di ActivityResultRegistry che puoi utilizzare per testare risultati di attività chiamati senza avviare effettivamente un'altra attività.

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

Ad esempio, un frammento che utilizza il contratto TakePicturePreview per ottenere un 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 onLaunch() . Invece di chiamare startActivityForResult(), il test di implementazione può chiamare dispatchResult() fornendo i risultati esatti che vuoi usare 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 e genera un test ActivityResultRegistry, la passa al frammento, attiva l'Avvio app direttamente o utilizzando altre API di test, come Espresso, e 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

Mentre ActivityResultContracts contiene una serie di classi ActivityResultContract predefinite da utilizzare, i tuoi contratti che forniscono l'API precisa e sicura per i tipi di cui hai bisogno.

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

Ogni contratto deve implementare createIntent() che prende Context e l'input e genera il valore Intent che viene utilizzato con startActivityForResult().

Ogni contratto deve inoltre implementare parseResult(), che produce l'output dall'resultCode specificato, come Activity.RESULT_OK o Activity.RESULT_CANCELED e i Intent.

I contratti possono implementare getSynchronousResult() se è possibile determinare il risultato per un determinato input senza di dover chiamare createIntent(), iniziare l'altra attività e usare 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 StartActivityForResult contratto. Questo è un contratto generico che prende qualsiasi Intent come input e restituisce un ActivityResult, che ti consente 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));
        }
    });
}