LoginSignup
24
13

More than 3 years have passed since last update.

ActivityResultContractを使ってonActivityResultに別れを告げよう

Posted at

はじめに

皆さんはonActivityResultはお好きですか?
requestCoderesultCodeの字面が似ていて間違えやすかったり、ifwhenによってコードの見通しが悪くなりやすいので個人的にはあまり好きではありません。
そんな悩みを解決してくれるActivityResultContractが最近発表されたktxのアップデート(2020/04/12時点ではα版)に来たようなので纏めておきます。

検証

検証用に2画面だけの雑なアプリを作りました。
activities.png

Before

MainActivityはボタンを押すとstartActivityForResultSecondActivityを起動し、SecondActivityが終了されるとonActivityResultで結果を受け取ります。

MainActivity.kt
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扱いになります。

SecondActivity.kt
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の方はコメントアウトしています。)

app/build.gradle
    implementation "androidx.activity:activity-ktx:1.2.0-alpha03"
    // implementation "androidx.fragment:fragment-ktx:1.3.0-alpha03"

MainActivityはこの様に書き換えることが出来ました。
ActivityResultContract自体は抽象クラスで、ここでは継承したStartActivityForResultを使っています。
onActivityResultだけでなくREQUEST_CODEも不要になり、かなりスッキリした記述になります。

MainActivity.kt
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
image.png

その他のユースケースについて

先程も触れましたがActivityResultContractは抽象クラスであり、継承したいくつかのクラスが用意されています。
参考:https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContract

例えばファイル一覧から画像を取得する場合ならば従来は下記のようなコードが必要でしたが

MainActivity.kt
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を用いると下記のようにシンプルに書くことができるようになります。

MainActivity.kt
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

24
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
13