fragment-ktx
androidx.fragment:fragment-ktx
を1.2.5
から1.3.0
に更新した結果(というより、その内部で依存しているandroidx.activity:activity-ktx
が1.2.0
に変更された結果)、Activity
やFragment
の扱い方に関する幾つかの変更が発生した。
今回の内容もそのひとつであり、「onActivityResult
が非推奨になったのでActivityResultLauncher
使ってくれや」という問題に対応する。
他に対応したこと
(いまさら)ViewPagerからViewPager2へ移行する
startActivityForResult + onActivityResult
「他のActivity
に遷移して行った操作の結果を遷移前のActivity
やFragment
で受け取る」ということがしたい場合、
これまではstartActivityForResult(intent)
で画面遷移し、onActivityResult(requestCode, resultCode, intent)
をオーバーライドして結果を受け取って処理する、というようにしていた。
class HogeActivity : AppCompatActivity {
// ...
/** リクエストコード */
enum class RequestCode {
FOO,
BAR,
BAZ
}
// ------ //
fun startPiyoActivity() {
val intent = Intent(this, PiyoActivity::class.java)
startActivityForResult(intent, RequestCode.FOO.ordinal)
}
// ------ //
/** 結果を受け取る */
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode != RESULT_OK) {
// failed
}
when (requestCode) {
RequestCode.FOO.ordinal -> {
val msg = data?.getStringExtra(PiyoActivity.ResultExtra.MESSAGE.name)!!
Log.i("piyo result", msg)
}
else -> ...
}
}
}
class PiyoActivity : AppCompatActivity() {
// ...
enum class ResultExtra {
MESSAGE
}
fun finishActivity() {
val intent = Intent().apply {
putExtra(ResultExtra.MESSAGE.name, "piyo")
}
setResult(RESULT_OK, intent)
finish()
}
}
ActivityResultLauncher
これからはActivityResultLauncher
を使用した方法が推奨となる。
上記コードをひとまず機械的に置換した例が次になる。
class HogeActivity : AppCompatActivity() {
// ...
/**
* Intentを開始するためのランチャ
* STARTEDライフサイクル前に作成する
*/
private val launcher = registerForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result -> // 結果を受け取る関数
if (result.resultCode != RESULT_OK) {
// failed
}
val msg = result.data?.getStringExtra(PiyoActivity.ResultExtra.MESSAGE.name)!!
Log.i("piyo result", msg)
}
// ------ //
fun startPiyoActivity() {
val intent = Intent(this, PiyoActivity::class.java)
launcher.launch(intent)
}
}
なお、公式のドキュメント
アクティビティからの結果の取得 | Android デベロッパー | Android Developers
ではprepareCall()
を使用しているが、どうも情報が古いまま掲載されているようで、現時点(2021-02-25)ではこれは上記のようにregisterForActivityResult()
を使用するように変更されている。
ActivityResultContract<I,O>
要するに「何(I)を渡してIntent
を呼んで、何の結果(O)を得るか」を表している。
ActivityResultContracts.StartActivityForResult
の場合は、ActivityResultContract<Intent, ActivityResult>
を継承しており、「任意のIntent
を渡して画面遷移をし、結果をActivityResult
で受け取る」ということになる。
他にも基本的なIO用のコントラクトは用意されているので、用途に応じて選択する。
ActivityResultContracts | Android デベロッパー | Android Developers
ActivityResultContract<I,O>
を継承して独自のコントラクトを作成することもできる。
カスタム コントラクトを作成する | アクティビティからの結果の取得 | Android デベロッパー | Android Developers
当記事で使用したActivityResultContracts.StartActivityForResult
はあまり推奨されなくて、用途ごとに適切なデフォルトコントラクトやカスタムコントラクトを作成・選択していくべきなのかな、と感じた。
呼び出しについて
registerForActivityResult
はComponentActivity
やFragment
に生えている。
そのライフサイクルがSTARTED
になる前であれば新しくランチャを登録することができ、CREATED
以降であればランチャを実行することができる。
また、結果を別クラスで受け取ることも可能で、公式ドキュメントではActivityResultRegistry
を渡したLifecycleObserver
を作ってオブザーバ側で結果を受け取る例が紹介されている。
class MyLifecycleObserver(private val registry : ActivityResultRegistry)
: DefaultLifecycleObserver {
lateinit var getContent : ActivityResultLauncher<String>
fun onCreate(owner: LifecycleOwner) {
getContent = registry.register("key", owner, GetContent()) { uri ->
// Handle the returned Uri
}
}
fun selectImage() {
getContent("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()
}
}
}