permissions-dispatcher / PermissionsDispatcher

A declarative API to handle Android runtime permissions.
https://github.com/permissions-dispatcher/PermissionsDispatcher
Apache License 2.0
11.21k stars 1.44k forks source link

[KTX] onNeverAskAgain is called many times (incremental) #687

Closed mtrakal closed 3 years ago

mtrakal commented 3 years ago

Overview

KTX version call onNeverAskAgain many times.

Environment

import android.Manifest
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.MediaStore
import android.provider.Settings
import android.util.Log
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import permissions.dispatcher.ktx.constructPermissionsRequest

object PermissionHelper {
    private fun showSettingsDialog(context: Context) {
        Log.e(PermissionHelper::class.java.simpleName, "showSettingsDialog")
        val builder: AlertDialog.Builder = AlertDialog.Builder(context)
        builder.setTitle("Need Permissions")
        builder.setMessage("This app needs permission to use this feature. You can grant them in app settings.")
        builder.setPositiveButton("GOTO SETTINGS") { dialog, which ->
            dialog.cancel()
            openSettings(context)
        }
        builder.setNegativeButton("Cancel") { dialog, which -> dialog.cancel() }
        builder.show()
    }

    private fun showRationale(context: Context, permissionDispatcher: permissions.dispatcher.PermissionRequest? = null) = AlertDialog.Builder(context).apply {
        setTitle("Camera permission")
        setMessage("Camera permission is needed to take pictures of your cat")
        setNegativeButton(android.R.string.ok) { dialog, which ->
            permissionDispatcher?.proceed()
            dialog.cancel()
        }
    }.show().also {
        Log.e(PermissionHelper::class.java.simpleName, "showRationale")
    }

    // navigating user to app settings
    private fun openSettings(context: Context) {
        Log.e(PermissionHelper::class.java.simpleName, "openSettings")
        val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
        val uri: Uri = Uri.fromParts("package", context.packageName, null)
        intent.data = uri
        context.startActivity(intent)
    }

    private fun openCamera(context: Context) {
        Log.e(PermissionHelper::class.java.simpleName, "openCamera")
        val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        context.startActivity(intent)
    }

    private fun permissionDenied(context: Context) {
        Log.e(PermissionHelper::class.java.simpleName, "permissionDenied")
    }

    fun permissionDispatcher(activity: AppCompatActivity) {
        val context: Context = activity
        activity.constructPermissionsRequest(Manifest.permission.CAMERA,
            onShowRationale = { permissionRequest ->
                Log.e(PermissionHelper::class.java.simpleName, "PD: onShowRationale")
                showRationale(context, permissionDispatcher = permissionRequest) },
            onPermissionDenied = {
                Log.e(PermissionHelper::class.java.simpleName, "PD: onPermissionDenied")
                permissionDenied(context) },
            onNeverAskAgain = {
                Log.e(PermissionHelper::class.java.simpleName, "PD: onNeverAskAgain")
                showSettingsDialog(context) }) {
            Log.e(PermissionHelper::class.java.simpleName, "PD: onPermissionGranted")
            openCamera(context)
        }.launch()
    }
}

Reproducible steps

Create button:

vButton.setOnClickListener {
            PermissionHelper.permissionDispatcher(this)
        }

Video: http://issues.mtrakal.cz/device-2020-09-09-123308.mp4

mtrakal commented 3 years ago

https://github.com/mtrakal/permissionDispatcherIssue687

https://github.com/mtrakal/permissionDispatcherIssue687/blob/master/app/src/main/java/cz/mtrakal/permissions/PermissionDispatcherHelper.kt#L26 is called multipletimes

mtrakal commented 3 years ago

It's possible to reproduce on your ktx-sample too, just replace Toasts with Log or AlertDialog, because Toasts are shown over itself...

hotchemi commented 3 years ago

sorry let me check the PR!

mtrakal commented 3 years ago

the main issue maybe is, that persists more ViewModel instances and observers are not unsubscribed. But PermissionRequestFragment -> viewModel.removeObservers(this) in dismis() didn't help

Sure, check the PR, it notify only one observer, it didn't solve the root cause I think, but consequence

hotchemi commented 3 years ago

The issue has been resolved in 1.0.1 I guess 🙏