アクティビティの結果を取得する

別のアクティビティを開始する場合、アプリ内のアクティビティか別のアプリのアクティビティかに関係なく、一方向の操作である必要はありません。アクティビティを開始して、結果を受け取ることもできます。たとえば、アプリからカメラアプリを起動し、結果として撮影した写真を受け取ることができます。または、ユーザーが連絡先アプリを起動して連絡先を選択し、結果として連絡先の詳細を受け取るようにすることもできます。

基盤となる startActivityForResult() API と onActivityResult() API は、あらゆる API レベルの Activity クラスで使用できますが、AndroidX Activity クラスと Fragment クラスで導入された Activity Result API を使用することを強くおすすめします。

Activity Result API には、結果を登録するためのコンポーネントが用意されています。 結果を生成するアクティビティを起動し、結果が生成されたら システムによってディスパッチされます。

アクティビティの結果に対してコールバックを登録する

アクティビティを開始して結果を取得する場合、メモリ不足が原因でプロセスやアクティビティが破棄される可能性があります(カメラの使用など、メモリを大量に消費する処理の場合は、ほぼ確実に破棄されます)。

そのため、Activity Result API では、他のアクティビティを開始するコードの場所から結果のコールバックを切り離しています。プロセスやアクティビティが再作成されたときに結果のコールバックを使用できる必要があるため、他のアクティビティを開始するロジックがユーザー入力やその他のビジネス ロジックに基づいてのみ実行される場合でも、アクティビティが作成されるたびにコールバックを無条件で登録する必要があります。

ComponentActivity または Fragment の Activity Result API には、結果のコールバックを登録するための registerForActivityResult() が用意されています。registerForActivityResult()ActivityResultContractActivityResultCallback を受け取って、他のアクティビティを開始するために使用する ActivityResultLauncher を返します。

ActivityResultContract では、結果の生成に必要な入力タイプと結果の出力タイプを定義します。API には、写真の撮影や権限のリクエストなどの基本的なインテント アクション用のデフォルトのコントラクトが用意されています。カスタム コントラクトを作成することもできます。

ActivityResultCallback は、ActivityResultContract で定義した出力タイプのオブジェクトを受け取る onActivityResult() メソッドを含むシングル メソッド インターフェースです。

KotlinJava
val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
   
// Handle the returned Uri
}
// 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
       
}
});

アクティビティの結果を複数回呼び出す際に、それぞれ別々のコントラクトを使用するか、個別のコールバックが必要な場合、registerForActivityResult() を複数回呼び出して、複数の ActivityResultLauncher インスタンスを登録できます。配信中の結果が適切なコールバックに提供されるようにするには、フラグメントまたはアクティビティが作成されるたびに registerForActivityResult() を同じ順序で呼び出す必要があります。

registerForActivityResult() は、フラグメントまたはアクティビティが作成される前に安全に呼び出すことができ、返される ActivityResultLauncher インスタンスのメンバー変数を宣言する際に直接使用できます。

結果に対してアクティビティを起動する

registerForActivityResult() はコールバックの登録は行いますが、他のアクティビティは起動せず、結果に対するリクエストも開始しません。これらは、返された ActivityResultLauncher インスタンスが行います。

入力が存在する場合、ランチャーは ActivityResultContract のタイプと一致する入力を受け取ります。launch() を呼び出すと、結果の生成プロセスが開始されます。ユーザーがその後のアクティビティを完了して復帰すると、次の例に示すように、ActivityResultCallbackonActivityResult() が実行されます。

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

launch() のオーバーロードされたバージョンを使用すると、入力に加えて ActivityOptionsCompat も渡すことができます。

アクティビティの結果を別のクラスで受け取る

ComponentActivity クラスと Fragment クラスに実装されている ActivityResultCaller インターフェースで registerForActivityResult() API を使用できますが、ActivityResultCaller を実装していない別のクラスで ActivityResultRegistry を直接使用してアクティビティの結果を受け取ることもできます。

たとえば、コントラクトの登録とランチャーの起動を処理する LifecycleObserver を実装できます。

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

ActivityResultRegistry API を使用する場合は、LifecycleOwner を受け取る API を使用することを強くおすすめします。LifecycleOwner は、Lifecycle が破棄されると、登録済みのランチャーを自動的に削除します。ただし、LifecycleOwner を使用できない場合は、代わりに各 ActivityResultLauncher クラスを使用して unregister() を手動で呼び出すことができます。

テスト

registerForActivityResult() はデフォルトで、アクティビティから提供される ActivityResultRegistry を自動的に使用します。また、独自の ActivityResultRegistry インスタンスを渡すことができるオーバーロードを提供します。このインスタンスを使用すると、別のアクティビティを実際に起動せずにアクティビティの結果の呼び出しをテストできます。

アプリのフラグメントをテストする際にテスト用の ActivityResultRegistry を提供するには、FragmentFactory を使用してフラグメントのコンストラクタに ActivityResultRegistry を渡します。

たとえば、TakePicturePreview コントラクトを使用して画像のサムネイルを取得するフラグメントは、次のように記述できます。

KotlinJava
class MyFragment(
   
private val registry: ActivityResultRegistry
) : Fragment() {
   
val thumbnailLiveData = MutableLiveData<Bitmap?>

   
val takePicture = registerForActivityResult(TakePicturePreview(), registry) {
        bitmap
: Bitmap? -> thumbnailLiveData.setValue(bitmap)
   
}

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

   
// ...
}

テスト固有の ActivityResultRegistry を作成する場合は、onLaunch() メソッドを実装する必要があります。startActivityForResult() を呼び出す代わりに、テストの実装で dispatchResult() を直接呼び出して、テストで使用する正確な結果を提供することもできます。

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

完全なテストでは、期待される結果が作成され、ActivityResultRegistry テストが作成されてフラグメントに渡され、直接、または Espresso などの他のテスト API を使用してランチャーがトリガーされ、結果が検証されます。

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

カスタム コントラクトを作成する

ActivityResultContracts に含まれている事前ビルド済みのいくつかの ActivityResultContract クラスを使用できますが、厳密にタイプセーフな API を必要に応じて提供する独自のコントラクトを作成することもできます。

ActivityResultContract には、定義された入力クラスと出力クラスが必要です。入力が不要な場合は、入力タイプとして Void を使用します(Kotlin では Void? または Unit を使用します)。

各コントラクトでは、createIntent() メソッドを実装する必要があります。このメソッドでは、Context と入力を受け取って、startActivityForResult() で使用する Intent を作成します。

各コントラクトでは parseResult() も実装する必要があります。このメソッドでは、指定された resultCodeActivity.RESULT_OKActivity.RESULT_CANCELEDIntent など)からの出力が生成されます。

createIntent() を呼び出したり、他のアクティビティを開始したり、parseResult() を使用して結果を作成したりせずに、指定された入力に対する結果を特定できる場合は、必要に応じてコントラクトに getSynchronousResult() を実装できます。

次の例は、ActivityResultContract の作成方法を示しています。

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

カスタム コントラクトが不要な場合は、StartActivityForResult コントラクトを使用できます。このコントラクトは、任意の Intent を入力として受け取って ActivityResult を返す汎用コントラクトです。このコントラクトを使用すると、次の例に示すように、コールバックの一部として resultCodeIntent を抽出できます。

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