はじめに
皆さんはonActivityResult
はお好きですか?
requestCode
とresultCode
の字面が似ていて間違えやすかったり、if
やwhen
によってコードの見通しが悪くなりやすいので個人的にはあまり好きではありません。
そんな悩みを解決してくれるActivityResultContract
が最近発表されたktxのアップデート(2020/04/12時点ではα版)に来たようなので纏めておきます。
検証
Before
MainActivity
はボタンを押すとstartActivityForResult
でSecondActivity
を起動し、SecondActivity
が終了されるとonActivityResult
で結果を受け取ります。
class MainActivity : AppCompatActivity() {
companion object {
private const val REQUEST_CODE = 1234
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button_open.setOnClickListener {
startActivityForResult(
SecondActivity.createIntent(this),
REQUEST_CODE
)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
Log.d("MainActivity", "requestCode: $requestCode, resultCode: $resultCode, data: $data")
}
}
SecondActivity
はボタンを押すとRESULT_OK
をセットして自身を終了します。
バックキー押下で閉じた場合にはRESULT_CANCELED
扱いになります。
class SecondActivity : AppCompatActivity() {
companion object {
fun createIntent(context: Context) =
Intent(context, SecondActivity::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
button_close.setOnClickListener {
setResult(Activity.RESULT_OK, Intent().putExtra("data", "hoge"))
finish()
}
}
}
ログ出力はこんな感じになりました。
# バックキーで閉じた
2020-04-13 03:22:27.578 28835-28835/com.masaibar.activityresultcontractsample D/MainActivity: requestCode: 1234, resultCode: 0, data: null
# ボタンで閉じた
2020-04-13 03:22:34.206 28835-28835/com.masaibar.activityresultcontractsample D/MainActivity: requestCode: 1234, resultCode: -1, data: Intent { (has extras) }
After
ここからMainActivity
を書き換えていきます。
SecondActivity
には変更はありません。
app/build.gradle
にライブラリを追加します。
(今回はActivityでの検証なのでFragmentの方はコメントアウトしています。)
implementation "androidx.activity:activity-ktx:1.2.0-alpha03"
// implementation "androidx.fragment:fragment-ktx:1.3.0-alpha03"
MainActivity
はこの様に書き換えることが出来ました。
ActivityResultContract
自体は抽象クラスで、ここでは継承したStartActivityForResultを使っています。
onActivityResult
だけでなくREQUEST_CODE
も不要になり、かなりスッキリした記述になります。
class MainActivity : AppCompatActivity() {
private val launcher: ActivityResultLauncher<Intent> =
prepareCall(ActivityResultContracts.StartActivityForResult()) { activityResult ->
Log.d("MainActivity", activityResult.toString())
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button_open.setOnClickListener {
launcher.launch(SecondActivity.createIntent(this))
}
}
}
ログ出力は下記のようになりました。
# バックキーで閉じた
2020-04-13 03:24:01.444 29039-29039/com.masaibar.activityresultcontractsample D/MainActivity: ActivityResult{resultCode=RESULT_CANCELED, data=null}
# ボタンで閉じた
2020-04-13 03:24:31.886 29039-29039/com.masaibar.activityresultcontractsample D/MainActivity: ActivityResult{resultCode=RESULT_OK, data=Intent { (has extras) }}
コード差分も置いておきます。
https://github.com/masaibar/ActivityResultContractSample/pull/1/files
##その他のユースケースについて
先程も触れましたがActivityResultContract
は抽象クラスであり、継承したいくつかのクラスが用意されています。
参考:https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContract
例えばファイル一覧から画像を取得する場合ならば従来は下記のようなコードが必要でしたが
class MainActivity : AppCompatActivity() {
companion object {
private const val REQUEST_CODE_CHOOSER = 1234
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button_get_content.setOnClickListener {
startActivityForResult(
Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "image/*"
},
REQUEST_CODE_CHOOSER
)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_CHOOSER && resultCode == Activity.RESULT_OK) {
Log.d("MainActivity", "uri: ${data?.data}")
}
}
}
ActivityResultContracts.GetContent
を用いると下記のようにシンプルに書くことができるようになります。
class MainActivity : AppCompatActivity() {
private val launcher = prepareCall(ActivityResultContracts.GetContent()) { uri ->
Log.d("MainActivity", "uri: $uri")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button_get_content.setOnClickListener {
launcher.launch("image/*")
}
}
}
現在ライブラリ側に定義されているのは下記の13種類です。
ぱっと見た感じファイル取得以外だと撮影系や、パーミッションに関連するものがありますね。
もしお目当てのクラスがここに無かった場合でも自身でActivityResultContractsの継承クラスを実装することが可能です。
ActivityResultContracts.CreateDocument
ActivityResultContracts.GetContent
ActivityResultContracts.GetContents
ActivityResultContracts.OpenDocument
ActivityResultContracts.OpenDocumentTree
ActivityResultContracts.OpenDocuments
ActivityResultContracts.PickContact
ActivityResultContracts.RequestPermission
ActivityResultContracts.RequestPermissions
ActivityResultContracts.StartActivityForResult
ActivityResultContracts.TakePicture
ActivityResultContracts.TakePicturePreview
ActivityResultContracts.TakeVideo
おわりに
ActivityResultContracts
の原理などはtakahiromさんのActivityResultContractのコードを少し読んでみるが詳しいです。
まだα版ということで心理的に導入しづらいかも知れませんが、コードの見通しがかなりスッキリするためonActivityResult
と別れたい方は検討する価値があるのではないでしょうか。
願わくば早く正式版としてアップデートが来ますように🙏
参考
https://developer.android.com/training/basics/intents/result
https://qiita.com/takahirom/items/658ebabea3dc8c1c5a6c