更新至 androidx.fragment:fragment:1.3.0-alpha08:在 onCreate 之后不再允许 registerForActivityResult。 onCreate之后如何使用?

Update to androidx.fragment:fragment:1.3.0-alpha08: registerForActivityResult not allowed after onCreate anymore. How to use after onCreate?

初始问题(18/05/2020):

因此使用来自

的最新更新

我收到错误:

FragmentXY is attempting to registerForActivityResult after being created. Fragments must call registerForActivityResult() before they are created (i.e. initialization, onAttach(), or onCreate()).

在向用户显示有关这些权限的使用以及为什么需要这些权限的信息后,我过去常常在我的 StartFragment(单个 Activity 应用程序,在 onViewCreated 中)中检查权限。在过去的 3(?)个月里,一切都运行良好。

我在 changelog 中看到:

Behavior Changes

[...]
Calling registerForActivityResult() after onCreate() now throws an exception indicating that this is not allowed rather than silently failing to deliver results after a configuration change. (b/162255449) "

我暂时降级回版本 1.3.0-alpha07。
但是如果我需要 registerForActivityResult 在我的 Fragments AFTER 中创建视图(例如权限),我该怎么做什么时候升级到 1.3.0-alpha08 版本?

文档指出我应该在 Fragment 的 onCreate 中使用 launch()(见下文),但这意味着我必须在创建视图之前执行此操作,这与我的应用程序流程相矛盾。

Behavior Changes

[...]
You can now call launch() on an ActivityResultLauncher in the onCreate() lifecycle method of a fragment. (b/161464278) "

由于此行为似乎是开发人员有意为之,这不是错误或其他任何问题,但如何在 onCreate 之后继续使用 ActivityResults?有什么想法吗?


编辑(19/05/2020):

感谢@A.Andriyishyna 我了解到注册(在 onCreate 中)和执行(在需要时,例如在 onViewCreated 中)必须单独处理。 问题是我在其他文件中有方便的内联函数(由 Flywith24 提供),这有助于我将权限 BL 与视图(片段)分开。
有没有办法保留这些内联函数而不必彻底更改它们?

  1. 片段
class GalleryFragment: ScopedFragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initializePermissions(requiredContext)
    }

    private fun initializePermissions(context: Context) {
        storagePermissions(
            context = context,
            actionOnGranted = { showImages() },
            actionOnDeclined = { showNoAccess() },
            actionRepeat = { initializePermissions(context) }
        )
    }
}
  1. 权限DSL
inline fun Fragment.storagePermissions(
    context: Context,
    crossinline actionOnGranted: () -> Unit,
    crossinline actionOnDeclined: () -> Unit,
    crossinline actionRepeat: () -> Unit
) {
    when {
        Build.VERSION.SDK_INT < Build.VERSION_CODES.Q -> {

            if (
                ContextCompat.checkSelfPermission(
                    context, Manifest.permission.READ_EXTERNAL_STORAGE
                ) == PackageManager.PERMISSION_GRANTED
            ) {
                actionOnGranted()
            } else {
                permission(
                    Manifest.permission.READ_EXTERNAL_STORAGE
                ) {
                    granted = {
                        actionOnGranted()
                    }
                    denied = {
                        actionRepeat()
                    }
                    explained = {
                        actionOnDeclined()
                    }
                }
            }
        }

        Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
            if (
                ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_MEDIA_LOCATION
                ) == PackageManager.PERMISSION_GRANTED) {
                Log.d("Storage Permission", "Permission already granted.")
                actionOnGranted()
            } else {
                Log.d("Storage Permission", "No Permission Yet -> Ask for it!")
                permissions(
                    Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.ACCESS_MEDIA_LOCATION
                ) {
                    allGranted = {
                        actionOnGranted()
                    }
                    denied = {
                        Log.d("Storage Permission", "Denied")
                        actionRepeat()
                    }
                    explained = {
                        Log.d("Storage Permission", "Permanently Denied")
                        actionOnDeclined()
                    }
                }
            }
        }
    }
}
  1. 权限扩展
inline fun Fragment.requestPermission(
    permission: String,
    crossinline granted: (permission: String) -> Unit = {},
    crossinline denied: (permission: String) -> Unit = {},
    crossinline explained: (permission: String) -> Unit = {}

) {
    registerForActivityResult(ActivityResultContracts.RequestPermission()) { result ->
        when {
            result -> granted.invoke(permission)
            shouldShowRequestPermissionRationale(permission) -> denied.invoke(permission)
            else -> explained.invoke(permission)
        }
    }.launch(permission)
}


inline fun Fragment.requestMultiplePermissions(
    vararg permissions: String,
    crossinline allGranted: () -> Unit = {},
    crossinline denied: (List<String>) -> Unit = {},
    crossinline explained: (List<String>) -> Unit = {}
) {
    registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result: MutableMap<String, Boolean> ->

        val deniedList = result.filter { !it.value }.map { it.key }
        when {
            deniedList.isNotEmpty() -> {

                val map = deniedList.groupBy { permission ->
                    if (shouldShowRequestPermissionRationale(permission)) DENIED else EXPLAINED
                }

                map[DENIED]?.let { denied.invoke(it) }

                map[EXPLAINED]?.let { explained.invoke(it) }
            }
            else -> allGranted.invoke()
        }
    }.launch(permissions)
}

这只是意味着你不应该注册 onCreate() 之后的回调。

所以你可以这样做

private val checkPermission = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
        ...
    }

然后随时启动支票

checkPermission.launch(array-of-permissions)

reason:

When starting an activity for a result, it is possible (and, in cases of memory-intensive operations such as camera usage, almost certain) that your process and your activity will be destroyed due to low memory.

For this reason, the Activity Result APIs decouple the result callback from the place in your code where you launch the other activity. As the result callback needs to be available when your process and activity are recreated, the callback must be unconditionally registered every time your activity is created, even if the logic of launching the other activity only happens based on user input or other business logic.

我最近 运行 遇到了同样的问题,我根据此处的问题创建了自己的 PermissionDSL,这有助于我将权限代码与片段分开, (虽然这种方式和原来的请求权限的方式差别不大)

注意:这是更新问题的答案

... Is there a way to keep those inline functions without having to change them drastically?

  • 这只能在片段中使用
  • 虽然它可以很容易地扩展为多个权限,但它用于请求单一权限

步骤 1 添加gradle依赖

def lifecycle_version = "2.3.0"

implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"

步骤2 添加以下代码

inline fun <reified R : ActivityResultLauncher<String>> Fragment.requestPermission(
    permission: String,
    noinline granted: (permission: String) -> Unit = {},
    noinline denied: (permission: String) -> Unit = {},
    noinline explained: (permission: String) -> Unit = {}

): ReadOnlyProperty<Fragment, R> = PermissionResultDelegate(this, permission, granted, denied, explained)



class PermissionResultDelegate<R : ActivityResultLauncher<String>>(
    private val fragment: Fragment, private val permission: String,
    private val granted: (permission: String) -> Unit,
    private val denied: (permission: String) -> Unit,
    private val explained: (permission: String) -> Unit
) :
    ReadOnlyProperty<Fragment, R> {

    private var permissionResult: ActivityResultLauncher<String>? = null


    init {
        fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onCreate(owner: LifecycleOwner) {
                fragment.apply {
                    permissionResult = registerForActivityResult(
                        ActivityResultContracts.RequestPermission()
                    ) { isGranted: Boolean ->

                        when {
                            isGranted -> granted(permission)
                            shouldShowRequestPermissionRationale(permission) -> denied(permission)
                            else -> explained(permission)
                        }
                    }
                }
            }

            override fun onDestroy(owner: LifecycleOwner) {
                permissionResult = null
            }
        })
    }

    override fun getValue(thisRef: Fragment, property: KProperty<*>): R {
        permissionResult?.let { return (it as R) }

        error("Failed to Initialize Permission")
    }
}

用法

class DemoFrag : Fragment {

private val readStoragePermissionResult: ActivityResultLauncher<String> by requestPermission(Manifest.permission.READ_EXTERNAL_STORAGE,
            granted = {
                Log.d(TAG, "Granted")
            }, denied = {
        Log.d(TAG", "Denied")
    })

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        readStoragePermissionResult.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
    }
}