startActivityForResult()を新APIに置き換える
ActivityResultLauncherの基本的な使い方
Created at Updated at

1696 Words
⚠️

fragment-ktx

androidx.fragment:fragment-ktx1.2.5から1.3.0に更新した結果(というより、その内部で依存しているandroidx.activity:activity-ktx1.2.0に変更された結果)、ActivityFragmentの扱い方に関する幾つかの変更が発生した。

今回の内容もそのひとつであり、「onActivityResultが非推奨になったのでActivityResultLauncher使ってくれや」という問題に対応する。

他に対応したこと

(いまさら)ViewPagerからViewPager2へ移行する


startActivityForResult + onActivityResult

「他のActivityに遷移して行った操作の結果を遷移前のActivityFragmentで受け取る」ということがしたい場合、
これまでは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はあまり推奨されなくて、用途ごとに適切なデフォルトコントラクトやカスタムコントラクトを作成・選択していくべきなのかな、と感じた。

呼び出しについて

registerForActivityResultComponentActivityFragmentに生えている。

そのライフサイクルが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()
        }
    }
}

See Also