Ergebnis aus einer Aktivität abrufen

Das Starten einer anderen Aktivität innerhalb Ihrer Anwendung oder von einer anderen Anwendung aus muss nicht in eine Richtung erfolgen. Sie können auch eine Aktivität starten und ein Ergebnis erhalten. Ihre App kann beispielsweise eine Kamera-App starten und das aufgenommene Foto erhalten. Oder Sie starten die Kontakte-App, damit der Nutzer einen Kontakt auswählt und dann die Kontaktdetails erhält.

Die zugrunde liegenden APIs startActivityForResult() und onActivityResult() sind in der Klasse Activity auf allen API-Ebenen verfügbar. Google empfiehlt jedoch dringend, die in den AndroidX-Klassen Activity und Fragment eingeführten Activity Result APIs zu verwenden.

Die Activity Result APIs bieten Komponenten für die Registrierung für ein Ergebnis, den Start des Ergebnisses und die Verarbeitung des Ergebnisses, sobald es vom System weitergeleitet wird.

Callback für ein Aktivitätsergebnis registrieren

Wenn Sie eine Aktivität für ein Ergebnis starten, ist es möglich – und im Fall von speicherintensiven Vorgängen wie der Kameranutzung – sehr wahrscheinlich, dass Ihr Prozess und Ihre Aktivität aufgrund des geringen Arbeitsspeichers zerstört werden.

Aus diesem Grund entkoppeln die Activity Result APIs den Ergebnis-Callback von der Stelle im Code, an der Sie die andere Aktivität starten. Da der Ergebnis-Callback verfügbar sein muss, wenn Ihr Prozess und Ihre Aktivität neu erstellt werden, muss der Callback bei jeder Erstellung Ihrer Aktivität unbedingt registriert werden, selbst wenn die Logik zum Starten der anderen Aktivität nur auf Grundlage von Nutzereingaben oder einer anderen Geschäftslogik erfolgt.

In einem ComponentActivity- oder Fragment-Objekt bieten die Aktivitätsergebnis-APIs eine registerForActivityResult() API zum Registrieren des Ergebnis-Callbacks. registerForActivityResult() verwendet ein ActivityResultContract und ein ActivityResultCallback und gibt ein ActivityResultLauncher-Element zurück, mit dem Sie die andere Aktivität starten.

Ein ActivityResultContract definiert den Eingabetyp, der zum Erstellen eines Ergebnisses zusammen mit dem Ausgabetyp des Ergebnisses erforderlich ist. Die APIs bieten Standardverträge für grundlegende Intent-Aktionen wie das Aufnehmen eines Bildes, das Anfordern von Berechtigungen usw. Außerdem haben Sie die Möglichkeit, einen benutzerdefinierten Vertrag zu erstellen.

ActivityResultCallback ist eine einzelne Methodenschnittstelle mit der Methode onActivityResult(), die ein Objekt des Ausgabetyps verwendet, der in ActivityResultContract definiert ist:

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

Wenn Sie mehrere Aktivitätsergebnisaufrufe haben und entweder unterschiedliche Verträge verwenden oder separate Callbacks wünschen, können Sie registerForActivityResult() mehrmals aufrufen, um mehrere ActivityResultLauncher-Instanzen zu registrieren. Sie müssen registerForActivityResult() bei jeder Erstellung Ihres Fragments oder Ihrer Aktivität in derselben Reihenfolge aufrufen, damit die Inflight-Ergebnisse an den richtigen Callback gesendet werden.

registerForActivityResult() kann bedenkenlos aufgerufen werden, bevor das Fragment oder die Aktivität erstellt wird, sodass es direkt bei der Deklaration von Mitgliedsvariablen für die zurückgegebenen ActivityResultLauncher-Instanzen verwendet werden kann.

Aktivität für Ergebnis starten

Während registerForActivityResult() Ihren Callback registriert, wird die andere Aktivität nicht gestartet und die Anfrage für ein Ergebnis gestartet. Stattdessen liegt dies in der Verantwortung der zurückgegebenen ActivityResultLauncher-Instanz.

Wenn eine Eingabe vorhanden ist, verwendet der Launcher die Eingabe, die dem Typ von ActivityResultContract entspricht. Durch Aufrufen von launch() wird die Ergebniserstellung gestartet. Wenn der Nutzer die nachfolgende Aktivität abgeschlossen hat und zurückkehrt, wird das onActivityResult() aus dem ActivityResultCallback ausgeführt, wie im folgenden Beispiel gezeigt:

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

Bei einer überlasteten Version von launch() können Sie zusätzlich zur Eingabe ein ActivityOptionsCompat-Objekt übergeben.

Ein Aktivitätsergebnis in einem separaten Kurs erhalten

Während die Klassen ComponentActivity und Fragment die ActivityResultCaller-Schnittstelle implementieren, damit Sie die registerForActivityResult() APIs verwenden können, können Sie das Aktivitätsergebnis auch in einer separaten Klasse empfangen, die ActivityResultCaller nicht implementiert, indem Sie ActivityResultRegistry direkt verwenden.

Du kannst beispielsweise einen LifecycleObserver implementieren, der die Registrierung eines Vertrags sowie das Starten des Launchers übernimmt:

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

Bei der Verwendung der ActivityResultRegistry APIs empfiehlt Google dringend, die APIs zu verwenden, die ein LifecycleOwner verarbeiten, da der LifecycleOwner den registrierten Launcher automatisch entfernt, wenn der Lifecycle gelöscht wird. Wenn jedoch keine LifecycleOwner verfügbar ist, können Sie unregister() mit jeder ActivityResultLauncher-Klasse manuell aufrufen.

Test

Standardmäßig verwendet registerForActivityResult() automatisch den von der Aktivität bereitgestellten ActivityResultRegistry. Außerdem stellt es eine Überlastung bereit, mit der Sie eine eigene Instanz von ActivityResultRegistry übergeben können, mit der Sie Ihre Aktivitätsergebnisaufrufe testen können, ohne eine weitere Aktivität zu starten.

Beim Testen der Fragmente Ihrer App stellen Sie eine Test-ActivityResultRegistry mit einem FragmentFactory bereit, um die ActivityResultRegistry an den Konstruktor des Fragments zu übergeben.

Beispielsweise kann ein Fragment, das den Vertrag TakePicturePreview verwendet, um eine Miniaturansicht des Bildes zu erhalten, in etwa so geschrieben werden:

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

    // ...
}

Wenn Sie eine testspezifische ActivityResultRegistry erstellen, müssen Sie die Methode onLaunch() implementieren. Anstatt startActivityForResult() aufzurufen, kann Ihre Testimplementierung dispatchResult() direkt aufrufen und so genau die Ergebnisse liefern, die Sie in Ihrem Test verwenden möchten:

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

Der vollständige Test erstellt das erwartete Ergebnis, erstellt einen Test-ActivityResultRegistry, übergibt ihn an das Fragment, löst den Launcher entweder direkt oder mithilfe anderer Test-APIs wie Espresso aus und verifiziert dann die Ergebnisse:

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

Benutzerdefinierten Vertrag erstellen

ActivityResultContracts enthält eine Reihe vordefinierter ActivityResultContract-Klassen. Sie können aber auch eigene Verträge bereitstellen, um die benötigte typsichere API bereitzustellen.

Für jeden ActivityResultContract sind definierte Eingabe- und Ausgabeklassen erforderlich. Wenn keine Eingabe erforderlich ist, verwenden Sie Void als Eingabetyp. Verwenden Sie in Kotlin entweder Void? oder Unit.

In jedem Vertrag muss die Methode createIntent() implementiert werden, die ein Context und die Eingabe verwendet und die Intent erstellt, die mit startActivityForResult() verwendet wird.

In jedem Vertrag muss auch parseResult() implementiert werden, das die Ausgabe aus dem angegebenen resultCode, z. B. Activity.RESULT_OK oder Activity.RESULT_CANCELED, und dem Intent-Element erstellt.

In Verträgen kann optional getSynchronousResult() implementiert werden, wenn das Ergebnis für eine bestimmte Eingabe ermittelt werden kann, ohne createIntent() aufzurufen, die andere Aktivität zu starten und parseResult() zum Erstellen des Ergebnisses zu verwenden.

Das folgende Beispiel zeigt, wie ein ActivityResultContract erstellt wird:

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

Wenn Sie keinen benutzerdefinierten Vertrag benötigen, können Sie den StartActivityForResult-Vertrag verwenden. Dies ist ein generischer Vertrag, bei dem jedes Intent als Eingabe verwendet und ein ActivityResult zurückgegeben wird. So können Sie resultCode und Intent als Teil Ihres Callbacks extrahieren, wie im folgenden Beispiel gezeigt:

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