JetPackにstartActivityForResultというライブラリが新規で追加されました。
正直、最初に見たときは目を疑いましたよ。
startActivityForResultといえばAndroidでActivity間をまたぐ最も基本的なAPIの一つ。
最も初期から存在するAPIの一つであり、そこにメスが入るというのです。
そして、正直な感想としては、あぁたしかに そこ不便だったし便利になるな と言う気持ち。
導入方法
build.gradleにてライブラリを読み込む
build.gradle
implementation "androidx.activity:activity:1.2.0-alpha07"
implementation "androidx.fragment:fragment:1.3.0-alpha07"
appCompatActivityを使っているなら これだけでOK
MainActivityからSecondActivityを起動する場合
registerForActivityResultで結果を受け取ったあとの処理を記載し、
launchにSecondActivityへのIntentを渡すことでSecondActivityが起動されます。
以前はどのActivityからの戻りかをIntのRequestCodeで管理しないといけなかったのですが、それらはライブラリが隠蔽するようになりました。
MainActivity.kt
private val secondActivityLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { intent ->
if(intent.resultCode== RESULT_OK){
// todo: Activityの結果を処理
}
}
fun onCreate(){
// 中略
secondActivityLauncher.launch(Intent(this, SecondActivity::class.java))
}
もっと便利に
正直、上記だけならRequestCodeが不要になった程度でそれほど便利な感じはありません。
startActivityForResultの真骨頂はActivityの受け取り側でActivityのI/Oを明記できるようになることです。
そのためには、カスタムコントラクトを作ります。
カスタムコントラクトでは、呼び出され側のstartActivityとResultのIntentをより具体的な形に変換します。
どこに記載してもいいですが、個人的には呼ばれる側のActivity内に記載するのがわかりやすいように思います。
SecondActivity.kt
// User Id(Int)を受け取り、User Name(String)を返すActivity
class SecondActivity : AppCompatActivity() {
// SecondActivityの中に記載してみた。JavaでいうStatic inner classに相当する
// <Inputの型、Outputの型>を記載する。
class ResultContracts : ActivityResultContract<Int, String>() {
// InputからstartActivity用のIntentを作る
override fun createIntent(context: Context, input: Int): Inten
= Intent(context, SecondActivity::class.java).apply {
putExtra(INPUT_USER_ID, input)}
// ResultのIntentからOutputの型を作る
override fun parseResult(resultCode: Int, intent: Intent?): String
= intent?.getStringExtra(RESULT_USER_NAME) ?: ""
}
fun onCreate(){
// intentの受け取り方は今まで通り
intent.getIntExtra(INPUT_USER_ID, 0)
// 中略
}
private fun resultValue(){
// 呼び出され側はResultのIntentを今まで通りsetResultで返す。
intent.putExtra(RESULT_USER_NAME, editText.text.toString())
setResult(RESULT_OK, intent)
finish()
}
companion object {
// IntetにおけるEXTRA用のKEYを定義する。
// このファイル内に閉じるのでprivateにできることに注目 やったね
private const val INPUT_USER_ID = "USER_ID"
private const val RESULT_USER_NAME = "USER_NAME"
}
}
呼び出し側は次のように記載できます。
MainActivity.kt
class MainActivity : AppCompatActivity() {
// registerForActivityResultに SecondActivityで記載したカスタムActivityResultContractのインスタンスを渡す。
// カスタムActivityResultContractにはどのActivityを実行し、
// 入出力の型が明記されているためMainActivityではそのことを意識する必要がなくなる。
private val activityLauncher =
registerForActivityResult(SecondActivity.ResultContracts()) { userName ->
// Activityからの戻り値をStringで受け取れる
}
override fun onCreate(savedInstanceState: Bundle?) {
// 中略
// SecondActivityをIntを渡して実行できる
activityLauncher.launch(10)
}
}
呼び出し元であるMainActivityからSecondActivityの実装にまつわる情報を省くことが出来てかなり保守性が高まるように思います。
startActivityForResultは上記に加えて、Permissionの取得のようなよくあるIntentに対してデフォルトのコントラクトが用意されているなど、便利な機能がまだたくさんあるようです。
実行可能なコードのサンプルはGithubにあります。