How to avoid writing duplicate boilerplate code for requesting permissions?

前端 未结 2 609
无人共我
无人共我 2021-02-06 02:53

I\'m updating my app to be Android 6-compatible. The permission model is not very complex in theory, but now I\'m in the process of implementing it and I find myself writing the

相关标签:
2条回答
  • 2021-02-06 03:15

    I didn't want to use any of the available libraries for the reasons explained in the question, so I developed something myself.

    All my activities that require one or more permissions inherit from a PermissionActivity that handles all the permission related tasks.

    How it works is your activity calls

    if (checkHasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) { }
    

    from the parent class. If the permission is already granted, the code can continue. If not, the parent class will request the permission and send the results to the child class using an abstract method and/or one or multiple overridable methods.


    The parent class

    This class can be left unchanged, apart from the switch blocks in messageForRationale() and requestCodeForPermission(). Update those for the permissions your app needs.

    /**
     * An activity that can be extended to simplify handling permissions.
     * <p>
     * Deriving classes will not have to write boilerplate code and code duplication between activities
     * that share this functionality is avoided.
     */
    public abstract class PermissionActivity extends AppCompatActivity {
    
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                               @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    
            // If multiple permissions were requested in one call, check if they were all granted.
            if (requestCode == RequestCode.PERMISSION_MULTIPLE) {
                boolean allPermissionsGranted = true;
                for (int grantResult : grantResults) {
                    if (grantResult != PackageManager.PERMISSION_GRANTED) {
                        allPermissionsGranted = false;
                    }
                }
    
                if (allPermissionsGranted) {
                    onAllPermissionsGranted(permissions);
                    return;
                }
            }
    
            // Else, check each one if it was granted/denied/blocked.
            for (int i = 0; i < permissions.length; i++) {
                if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                    // User granted permission.
                    onPermissionGranted(permissions[i]);
                } else {
                    if (ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i])) {
                        // User denied permission.
                        onPermissionDenied(permissions[i]);
                    } else {
                        // User denied permission and checked 'never ask again'.
                        onPermissionBlocked(permissions[i]);
                    }
                }
            }
        }
    
        /**
         * Checks if the app has the given permission(s).
         * <p>
         * If not, it will request them.
         * <p>
         * The method is called `checkHasPermission` to avoid the linter showing a warning in the
         * child class when it's delegating permission checks to its parent class. See
         * http://stackoverflow.com/questions/36031218/check-android-permissions-in-a
         * -method/36193309#36193309 for details.
         */
        public boolean checkHasPermission(int requestCode, String... permissions) {
            if (!(permissions.length > 0)) {
                throw new IllegalArgumentException("must request at least one permission");
            }
    
            if (requestCode == RequestCode.PERMISSION_MULTIPLE) {
                List<String> permissions_ = new ArrayList<>();
    
                for (String permission : permissions) {
                    if (ActivityCompat.checkSelfPermission(this, permission) !=
                            PackageManager.PERMISSION_GRANTED) {
                        permissions_.add(permission);
                    }
                }
    
                if (!permissions_.isEmpty()) {
                    requestPermissions(this, permissions_.toArray(new String[permissions_.size()]), requestCode);
                    return false;
                } else {
                    return true;
                }
            } else {
                if (ActivityCompat.checkSelfPermission(this, permissions[0]) !=
                        PackageManager.PERMISSION_GRANTED) {
                    requestPermissions(this, permissions, requestCode);
                    return false;
                } else {
                    return true;
                }
            }
        }
    
        /**
         * Requests the given permissions.
         */
        private void requestPermissions(Activity activity, String permissions[], int resultCode) {
            showRequestPermissionsDialog(activity, permissions, resultCode);
        }
    
        /**
         * Called when a rationale (explanation why a permission is needed) should be shown to the user.
         * <p>
         * If the user clicks the positive button, the permission is requested again, otherwise the
         * dialog is dismissed.
         */
        public void showRationaleDialog(Activity activity, String permission, String message,
                                        int resultCode) {
            new AlertDialog.Builder(activity)
                    .setMessage(message)
                    .setPositiveButton("ok", (dialog, which) ->
                            showRequestPermissionDialog(activity, permission, resultCode))
                    .setNegativeButton("not now", (dialog, which) -> { /* Do nothing */ })
                    .show();
        }
    
        /**
         * Requests a single permission.
         */
        private void showRequestPermissionDialog(Activity activity, String permission, int resultCode) {
            ActivityCompat.requestPermissions(activity, new String[]{permission}, resultCode);
        }
    
        /**
         * Requests multiple permissions in one call.
         */
        private void showRequestPermissionsDialog(Activity activity, String[] permissions,
                                                  int resultCode) {
            ActivityCompat.requestPermissions(activity, permissions, resultCode);
        }
    
        /**
         * Returns a message to be shown to the user that explains why a specific permission is
         * required.
         */
        public String messageForRationale(String permission) {
            String s;
            switch (permission) {
                case Manifest.permission.READ_PHONE_STATE:
                    s = "access this device's state";
                    break;
                case Manifest.permission.ACCESS_FINE_LOCATION:
                    s = "access the location of this device";
                    break;
                case Manifest.permission.SEND_SMS:
                    s = "send text messages";
                    break;
                default:
                    throw new IllegalArgumentException("Permission not handled: " + permission);
            }
    
            return String.format("MyApp needs permission to %s.", s);
        }
    
        /**
         * Get the RequestCode for the given permission.
         */
        public int requestCodeForPermission(String permission) {
            int code;
            switch (permission) {
                case Manifest.permission.READ_PHONE_STATE:
                    code = RequestCode.PERMISSION_READ_PHONE_STATE;
                    break;
                case Manifest.permission.ACCESS_FINE_LOCATION:
                    code = RequestCode.PERMISSION_FINE_LOCATION;
                    break;
                case Manifest.permission.SEND_SMS:
                    code = RequestCode.PERMISSION_SEND_SMS;
                    break;
                // TODO: add required permissions for your app
                default:
                    throw new IllegalArgumentException("Permission not handled: " + permission);
            }
    
            return code;
        }
    
        /**
         * Called if all requested permissions were granted in the same dialog.
         * E.g. FINE_LOCATION and SEND_SMS were requested, and both were granted.
         * <p>
         * Child class can override this method if it wants to know when this happens.
         * <p>
         * Linter can show an unjust "call requires permission" warning in child class if a method that
         * requires permission(s) is called. Silence it with `@SuppressWarnings("MissingPermission")`.
         */
        protected void onAllPermissionsGranted(String[] permissions) {
    
        }
    
        /**
         * Called for all permissions that were granted in the same dialog, in case not all were
         * granted. E.g. if FINE_LOCATION, COARSE_LOCATION and SEND_SMS were requested and FINE_LOCATION
         * was not granted but COARSE_LOCATION and SEND_SMS were, it will be called for COARSE_LOCATION
         * and SEND_SMS.
         * <p>
         * Child class can override this method if it wants to know when this happens.
         * <p>
         * Linter can show an unjust "call requires permission" warning in child class if a method that
         * requires permission(s) is called. Silence it with `@SuppressWarnings("MissingPermission")`.
         */
        protected void onPermissionGranted(String permission) {
    
        }
    
        /**
         * Called for all permissions that were denied in the same dialog, handled one by one.
         * <p>
         * Child class should not override this general behavior.
         */
        protected void onPermissionDenied(String permission) {
            String message = messageForRationale(permission);
            showRationaleDialog(this, permission, message, requestCodeForPermission(permission));
        }
    
        /**
         * Called for all permissions that were blocked in the same dialog, handled one by one.
         * <p>
         * Blocked means a user denied a permission with the 'never ask again' checkbox checked.
         * <p>
         * Child class must override and decide what to do when a permission is blocked.
         */
        protected abstract void onPermissionBlocked(String permission);
    }
    

    The -> notation are lambda expressions.

    RequestCode is an interface solely used for abstracting away the numbers:

    public interface RequestCode {
        int PERMISSION_READ_PHONE_STATE = 0;
        int PERMISSION_FINE_LOCATION = 1;
        int PERMISSION_SEND_SMS = 2;
        int PERMISSION_MULTIPLE = 3;
    }
    

    you can change it as you like. Don't use numbers over 256. If you do, an exception will be thrown saying

    You can only use the lower 8 bits for the request code.


    In activities where permission is required, you can use it like this (just an example). Be sure to have the activity extend PermissionActivity

    private void callThisSomewhere() {
        if (checkHasPermission(RequestCode.PERMISSION_READ_PHONE_STATE,
                Manifest.permission.READ_PHONE_STATE)) {
            tryDoStuffWithPhoneState();
        }
    }
    
    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
    private void doStuffWithPhoneState() {
        // Do stuff.
    }
    
    @Override
    public void onPermissionGranted(String permission) {
        tryDoStuffWithPhoneState();
    }
    
    @Override
    public void onPermissionBlocked(String permission) {
        // Disable parts of app that require this permission.
    }
    

    If you are requesting multiple permissions in one go, you should use RequestCode.PERMISSION_MULTIPLE. Otherwise only the first permission will be requested.

    Permissions that are not listed in AndroidManifest.xml will be automatically blocked without showing the user the dialog, so be sure to add any permissions you have to request in the manifest as well.


    Limitations

    This solution is not compatible with fragments or services.

    0 讨论(0)
  • 2021-02-06 03:23

    This is what I used for managing permissions in my app.

    Parent Class for Permission

        public class PermissionManager extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback {
        private static final int REQUEST_CODE = 200;
        private Activity context;
    
        private String[] permissions;
        private ArrayList<String> grantedPermissions = new ArrayList<>();
        private RequestedPermissionResultCallBack callBack;
    
        @Override
        public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
            super.onCreate(savedInstanceState, persistentState);
        }
    
        @Override
        protected void onStart() {
            super.onStart();
            setContentView(R.layout.actvity_permission);
            checkForRequiredPermission(getIntent().getStringArrayExtra(getString(R.string.permission)));
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    
            switch (requestCode) {
    
                case REQUEST_CODE:
    
                    checkForGrantedPermissions(permissions, grantResults);
                    break;
            }
    
        }
    
    
        @TargetApi(Build.VERSION_CODES.M)
        private void checkForRequiredPermission(String[] permissions) {
            ArrayList<String> requiredPermissionList = new ArrayList<>();
            for (String permission : permissions) {
                if (ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                    requiredPermissionList.add(permission);
                } else {
                    grantedPermissions.add(permission);
                }
            }
    
            if (requiredPermissionList.size() > 0) {
                (this).requestPermissions(requiredPermissionList.toArray(new String[requiredPermissionList.size()]), REQUEST_CODE);
            } else {
    
                setResult(grantedPermissions);
            }
    
        }
    
    
        public void checkForGrantedPermissions(String[] permissions, int[] grantResults) {
    
            for (int i = 0; i < permissions.length; i++) {
                if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                    grantedPermissions.add(permissions[i]);
                }
    
            }
            setResult(grantedPermissions);
    
        }
    
        private void setResult(ArrayList<String> grantedPermissions) {
            Intent intent = new Intent();
            intent.putStringArrayListExtra(getString(R.string.granted_permission), grantedPermissions);
            setResult(Activity.RESULT_OK, intent);
            this.finish();
        }
    }
    

    When you want to check for permission call this class like this

    private void checkForPermissions() {
            Intent intent = new Intent(this, PermissionManager.class);
            intent.putExtra(getString(R.string.permission), permission);
            startActivityForResult(intent, AppConstants.PERMSION_REQUEST_CODE);
        }
    

    Here permission is an array of permission you want to ask for something like this

        private String permission[] = new String[]{Manifest.permission.READ_PHONE_STATE, Manifest.permission.RECEIVE_SMS, Manifest.permission.READ_SMS};
    

    and this is the code for onActivityResult

    case AppConstants.PERMSION_REQUEST_CODE:
                        ArrayList<String> grantedPermissionList = data.getStringArrayListExtra(getString(R.string.granted_permission));
    
                        if (grantedPermissionList != null && grantedPermissionList.size() > 0 && grantedPermissionList.contains(permission[0])) {
                            createRequest();
    
                        } else {
                            showSettingsDialog(getString(R.string.permission_required));
                        }
    
                        break;
    
    0 讨论(0)
提交回复
热议问题