Android XはstartActivity ForResult()の代わりにActivity Result APIを使用します.

25718 ワード

Android XはstartActivity ForResult()の代わりにActivity Result APIを使用します.
別のActivity(アプリケーションのActivityまたは他のアプリケーションのActivity)を起動することは、必ずしも一方的な操作ではありません.また、別のActivityを起動して、戻ってきた結果を受信することもできます.たとえば、カメラアプリケーションを起動して、撮影した写真を受信することもできます.または、「連絡先」を起動することもできます.ユーザーが連絡先を選択し、連絡先の詳細を結果として受信するように適用します.
すべてのAPIレベルのActivityクラスには、最下位のstartActivity ForResult()およびonActivity Result()APIがありますが、AndroidX Activity 1.2を使用することを強くお勧めします.0-alpha 02とFragment 1.3.0-alpha 02に導入されたActivity Result API.
Activity Result APIは、登録結果、起動結果、およびシステムが結果を割り当てた後に処理するためのコンポーネントを提供する.
Activity Resultの取得の準備
Activityを起動して結果を取得すると、メモリ不足でプロセスとActivityが破棄される場合があります.カメラなどのメモリが密集している場合は、ほぼ確実です.
したがって、Activity Result APIは、前に別のActivityを起動したコードの場所から結果コールバックを分離します.プロセスとActivityを再作成するときに結果コールバックを使用する必要があるため、Activityを作成するたびに、別のActivityを起動するロジックがユーザー入力または他のビジネスロジックに基づいている場合でも、コールバックを無条件に登録する必要があります.
ComponentActivityまたはFragmentに存在する場合、Activity Result APIは結果コールバックを登録するためのprepareCall()APIを提供します.prepareCall()は、Activity ResultContractとActivity ResultCallbackをパラメータとして受け入れ、別のActivityを起動するためにActivity ResultLauncherを返します.
Activity ResultContractは、結果を生成するために必要な入力タイプと結果の出力タイプを定義します.これらのAPIは、写真撮影やリクエスト権限などの基本的なintent操作にデフォルトの協定を提供することができる.また、独自のカスタム協定を作成することもできます.
Activity ResultCallbackは単一のメソッドインタフェースであり、onActivity Result()メソッドがあり、Activity ResultContractで定義された出力タイプのオブジェクトを受け入れることができます.
    val getContent = prepareCall(GetContent()) { uri: Uri? ->
        // Handle the returned Uri
    }  

異なる協定を使用している場合や、個別にコールバックする必要があるActivity結果呼び出しが複数ある場合は、複数のActivity ResultLauncherインスタンスを準備するためにprepareCall()を複数回呼び出すことができます.FragmentまたはActivityを作成するたびに、生成された結果が正しいコールバックに渡されるようにprepareCall()を同じ順序で呼び出す必要があります.
FragmentまたはActivityの作成が完了する前にprepareCall()を安全に呼び出すことができるため、返されるActivity ResultLauncherインスタンスのメンバー変数を宣言するときに直接使用できます.
注意:prepareCall()はFragmentまたはActivityの作成が完了するまで安全に呼び出されますが、FragmentまたはActivityのLifecycleがCREATED状態になるまでActivity ResultLauncherを起動できません.
Activityを起動してResultを取得
prepareCall()はコールバックを登録しますが、別のActivityを起動して結果要求を発行しません.これらの操作は、返されたActivity ResultLauncherインスタンスが担当します.
入力がある場合、イニシエータはActivity ResultContractのタイプに一致する入力を受け入れます.launch()を呼び出すと、結果を生成するプロセスが開始されます.次の例に示すように、ユーザーが後続のActivityを完了して戻ってくると、Activity ResultCallbackのonActivity Result()が実行されます.
    val getContent = prepareCall(GetContent()) { uri: Uri? ->
        // Handle the returned Uri
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...

        val selectButton = findViewById<Button>(R.id.select_button)

        selectButton.setOnClickListener {
            // Use the Kotlin extension in activity-ktx
            // passing it the mime type you'd like to allow the user to select
            getContent("image/*")
        }
    }
    

入力内容の転送に加えて、launch()のリロードバージョンではActivity OptionsCompatを転送できます.
注:launch()とonActivity Result()コールバックをトリガーする2つの時点の間で、プロセスとActivityが破棄される可能性があるため、処理結果に必要なその他のステータスは、これらのAPIとは別に保存およびリカバリする必要があります.
個々のクラスでActivity結果を受信
ComponentActivityクラスとFragmentクラスでは、Activity ResultCallerインタフェースを実装することでprepareCall()APIを使用できますが、Activity ResultRegistryを直接使用してActivity ResultCallerが実装されていない個別クラスで結果を受信することもできます.
たとえば、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()
            }
        }
    }
    

Activity ResultRegistry APIを使用する場合は、LifecycleOwnerがLifecycleOwnerが破棄されたときに登録されているイニシエータを自動的に削除するため、LifecycleOwnerをパラメータとして受け入れるAPIを使用することを強くお勧めします.ただし、LifecycleOwnerが存在しない場合、各Activity ResultLauncherクラスでは、代替としてunregister()を手動で呼び出すことができます.
テスト
デフォルトでは、prepareCall()はActivityが提供するActivity ResultRegistryを自動的に使用します.また、独自のActivity ResultRegistryインスタンスに転送できるリロードも用意されています.このインスタンスは、別のActivityを実際に起動することなく、Activity結果呼び出しをテストするために使用できます.
適用するFragmentをテストする場合、FragmentFactoryを使用してActivity ResultRegistryをFragmentのコンストラクション関数に入力すると、Activity ResultRegistryをテストできます.
注意:テストに個別のActivity ResultRegistryを注入できるメカニズムは、Activity結果呼び出しをテストするのに十分です.
例えば、TakePicturePreview協定を使用して画像サムネイルを取得するFragmentは、以下のように記述されることがある.
    class MyFragment(
        private val registry: ActivityResultRegistry
    ) : Fragment() {
        val thumbnailLiveData = MutableLiveData<Bitmap?>

        val takePicture = prepareCall(TakePicturePreview(), registry) {
            bitmap: Bitmap? -> thumbnailLiveData.setValue(bitmap)
        }

        // ...
    }
    

テスト専用のActivity ResultRegistryを作成する場合は、invoke()メソッドを実装する必要があります.テストインプリメンテーションは、startActivity ForResult()ではなくdispatchResult()を直接呼び出し、テストで使用する正確な結果を提供します.
val testRegistry = object : ActivityResultRegistry() {
        override fun <I, O> invoke(
                requestCode: Int,
                contract: ActivityResultContract<I, O>,
                input: I,
                options: ActivityOptionsCompat?
        ) {
            dispatchResult(requestCode, expectedResult)
        }
    }

完全なテストでは、予想される結果が得られます.Activity ResultRegistryを構築し、Fragmentに渡し、イニシエータを直接トリガするか、Espressoなどの他のテストAPIを介してイニシエータをトリガし、結果を検証します.
@Test
    fun activityResultTest {
        // Create an expected result Bitmap
        val expectedResult = Bitmap.createBitmap(1, 1, Bitmap.Config.RGBA_F16)

        // Create the test ActivityResultRegistry
        val testRegistry = object : ActivityResultRegistry() {
                override fun <I, O> invoke(
                requestCode: Int,
                contract: ActivityResultContract<I, O>,
                input: I,
                options: ActivityOptionsCompat?
            ) {
                dispatchResult(requestCode, expectedResult)
            }
        }

        // Use the launchFragmentInContainer method that takes a
        // lambda to construct the Fragment with the testRegistry
        with(launchFragmentInContainer { MyFragment(testRegistry) }) {
                onFragment { fragment ->
                    // Trigger the ActivityResultLauncher
                    fragment.takePicture()
                    // Verify the result is set
                    assertThat(fragment.thumbnailLiveData.value)
                            .isSameInstanceAs(expectedResult)
                }
        }
    }
    

カスタム協定の作成
Activity ResultContractsには、事前に構築された使用可能なActivity ResultContractクラスが含まれていますが、独自の協定を使用して、必要な正確なタイプのセキュリティAPIを提供できます.
各Activity ResultContractでは、入力クラスと出力クラスを定義する必要があります.入力を必要としない場合は、入力タイプとしてVoid(KotlinではVoid?またはUnit)を使用します.
各プロトコルは、Contextと入力内容をパラメータとして受け入れ、startActivity ForResult()と組み合わせて使用するIntentを構築するcreateIntent()メソッドを実装する必要があります.
各協定ではparseResult()も実装する必要があります.これにより、指定されたresultCode(Activity.RESULT_OKやActivity.RESULT_CANCELEEDなど)やIntentに基づいて出力コンテンツが生成されます.
createIntent()を呼び出したり、別のActivityを起動したり、parseResult()を使用して結果を構築したりすることなく、指定した入力内容の結果を決定できる場合、協定はgetSynchronousResult()を選択的に実現することができます.
    class PickRingtone : ActivityResultContract<Int, Uri?>() {
        override fun createIntent(context: Context, ringtoneType: Int) =
            Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
                putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType)
            }

        override fun parseResult(resultCode: Int, result: Intent?) : Uri? {
            if (resultCode != Activity.RESULT_OK) {
                return null
            }
            return result?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
        }
    }
    

カスタム協定が不要な場合は、StartActivity ForResult協定を使用できます.これは、入力内容として任意のIntentを受け入れ、Activity Resultに戻る共通の協定です.コールバックでresultCodeとIntentを抽出できます.次の例に示します.
    val startForResult = prepareCall(StartActivityForResult()) { result: ActivityResult ->
        if (result.resultCode == Activity.RESULT_OK) {
            val intent = result.intent
            // Handle the Intent
        }
    }

    override fun onCreate(savedInstanceState: Bundle) {
        // ...

        val startButton = findViewById(R.id.start_button)

        startButton.setOnClickListener {
            // Use the Kotlin extension in activity-ktx
            // passing it the Intent you want to start
            startForResult(Intent(this, ResultProducingActivity::class.java))
        }
    }