При создании новых приложении утомляет каждый раз писать код для запроса runtime permissions у пользователя. Недавно нашел удобную библиотеку, уменьшающую бойлерплейт до пары строк, и решил написать шпаргалку.

Рассказ будет о библиотеке https://permissions-dispatcher.github.io/PermissionsDispatcher/

Исходная задача такая: в MainActivity нужно вызывать метод start(), предварительно запросив разрешение на определение точного местоположения.

class MainActivity : AppCompatActivity() {
    ....

    fun start() {
        Snackbar.make(fab, "Visa-Visa-Fly-Fly", Snackbar.LENGTH_LONG).show()
    }
}

Итак. Все разрешения, даже запрашиваемые явно, должны быть в AndroidManifest.xml:

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

Если еще не включен процессор аннтоаций kapt, то не забыаем включить его в build.gradle:

apply plugin: 'kotlin-kapt'

Там же подключаем библиотеку, на момент последнего редактирования статьи permissionsDispatcher = "3.2.1" (в момент написания была 3.1.0).

dependencies {
    ....
    implementation("com.github.hotchemi:permissionsdispatcher:${permissionsDispatcher}") {
        // if you don't use android.app.Fragment you can exclude support for them
        exclude module: "support-v13"
    }
    kapt "com.github.hotchemi:permissionsdispatcher-processor:${permissionsDispatcher}"
}

Добавляем аннотацию к MainActivity и делаем Rebuild Project:

@RuntimePermissions
class MainActivity : AppCompatActivity() {
    ....
}

После этого будет автоматически создан файл MainActivityPermissionsDispatcher.kt с расширениями для MainActivity.

Теперь нужно в MainActivity переопределить унаследованный метод onRequestPermissionsResult() и добавить в него вызов сгенерированной функции с тем же названием. Сгенерированный код никак не влияет на поведение самой Activity, вызов мы должны обеспечить сами.

@SuppressLint("NeedOnRequestPermissionsResult")
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    onRequestPermissionsResult(requestCode, grantResults)
}

Выглядит как “масло маслянное”, конечно. Чтобы не запутаться, обратите внимание на количество параметров у разных onRequestPermissionsResult(), и из любопытства можете сделать Go to declaration на обоих, сразу станет все понятно.

Ко всем методам, вызов который требует проверки и запроса разрешений, нужно добавить аннотацию со списком требуемых разрешений (и опять не забыть Rebuild Project):

@NeedsPermission(Manifest.permission.ACCESS_FINE_LOCATION)
fun start() {
    ....
}

Для всех методов с аннотацией @NeedsPermission будет создан метод-близнец, запрашивающий разрешения (это тоже расширение, не поленитесь заглянуть в исходник). Его нужно вызывать вместо исходного метода в нужный нам момент:

fab.setOnClickListener { view ->
    startWithPermissionCheck()
}

Программа-минимум выполнена. Запускаем, наслаждаемся.

Также можно аннотациями объявить функции, которые будут вызваны перед запросом разрешения (обычно для того, чтобы объяснить пользователю необходимость предоставить это разрешение), и в случае, если пользователь разрешение дать отказался (чтобы объяснить необходимость еще раз). Подробности в документации.

Исходный код примера.