PermissionsDispatcher: [KTX] System dialog doesn't show up, after manually disable permission from setting

Overview

I’ve two permissions in my fragment (camera and storage) this is my code for them:

private fun checkCameraPermission() {
        constructPermissionsRequest(
                Manifest.permission.CAMERA,
                onShowRationale = { request ->
                    showPermissionRationaleDialog(
                            Manifest.permission.CAMERA,
                            onAccept = { request.proceed() },
                            onDeny = { navigateHome() }
                    )
                },
                onPermissionDenied = {
                    navigateHome()
                },
                onNeverAskAgain = {
                    showPermissionNeverAskAgainDialog(
                            Manifest.permission.CAMERA,
                            onDeny = { navigateHome() }
                    )
                }
        ) {
            showCamera()
        }.launch()
    }

    private fun checkStoragePermission() {
        constructPermissionsRequest(
                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                onShowRationale = { request ->
                    showPermissionRationaleDialog(
                        Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        onAccept = { request.proceed() }
                    )
                },
                onNeverAskAgain = {
                    showPermissionNeverAskAgainDialog(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                }
        ) {
            saveFile()
        }.launch()
    }

I’m calling checkCameraPermission() on my fragment’s onStart() function. but there is a save button for calling checkStoragePermission(). Everything works great. until when I go to setting and manually disable storage permission from there. After returning to the fragment when I press the save button, Rationale shows up but when I accept button the system permission dialog doesn’t show up. (I set a breakpoint and I saw that request.proceed() called. but It doesn’t jump to the requiresPermission method.) The Strange part is that If I disable checkCameraPermission() from onStart() It will work.

Expected

System dialog shows up event after manually disabling permission from setting

Actual

System dialog doesn’t show up, after manually disable permission from setting (when there is two permission checks on the fragment)

Environment

  • Which library version are you using? ktx 1.0.1
  • On which devices do you observe the issue? all devices

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 21 (12 by maintainers)

Most upvoted comments

I’m using api level 29. I’ll try to make a sample project and share it with you.

got it for point 1 and 3. for point 2, let me think it over…it may have some degrades from my initial design 🤔

@hotchemi

ok i have tried to implement some changes and found out

Maybe should only create requestFun when PermissionUtils.shouldShowRequestPermissionRationale(activity, *permissions) == false ?

this is not valid anymore i missed out onShowRationale?.invoke(KtxPermissionRequest.create(onPermissionDenied, requestFun)) is also using the requestFun, no change needed here.

storing a reference to PermissionRequest in PermissionRequester

do you have any proposal for this? for now I’m not so much positive as it looks beyond the responsibility of the library but might be because I haven’t been able to imagine that well.

I was basically trying to find a way to implement orientation change support, which did not work as the permissionRequest relies on higher order functions that were passed as arguments to the permissionsRequester which is no longer valid in an orientation change even if i saved the permissionRequest object in the viewmodel.

And due to these higher order function arguments, the ktx lib will not work for orientation change scnario. Annotation will still work though as no higher order function are being used as arguments.

using childfragmentmanager and a fragment scope viewmodel

Actually when I was trying to fix the original issue, the initial approach was to use childfragmentmanager, but it didn’t go well(sorry I forgot why but might be avoidable).

and the childfragmentmanager is not possible due to this No view found for id 0x1020002 (android:id/content) for fragment NormalRequestPermissionFragment{69f2b36 #0 id=0x1020002} There is not container to inflate the permission fragment in the childfragmentmanager.

@hotchemi just tried ktx 1.0.3, crash is not happening anymore. Just a suggestion, or maybe even a nitpick.

Maybe should only create requestFun when PermissionUtils.shouldShowRequestPermissionRationale(activity, *permissions) == false ?

like below

override fun launch() {
        if (permissionRequestType.checkPermissions(activity, permissions)) {
            requiresPermission()
        } else {
            ViewModelProvider(activity).get(PermissionRequestViewModel::class.java).observe(
                activity,
                requiresPermission,
                onPermissionDenied,
                onNeverAskAgain
            )
            
            if (PermissionUtils.shouldShowRequestPermissionRationale(activity, *permissions)) {
                onShowRationale?.invoke(KtxPermissionRequest.create(onPermissionDenied, requestFun))
            } else {
               val requestFun: Fun = {
                    activity.supportFragmentManager
                        .beginTransaction()
                        .replace(android.R.id.content, permissionRequestType.fragment(permissions))
                        .commitAllowingStateLoss()
                }
                requestFun.invoke()
            }
        }
    }

And excuse my ignorance if i cannot figure out the reason, but maybe for fragments, could use childfragmentmanager(as u mentioned before) and fragment scope viewmodels instead? It looks more correct to encapsulate the permissions request to the fragment scope itself instead of the activity scope if the permissions is request from the fragment.

like below

override fun launch() {
        if (permissionRequestType.checkPermissions(activity, permissions)) {
            requiresPermission()
        } else {
            ViewModelProvider(activity).get(PermissionRequestViewModel::class.java).observe(
                fragment,
                requiresPermission,
                onPermissionDenied,
                onNeverAskAgain
            )
            
            if (PermissionUtils.shouldShowRequestPermissionRationale(activity, *permissions)) {
                onShowRationale?.invoke(KtxPermissionRequest.create(onPermissionDenied, requestFun))
            } else {
               val requestFun: Fun = {
                    fragment.childFragmentManager
                        .beginTransaction()
                        .replace(android.R.id.content, permissionRequestType.fragment(permissions))
                        .commitAllowingStateLoss()
                }
                requestFun.invoke()
            }
        }
    }

and one more thing/suggestion. Sorry for the long winded comment. Could PermissionRequester perhaps store the PermissionRequest so that it can be retrieved in other places instead of only in the onShowRationale function specified? Reason is current i am triggering the PermissionRequest.proceed() from a button click. What i am doing currently is shown below.

// button click listener
    binding.btnCameraPermissionOk.clicks()
            .debounce(300)
            .onEach {               
                    if (gotoSettings)
                        goToSystemSettingsPage()
                    else {
                        permissionRequest?.proceed() ?: permissionsRequester.launch()
                    }
                }
            }
            .launchIn(viewLifecycleOwner.lifecycleScope)

// onRationale function, triggering a showing of a button Ui on screen
    fun showRationaleStartCameraSource(permrequest: PermissionRequest) {
        permissionRequest = permrequest
       // fullscreen UI to show camera permissions needs to be enable 
        binding.flCamPermission.visibility = View.VISIBLE
        binding.flCamExistence.visibility = View.GONE
        binding.btnCameraPermissionOk.text = getString(R.string.button_ok)
    }

good catch and sorry for messing up after maven central migration🙏 now released 1.0.3 and updated readme 🙇

um, let us take a look at this weekend 🙏