別のアクティビティを開始する場合、アプリ内のアクティビティか別のアプリのアクティビティかに関係なく、一方向の操作である必要はありません。アクティビティを開始して、結果を受け取ることもできます。たとえば、アプリからカメラアプリを起動し、結果として撮影した写真を受け取ることができます。または、ユーザーが連絡先アプリを起動して連絡先を選択し、結果として連絡先の詳細を受け取るようにすることもできます。
基盤となる 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()
は ActivityResultContract
と ActivityResultCallback
を受け取って、他のアクティビティを開始するために使用する ActivityResultLauncher
を返します。
ActivityResultContract
では、結果の生成に必要な入力タイプと結果の出力タイプを定義します。API には、写真の撮影や権限のリクエストなどの基本的なインテント アクション用のデフォルトのコントラクトが用意されています。カスタム コントラクトを作成することもできます。
ActivityResultCallback
は、ActivityResultContract
で定義した出力タイプのオブジェクトを受け取る onActivityResult()
メソッドを含むシングル メソッド インターフェースです。
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()
を呼び出すと、結果の生成プロセスが開始されます。ユーザーがその後のアクティビティを完了して復帰すると、次の例に示すように、ActivityResultCallback
の onActivityResult()
が実行されます。
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
を実装できます。
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
コントラクトを使用して画像のサムネイルを取得するフラグメントは、次のように記述できます。
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()
も実装する必要があります。このメソッドでは、指定された resultCode
(Activity.RESULT_OK
、Activity.RESULT_CANCELED
、Intent
など)からの出力が生成されます。
createIntent()
を呼び出したり、他のアクティビティを開始したり、parseResult()
を使用して結果を作成したりせずに、指定された入力に対する結果を特定できる場合は、必要に応じてコントラクトに getSynchronousResult()
を実装できます。
次の例は、ActivityResultContract
の作成方法を示しています。
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
を返す汎用コントラクトです。このコントラクトを使用すると、次の例に示すように、コールバックの一部として resultCode
と Intent
を抽出できます。
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));
}
});
}