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

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

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

Activity Result API には、システムから結果がディスパッチされた後の結果の登録、結果に対するアクティビティの起動、結果の処理を行うためのコンポーネントが用意されています。

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

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

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

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

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

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

Kotlin

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

Java

// GetContent creates an ActivityResultLauncher<String> to allow you to pass
// in the mime type you'd like to allow the user to 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() が実行されます。

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'd like to allow the user to 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 savedInstanceState: Bundle) {
    // ...

    Button selectButton = findViewById(R.id.select_button);

    selectButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            // Pass in the mime type you'd like to allow the user to select
            // as the input
            mGetContent.launch("image/*");
        }
    });
}

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

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

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

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

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

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

テスト

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

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

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

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

    // ...
}

テスト固有の 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_CANCELED など)と Intent から出力を生成します。

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

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

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

Kotlin

val startForResult = registerForActivityResult(StartActivityForResult()) { result: ActivityResult ->
    if (result.resultCode == Activity.RESULT_OK) {
        val intent = result.intent
        // 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.getIntent();
            // 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));
        }
    });
}