안드로이드 마시맬로 6.0 이상의 런타임 권한


소개


API 23 이전에 요청 된 권한은 응용 프로그램을 설치하기 전에 사용자에게 제공됩니다. 사용자는 이러한 권한을 응용 프로그램에 부여해야하는지 여부를 결정해야합니다.


사용자가 필요한 권한을 거부하면 관련 응용 프로그램을 설치할 수 없습니다. 권한 확인은 설치 중에 만 수행됩니다. 설치 후 사용 권한을 거부하거나 부여 할 수 없습니다.


Android 6.0 Marshmallow (API 23)는 새로운 런타임 권한 모델을 도입했습니다. 런타임 권한을 보다 쉽게 만들 수있는 여러 라이브러리가 있습니다. 빨리 시작하려면 PermissionsDispatcher를 사용하여 런타임 권한 관리에 대한 안내서를 확인하십시오.


android_marshmallow_permissions.png

이 개념은 사용자가 응용 프로그램에 부여하는 모든 각 권한에 대해 알 수 있기 때문에 유용합니다. 사용자는 자신의 기기에서 설정 > 앱 > APPNAME > 권한 을 통해 부여 된 권한을 취소 할 수 있습니다.


런타임 권한을 구현하지 않으면 응용 프로그램이 충돌하거나 MarshMallow 이상의 장치에서 제대로 작동하지 않습니다.


두 가지 중요한 보호 수준이 있습니다.


일반 사용 권한 은 사용자의 개인 정보 보호 또는 다른 응용 프로그램의 작동에 무해한 것으로 간주되는 사용 권한입니다. 예를 들어, 시간대 또는 인터넷 액세스를 설정할 권한. 일반 권한은 응용 프로그램에 자동으로 부여됩니다. 전체 목록을 보려면 일반 사용 권한 을 참조하십시오.

위험한 사용 권한 은 사용자의 개인 정보에 영향을 미치거나 데이터 또는 다른 응용 프로그램의 작동에 잠재적으로 영향을 미칠 수 있습니다. 예를 들어, 사용자 연락처 데이터를 읽는 기능. 위험한 권한은 런타임에 사용자가 앱에 부여해야합니다.

두 가지 유형의 권한은 AndroidManifest.xml에 정의되어야하지만 위험한 권한 만 런타임 요청이 필요합니다.


AndroidManifest.xml을 열고 다음 권한을 추가하십시오.


 <uses-permission android : name = "android.permission.ACCESS_COARSE_LOCATION"/>

권한이있는 경우 checkSelfPermission 메소드를 통해 확인할 수 있습니다.


 int CALLBACK_NUMBER = 1;

 String wantPermission = Manifest.permission.ACCESS_COARSE_LOCATION;

 // 표준 액티비티에서 호출, AppCompActivity에 ContextCompat.checkSelfPermission 사용

 int permissionCheck = checkSelfPermission (this, wantPermission);

 if (! permissionCheck == PackageManager.PERMISSION_GRANTED) {

     // 사용자가 이전에 거부했을 수 있습니다. Android에 이유를 제시해야하는지 물어 봅니다.

     if (shouldShowRequestPermissionRationale (MainActivity.isis, wantPermission)) {

         // 사용자에게 설명을 보여줍니다.

     } else {

         // 권한을 요청하십시오.

         // CALLBACK_NUMBER는 정수 상수입니다.

         requestPermissions (MainActivity.isis, new String [] {wantPermission}, CALLBACK_NUMBER);

     }

 } else {

     // 사용 권한이 있으면 사용하십시오.

 }

각 권한 요청에는 세 개의 매개 변수가 필요합니다. 첫 번째는 context 이고 두 번째는 사용 권한의 String 배열이고 세 번째는 Integer 유형의 requestCode 입니다. 마지막 매개 변수는 요청에 첨부 된 임의의 코드이며 사용 사례에 맞는 임의의 숫자가 될 수 있습니다. 결과가 활동에서 리턴되면,이 코드를 포함하며이를 사용하여 여러 결과를 서로 구별합니다.


위험한 사용 권한이 사용 권한 그룹 으로 그룹화되는 흥미로운 점이 하나 있습니다 (표에 표시됨). 그룹에서 한 번만 권한을 요청해야한다는 의미입니다. 권한 그룹의 권한이 부여되면 동일한 그룹의 다른 권한이 자동으로 부여됩니다. 마찬가지로 권한 그룹의 권한이 거부되면 전체 그룹이 거부됩니다. 예를 들어, READ_EXTERNAL_STORAGE 가 부여되면 응용 프로그램은 WRITE_EXTERNAL_STORAGE 권한도 부여합니다.


허가 그룹 권한

android.permission-group.CALENDAR

android.permission.READ_CALEENDAR

android.permission.WRITE_CALEENDAR

카메오

android.permission.CAMERA

android.permission-group.CONTACTS

android.permission.READ_CONTACTS

android.permission.WRITE_CONTACTS

android.permission.GET_ACCOUNTS

android.permission-group.LOCATION

android.permission.ACCESS_FINE_LOCATION

android.permission.ACCESS_COARSE_LOCATION

android.permission-group.MICROPHONE

android.permission.RECORD_AUDIO

android.permission-group.PHONE

android.permission.READ_PHONE_STATE

android.permission.CALL_PHONE

android.permission.READ_CALL_LOG

android.permission.WRITE_CALL_LOG com.

android.voicemail.permission.ADD_VOICEMAIL

android.permission.USE_SIP

android.permission.PROCESS_OUTGOING_CALLS

android.permission-group.SENSORS

android.permission.BODY_SENSORS

android.permission-group.SMS

android.permission.SEND_SMS

android.permission.RECEIVE_SMS

android.permission.READ_SMS

android.permission.RECEIVE_WAP_PUSH

android.permission.RECEIVE_MMS

android.permission.READ_CELL_BROADCASTS

android.permission-group.STORAGE

android.permission.READ_EXTERNAL_STORAGE

android.permission.WRITE_EXTERNAL_STORAGE

그래서 Marshmallow와 그 이상에서는 런타임에 이러한 위험한 권한이 부여되어야하며 정상적인 권한은 자동으로 부여됩니다. Lollipop (API 레벨 22) 이하의 Android 버전에서는 매니페스트에 지정된 권한이 설치시 부여됩니다.


Android Studio로 샘플 애플리케이션을 만들어 이러한 권한이 어떻게 작동하는지 명확하게 파악해 봅시다. Marshmallow (API 레벨 23)로 대상 SDK 버전을 선택하는 것을 잊지 마십시오.


AndroidManifest.xml에 다음 권한을 추가하십시오.


 <uses-permission android : name = "android.permission.WRITE_EXTERNAL_STORAGE"/>

위의 추가 권한은 런타임에 요청할 위험한 권한입니다.


activity_main.xml 레이아웃에는 두 개의 하위 Button 이있는 RelativeLayout 이라는 부모가 있습니다. 하나는 권한을 확인하고 다른 하나는 권한을 요청합니다. 전체 activity_main.xml 레이아웃은 아래와 같습니다.


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btnCheckPermission"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Check permission"/>

    <Button
        android:id="@+id/btnRequestPermission"
        android:layout_below="@+id/btnCheckPermission"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Request permission"/>
</RelativeLayout>


우리의 Activity 은 AppCompatActivity 까지 확장되고 OnClickListener 인터페이스를 구현 OnClickListener . 우리의 애플 리케이션은 두 개의 버튼이 있습니다. 하나는 권한이 이미 부여되었는지 여부를 확인하는 것입니다. 다른 하나는 권한이 부여되지 않은 경우 사용 권한을 요청하는 데 사용됩니다.


ContextCompat.checkSelfPermission() 메서드를 호출하여 사용 권한을 확인하면 결과가 PackageManager.PERMISSION_GRANTED 로 확인됩니다. 여기서 나는 Snackbar 를 표시하여 권한이 부여되는지 여부를 보여줍니다. Snackbar 는 Toast 와 비슷한 하단에 표시되는 새로운보기이며 Snackbar 에 작업 단추를 추가 할 수도 있습니다.


사용 권한은 Activity , String 배열 및 요청 코드로 사용 권한을 전달하여 ActivityCompat.requestPermissions() 메서드를 사용하여 요청합니다. 결과는 onRequestPermissionsResult() 메소드를 사용하여 처리됩니다. 요청 코드가 확인되고 결과가 허가 여부에 관계없이 Snackbar에서 표시됩니다.


사용자가 한 번 권한을 거부 한 경우 다시 권한을 요청할 수 없습니다. 두 번째 실행에서는 사용자가 나중에 다시 묻지 않음 옵션을 통해 응용 프로그램이 나중에이 권한을 요청하지 못하도록합니다. 거부하기 전에이 옵션을 선택하면됩니다. 사용자는 앱 설정에서 권한을 명시 적으로 부여해야합니다. 다음에 requestPermissions 를 호출 할 때이 대화 상자는 더 이상이 종류의 사용 권한에 대해 표시되지 않습니다. 대신, 그것은 단지 아무것도하지 않습니다.


이를 위해 사용자에게 왜 그 사용 권한이 필요한지 설명 할 수 있습니다. 활동과 권한을 전달하여 ActivityCompat.shouldShowRequestPermissionRationale() 메소드를 사용하여 확인할 수 있습니다. true 반환하면 사용자가 권한을 거부하고 권한을 허용하도록 사용자에게 요청할 수 있습니다. 그렇지 않으면 해당 paricular 함수를 비활성화 할 수 있습니다.


권한 검사 프로세스는 checkPermission() 메소드로 래핑됩니다. 요청 권한 프로세스는 requestPermission() 메소드에 랩핑됩니다.


완전한 MainActivity 클래스는 아래와 같습니다.


import android.Manifest;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Context context;
    private Activity activity;
    private static final int PERMISSION_REQUEST_CODE = 1;
    String wantPermission = Manifest.permission.WRITE_EXTERNAL_STORAGE;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_permissions);

        context = this;
        activity = MainActivity.this;

        Button btnCheckPermission = (Button)findViewById(R.id.btnCheckPermission);
        Button btnRequestPermission = (Button)findViewById(R.id.btnRequestPermission);
        btnCheckPermission.setOnClickListener(this);
        btnRequestPermission.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        int id = v.getId();
        switch (id){
            case R.id.btnCheckPermission:
                if (checkPermission(wantPermission)) {
                    Toast.makeText(context, "Permission already granted.", Toast.LENGTH_LONG).show();
                } else {
                    Toast.makeText(context, "Please request permission.", Toast.LENGTH_LONG).show();
                }
                break;
            case R.id.btnRequestPermission:
                if (!checkPermission(wantPermission)) {
                    requestPermission(wantPermission);
                } else {
                    Toast.makeText(context,"Permission already granted.", Toast.LENGTH_LONG).show();
                }
                break;
        }
    }

    private boolean checkPermission(String permission){
        if (Build.VERSION.SDK_INT >= 23) {
            int result = ContextCompat.checkSelfPermission(context, permission);
            if (result == PackageManager.PERMISSION_GRANTED){
                return true;
            } else {
                return false;
            }
        } else {
            return true;
        }
    }

    private void requestPermission(String permission){
        if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)){
            Toast.makeText(context, "Write external storage permission allows us to write data. 
                Please allow in App Settings for additional functionality.",Toast.LENGTH_LONG).show();
        }
        ActivityCompat.requestPermissions(activity, new String[]{permission},PERMISSION_REQUEST_CODE);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case PERMISSION_REQUEST_CODE:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(context, "Permission Granted. Now you can write data.", 
                        Toast.LENGTH_LONG).show();
                } else {
                    Toast.makeText(context,"Permission Denied. You cannot write data.", 
                        Toast.LENGTH_LONG).show();
                }
                break;
        }
    }
}

권한이 부여 된 경우 grantResults[0] 의 값은 PERMISSION_GRANTED 됩니다. 그리고 허가가 철회되면 grantResults[0] 의 값은 PERMISSION_DENIED 됩니다.


결과


android_single_permission.png

여러 권한 요청


여러 사용 권한을 요청해야하는 시나리오를 생각해보십시오. 이를 위해 여러 대화 상자에서 여러 권한을 요청하는 대신 권한을 차례로 스크롤하는 단일 대화 상자에서 모든 권한을 요청할 수 있습니다. 이제 우리는 새로운 활동을 만들어이 사례를 테스트 할 것입니다.


AndroidManifest.xml 파일에 아래의 권한을 추가합니다.


<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"  />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Following is full code of updated MainActivity.

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    final String TAG = "DPG";
    private Context context;
    private Activity activity;
    private final static int ALL_PERMISSIONS_RESULT = 101;
    ArrayList<String> permissions = new ArrayList<>();
    ArrayList<String> permissionsToRequest;
    ArrayList<String> permissionsRejected = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_permissions);

        context = this;
        activity = MainActivity.this;

        Button btnCheckPermission = (Button)findViewById(R.id.btnCheckPermission);
        Button btnRequestPermission = (Button)findViewById(R.id.btnRequestPermission);
        btnCheckPermission.setOnClickListener(this);
        btnRequestPermission.setOnClickListener(this);

        permissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
        permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        permissionsToRequest = findUnAskedPermissions(permissions);
    }

    private ArrayList findUnAskedPermissions(ArrayList<String> wanted) {
        ArrayList result = new ArrayList();

        for (String perm : wanted) {
            if (!hasPermission(perm)) {
                result.add(perm);
            }
        }

        return result;
    }

    private boolean hasPermission(String permission) {
        if (canAskPermission()) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                return (checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED);
            }
        }
        return true;
    }

    private boolean canAskPermission() {
        return (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1);
    }


    @TargetApi(Build.VERSION_CODES.M)
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        switch (requestCode) {
            case ALL_PERMISSIONS_RESULT:
                Log.d(TAG, "onRequestPermissionsResult");
                for (String perms : permissionsToRequest) {
                    if (!hasPermission(perms)) {
                        permissionsRejected.add(perms);
                    }
                }

                if (permissionsRejected.size() > 0) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        if (shouldShowRequestPermissionRationale(permissionsRejected.get(0))) {
                            String msg = "These permissions are mandatory for the application. Please allow access."; 
                            showMessageOKCancel(msg,
                                    new DialogInterface.OnClickListener() {
                                        @Override
                                        public void onClick(DialogInterface dialog, int which) {
                                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                                                requestPermissions(permissionsRejected.toArray(
                                                        new String[permissionsRejected.size()]), ALL_PERMISSIONS_RESULT);
                                            }
                                        }
                                    });
                            return;
                        }
                    }
                } else {
                    Toast.makeText(context, "Permissions garanted.", Toast.LENGTH_LONG).show();
                }
                break;
        }
    }

    private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
        new AlertDialog.Builder(activity)
                .setMessage(message)
                .setPositiveButton("OK", okListener)
                .setNegativeButton("Cancel", null)
                .create()
                .show();
    }

    @TargetApi(Build.VERSION_CODES.M)
    @Override
    public void onClick(View v) {
        permissionsToRequest = findUnAskedPermissions(permissions);

        switch (v.getId()){
            case R.id.btnCheckPermission:
                if (permissionsToRequest.size() == 0) {
                    Toast.makeText(context, "Permissions already granted.", Toast.LENGTH_LONG).show();
                } else {
                    Toast.makeText(context, "Please request permissions.", Toast.LENGTH_LONG).show();
                }
                break;
            case R.id.btnRequestPermission:
                if (permissionsToRequest.size() > 0) {
                    requestPermissions(permissionsToRequest.toArray(new String[permissionsToRequest.size()]),
                            ALL_PERMISSIONS_RESULT);
                } else {
                    Toast.makeText(context,"Permissions already granted.", Toast.LENGTH_LONG).show();
                }
                break;
        }
    }


}
android_multiple_permission.png

android_multiple_permission.png


지원 라이브러리를 사용하여 코드 포워드 호환 가능


위의 코드는 Android 6.0 Marshmallow에서 완벽하게 작동하지만 API 레벨 23에서 호출 된 함수가 추가되었으므로 안드로이드 프리 -Marshmallow에서 충돌이 발생한다는 것은 불행한 일입니다.


바로 아래의 코드로 빌드 버전을 확인할 수 있습니다.


if (Build.VERSION.SDK_INT >= 23) {
    // Marshmallow+
} else {
    // Pre-Marshmallow
}


그러나 코드는 더욱 복잡해질 것입니다. 따라서 이미이 라이브러리를 준비하고있는 Support Library v4의 도움을 받으시기 바랍니다. 다음 기능을 사용하십시오.


ContextCompat.checkSelfPermission() . M에 관계없이 응용 프로그램이 실행됩니다. 권한이 부여되면이 함수는 PERMISSION_GRANTED 를 올바르게 반환합니다. 그렇지 않으면 PERMISSION_DENIED 가 반환됩니다.

ActivityCompat.requestPermissions() . 이 함수가 pre-M에서 호출되면 OnRequestPermissionsResultCallback 은 올바른 PERMISSION_GRANTED 또는 PERMISSION_DENIED 결과와 함께 갑자기 호출됩니다.

ActivityCompat.shouldShowRequestPermissionRationale() . 이 함수가 pre-M에서 호출되면 항상 false 반환합니다.



Managing Permissions using ADB

Permissions can also be managed on the command-line using adb with the following commands.

Show all Android permissions:

adb shell pm list permissions -d -g

Dumping app permission state:

adb shell dumpsys package com.PackageName.enterprise

Granting and revoking runtime permissions:

adb shell pm grant com.PackageName.enterprise some.permission.NAME
adb shell pm revoke com.PackageName.enterprise android.permission.READ_CONTACTS

Installing an app with all permissions granted:

adb install -g myAPP.apk


런타임 권한을 처리하는 EasyPermissions


EasyPermissions 는 Android M 이상을 타겟팅 할 때 기본 시스템 권한 논리를 단순화하는 래퍼 라이브러리입니다. EasyPermissions를 사용하여 앱에 이미 필요한 권한이 있는지 확인하십시오. 이 메소드는 최종 인수로서 많은 수의 권한을 가질 수 있습니다.


EasyPermissions는 build.gradle 파일에 다음 종속성을 추가하여 설치됩니다.


 EasyPermissions is a wrapper library to simplify basic system permissions logic when targeting Android M or higher. Using EasyPermissions to check if the app already has the required permissions. This method can take any number of permissions as its final argument.

EasyPermissions is installed by adding the following dependency to your build.gradle file:

dependencies {
    implementation 'com.android.support:design:27.0.1'
    implementation 'pub.devrel:easypermissions:1.2.0'
}

To begin using EasyPermissions, have your Activity (or Fragment) override the onRequestPermissionsResultmethod:

public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks {
    private static final int RC_PERM_LOCATION_STORAGE = 124;
    private static final int RC_PERM_LOCATION = 125;
    private static final int RC_SETTINGS = 126;

    private String wantedPerm = Manifest.permission.ACCESS_FINE_LOCATION;
    private String[] wantedPerms = {Manifest.permission.ACCESS_FINE_LOCATION, 
        Manifest.permission.WRITE_EXTERNAL_STORAGE};
    private String PERM_RATIONALE = "This app needs access to your location.";

    private Activity activity = MainActivity.this;
    private String TAG = FontsActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fonts);

        if (EasyPermissions.hasPermissions(activity, wantedPerm)) {
            Log.d(TAG, "Already have permission, do the thing");
        } else {
            EasyPermissions.requestPermissions(activity, PERM_RATIONALE, RC_PERM_LOCATION, wantedPerm);
        }

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        // Forward results to EasyPermissions
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

    @Override
    public void onPermissionsGranted(int requestCode, List perms) {
        Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size());
    }

    @Override
    public void onPermissionsDenied(int requestCode, List perms) {
        Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size());
        if (EasyPermissions.somePermissionPermanentlyDenied(activity, perms)) {
            new AppSettingsDialog.Builder(activity)
                    .setTitle("Permissions Required")
                    .setPositiveButton("Settings")
                    .setNegativeButton("Cancel")
                    .setRequestCode(RC_SETTINGS)
                    .build()
                    .show();
        }
    }

    @AfterPermissionGranted(RC_PERM_LOCATION)
    private void afterLocationPermission() {
        if (EasyPermissions.hasPermissions(this, wantedPerm)) {
            Log.d(TAG, "Already have permission, do the thing");
        } else {
            Log.d(TAG, "Do not have permission, request them now");
            EasyPermissions.requestPermissions(activity, PERM_RATIONALE, RC_PERM_LOCATION, wantedPerm);
        }
    }   
}

There are a few things to note:

  • Using EasyPermissions#hasPermissions() to check if the app already has the required permissions. This method can take any number of permissions as its final argument.
  • Requesting permissions with EasyPermissions#requestPermissions. This method will request the system permissions and show the rationale string provided if necessary. The request code provided should be unique to this request, and the method can take any number of permissions as its final argument.
  • Use of the AfterPermissionGranted annotation. This is optional, but provided for convenience. If all of the permissions in a given request are granted, any methods annotated with the proper request code will be executed. This is to simplify the common flow of needing to run the requesting method after all of its permissions have been granted. This can also be achieved by adding logic on the onPermissionsGrantedcallback.

Handle Android Runtime Permissions in Kotlin way

Let's use PermissionsKt for runtime permission in Kotlin way.

PermissionsKt is published with JitPack.io. To add this library to your project, add these lines to your build.gradle.

repositories {
    maven { url "https://jitpack.io" }
}

dependencies {
    implementation 'com.github.sembozdemir:PermissionsKt:1.0.0'
}

Add the permission to AndroidManifest.xml.

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Just ask for permission you want anywhere on your Activity.

askPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE) {
    onGranted {
        writeFile()
    }
}

Call delegated extension function on onRequestPermissionsResult.

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, 
        grantResults: IntArray) {
    handlePermissionsResult(requestCode, permissions, grantResults)
}

You may also listen onDeniedonShowRationaleonNeverAskAgain callbacks if you need.

Following is a full code for MainActivity.

class MainActivity : AppCompatActivity() {
    private val wantedPerm = Manifest.permission.WRITE_EXTERNAL_STORAGE
    private val TAG = MainActivity::class.java.name;

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        askPermissions(wantedPerm) {
            onGranted {
                writeFile()
            }

            onDenied {
                Log.d(TAG, "WRITE_EXTERNAL_STORAGE permission is denied.")
            }

            onShowRationale { request ->
                Snackbar.make(rootView, "You should grant WRITE_EXTERNAL_STORAGE permission", 
                    Snackbar.LENGTH_INDEFINITE)
                        .setAction("Retry") { request.retry() }
                        .show()
            }

            onNeverAskAgain {
                Log.d(TAG, "Never ask again for WRITE_EXTERNAL_STORAGE permission")
            }
        }
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, 
                           grantResults: IntArray) {
        handlePermissionsResult(requestCode, permissions, grantResults)
    }

    private fun writeFile() {
        longToast("Write file")
    }
}