ActivityResultLauncherはAndroid Activity Result APIのコンポーネントであり、androidx.activityライブラリのActivity 1.2.0で導入されました。これは、Android SDKの初期から存在していた非推奨のstartActivityForResultおよびonActivityResultメソッドを置き換えます。Android Developers (2024)によると、新しいAPIはActivityとの密結合や型安全性の欠如といった問題を解消します。ActivityResultLauncherは事前に登録され、Contractを使用して入出力データの厳密な型付けを行います。
主なポイント
ActivityResultLauncherはandroidx.activity.resultパッケージのクラスで、Activityを起動して結果を受け取るための型安全なメカニズムを提供します。ランチャーはregisterForActivityResultメソッドを通じて作成され、Contract(入出力タイプを記述)とActivityResultCallback(結果ハンドラ)の2つのパラメータを受け取ります。登録後、ランチャーはlaunchメソッドを介して呼び出し可能になります。
古いAPIとの主な違いは、登録と起動の分離です。登録は初期化段階(Activity.onCreateまたはFragment.onCreate)で実行され、コールバックはランチャーに一度だけバインドされ、結果が返ってきたときに確実に実行されます。これにより、onActivityResultが予期しない順序で呼び出されたり、破棄されたActivityで呼び出されたりする問題が解消されます。
ActivityResultLauncherは、以前onActivityResultを介して処理されていたすべてのシナリオをサポートします:カメラの起動、ギャラリー、連絡先の要求、権限、カスタムActivity。さらに、APIは拡張可能で、開発者はActivity間の特定のデータ交換シナリオ向けにカスタムContractを作成できます。
startActivityForResultはAPI Level 1(2008)からAndroid SDKの一部であり、12年以上にわたってActivityから結果を取得する主要な方法でした。しかし、このメソッドには根本的な欠点があり、GoogleはActivity Result APIでそれらを解決しました。主な問題と新しいAPIがどのように解決するかを見てみましょう。
startActivityForResultメソッドは、requestCode(onActivityResultに渡される任意の整数)を介してActivityおよびFragmentに結び付けられていました。開発者は手動でコードを起動した操作と照合する必要があり、コードの再利用や継承でエラーが発生していました。ActivityResultLauncherはrequestCodeを完全に排除します。コールバックは登録時に特定のランチャーにバインドされ、そのランチャーに対してのみ呼び出されます。
構成変更(画面の回転、言語の変更)時、Activityは再作成され、onActivityResultが実行されない可能性がありました — コールバックが失われていました。Activity Result APIはSavedStateRegistryを介してランチャーの状態を自動的に保存および復元し、Activityの再作成後でも結果が確実に受信されるようにします。
古いAPIは、キーとデータ型がコンパイラによってチェックされないBundleを伴うIntentを介して結果を渡していました。Activity Result APIはContract(入力データ型(I)と結果型(O)を定義するジェネリックインターフェース)を使用します。型の不一致エラーは実行時ではなくコンパイル時に検出されます。
| 特徴 | startActivityForResult | ActivityResultLauncher |
|---|---|---|
| RequestCode | 手動管理が必要 | 自動、不要 |
| 型安全性 | なし | ジェネリックContract |
| 回転時の保存 | 失われる | SavedStateRegistry |
| 最小API | API Level 1 | Activity 1.2.0 |
| Composeでの使用 | 非対応 | rememberLauncherForActivityResult |
ContractはActivityResultContract<I, O>インターフェースであり、Activityを起動する方法と結果を解釈する方法を定義します。Googleは、ほとんどの開発者のニーズをカバーする一般的なシナリオ向けの組み込みコントラクトのセットを提供しています。
StartIntentSenderForResult — IntentSenderを起動するための基本コントラクト。システムシナリオで使用され、例えばGoogle Sign-Inによる認証やGoogle Payによる支払い時に使用されます。入力パラメータはPendingIntent、出力はコードとIntentを含むActivityResultです。
RequestMultiplePermissions — Android 6.0+で複数の権限を同時に要求するためのコントラクト。入力パラメータは権限名のString配列、出力は各要求の結果を含むMap<String, Boolean>です。以前は、onRequestPermissionsResultで要求コードを照合しながら手動で解析する必要がありました。
TakePicture — システムカメラを介して写真を撮影するためのコントラクト。入力は画像を保存するUri、出力はBoolean(成功)です。TakeVideoはビデオでも同様に機能します。これらのコントラクトは、デバイスによって動作が不安定な非推奨のMediaStore.ACTION_IMAGE_CAPTUREを置き換えます。
GetContent — システムピッカーを介してコンテンツを選択するためのコントラクト。入力はMIMEタイプ(例:image/*)、出力は選択されたファイルのUriです。OpenDocumentは複数選択とドキュメントタイプによるフィルタリングをサポートする点が異なります。どちらのコントラクトもSAF(Storage Access Framework)を介して動作します。
CreateDocument — システムダイアログを介して新しいドキュメントを作成するためのコントラクト。ユーザーが名前とフォルダを選択し、システムが書き込み用のUriを返します。OpenDocumentTreeはディレクトリ全体へのアクセスを提供します — ユーザーがフォルダを選択すると、アプリは内部のすべてのファイルを読み書きするためのtree-uriを受け取ります。
クラシックAndroidでActivityResultLauncherを使用する基本パターンは、初期化時にregisterForActivityResultを介した登録と、ユーザーアクションに応答したlaunchの呼び出しの2つのステップで構成されます。ギャラリーから画像を選択する典型的な例を見てみましょう。
ActivityのonCreateでランチャーを登録します — これにより、可能性のある呼び出しの前にコールバックが準備されていることが保証されます。起動の直前にランチャーを登録しないでください — APIコントラクトに違反し、Activityの再作成時に結果が失われる可能性があります。
class MainActivity : AppCompatActivity() {
private val pickImageLauncher =
registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
uri?.let { binding.imageView.setImageURI(it) }
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
pickImageLauncher.launch("image/*")
}
}
Fragmentでは、onCreate、onAttach、またはonCreateViewでの初期化時に登録が行われます。FragmentActivityは親Activityを介してランチャーを渡すため、結果はActivityではなくFragment内で処理されます。これにより、すべてのFragmentからのすべての結果が1つのActivityメソッドに集約されていたonActivityResultと比較して、カプセル化が向上します。
class ProfileFragment : Fragment() {
private val cameraLauncher =
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) { updateProfilePhoto() }
}
fun takePhoto(photoUri: Uri) {
cameraLauncher.launch(photoUri)
}
}
Jetpack Composeは、Activity Result APIのための特別なcomposable関数 — rememberLauncherForActivityResultを提供します。従来のアプローチとは異なり、Composeではランチャーはrememberを介してcomposableのライフサイクルにバインドされたオブジェクトとして作成されます。これにより、ActivityやFragmentに直接アクセスすることなく、宣言的スタイルでActivity Result APIを完全に使用できます。
rememberLauncherForActivityResultはContractとコールバックを受け取り、ActivityResultLauncherを返します。ランチャーは再コンポジション中に保持され、コンポジションを離れると自動的にクリアされます。launchの呼び出しは、イベント(ボタンクリックや状態変更など)に応答して発生します。
@Composable
fun PhotoPicker() {
val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent()
) { uri -> handleImage(uri) }
Button(onClick = { launcher.launch("image/*") }) {
Text("画像を選択")
}
}
Composeでの権限要求も、RequestPermissionまたはRequestMultiplePermissionsコントラクトを使用してrememberLauncherForActivityResultを介して行われます。Googleはaccompanist-permissionsの使用を推奨していますが、内部ではこれもActivity Result APIを使用しています。権限の状態を追跡するには、rememberまたはViewModelに状態を保存すると便利です。
Activity Result APIは古いアプローチの多くの問題を解消しましたが、不適切な使用は新しい種類のエラーにつながる可能性があります。最も一般的な問題とその回避方法を見てみましょう。
ランチャーの登録はコンポーネントの初期化時(ActivityのonCreateまたはFragmentの初期化子)に行う必要があります。ラムダ、コールバック、またはコルーチン内でランチャーを登録すると、Activityの再作成時に登録が再度実行され、古いランチャーが結果との接続を失う可能性があります。
各ランチャーは状態保存のための一意のキーを受け取ります。1つのコンポーネントに同じContractを持つ2つのランチャーを登録すると、SavedStateRegistryが一方の状態を他方で上書きする可能性があります。Android StudioはlintルールUnnecessaryRegisterForActivityResultを介してこれを警告しますが、一意性は手動で制御することをお勧めします。
ユーザーはアクションをキャンセルする可能性があります — システムの戻るボタンを押す、アプリを最小化する、または別のアプリに切り替える。この場合、コールバックはnullまたはRESULT_CANCELEDを含むActivityResultを受け取ります。NullPointerExceptionを回避するために、使用前に結果がnullでないか常に確認してください。
アプリが頻繁に同様のシナリオを起動する場合 — 例えば、連絡先を選択して名前と電話番号を返す — カスタムContractを作成します。これによりコードの可読性が向上し、起動と結果処理のロジックを集中管理できます。
class PickContactContract : ActivityResultContract<Void, ContactData?>() {
override fun createIntent(context: Context, input: Void?) =
Intent(Intent.ACTION_PICK).setType(ContactsContract.Contacts.CONTENT_TYPE)
override fun parseResult(resultCode: Int, intent: Intent?) =
intent?.data?.let { queryContact(it) }
}
よくある質問
いいえ — ActivityResultLauncherは登録にActivityまたはFragmentのコンテキストが必要です。ViewModelは状態の保存のみに使用し、ランチャーはActivityまたはFragmentで作成して結果をViewModelに渡してください。
Activity Result APIはライブラリactivity-ktx 1.2.0以降で利用可能です。最小SDKはAPI Level 14(Android 4.0)ですが、ほとんどのコントラクトはAPI Level 19+でのみ動作します。
最初の操作が完了する前の繰り返しのlaunch呼び出しは無視されます。Activity Result APIは並行起動をサポートしていません — 新しい呼び出しの前に最初の操作からのコールバックを待機してください。
移行は、startActivityForResultの呼び出しを適切なContractを持つregisterForActivityResultに置き換えることで行います。onActivityResultを削除し、ランチャーのコールバックで結果を処理します。GoogleはAndroid Developersドキュメントで移行ガイドを提供しています。
はい、多くのライブラリがActivityResultContractsを介した統合をサポートしています。例えば、ML Kit Barcode Scannerはスキャナーを起動するためにStartIntentSenderForResultを使用します。特定のライブラリのドキュメントを確認してください。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。