Android Studio: урок по Kotlin – запрашиваем разрешения

Камера, микрофон, местоположение — ко многим функциям телефона просто так не подступиться. Android требует явного согласия пользователя, и пока вы не научитесь правильно запрашивать разрешения, приложение будет выдавать ошибки или молча закрываться. В этом уроке мы разберём, как с помощью ActivityResultContracts и Kotlin сделать запрос разрешения простым и надёжным.


Зачем нужны разрешения и как они работают

Google защищает пользователя: приложение не может без спросу включить камеру или прочитать контакты. Поэтому придумали систему разрешений. Одни даются автоматически (интернет), другие требуют, чтобы вы попросили и пользователь нажал «Разрешить». Без такого диалога опасное действие просто не сработает. А если попытаться выполнить его без проверки, приложение упадёт с SecurityException.


Добавляем разрешение в манифест

Первым делом любое разрешение нужно объявить в AndroidManifest.xml. Без этой записи даже запрос не появится. Например, для доступа к камере добавьте внутрь тега <manifest>:

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

После этого при установке приложения пользователь увидит, что вы хотите работать с камерой, но сам запрос ещё не сделан — его нужно выполнять отдельно во время работы программы.


Проверяем, дал ли пользователь разрешение

Перед запросом нужно убедиться, что разрешения ещё нет. Для этого вызываем ContextCompat.checkSelfPermission. Если метод вернул PERMISSION_GRANTED, значит всё в порядке, и можно сразу переходить к делу.

if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
    == PackageManager.PERMISSION_GRANTED) {
    // камера доступна
} else {
    // будем запрашивать
}

Регистрируем контракт на запрос разрешения

Раньше использовали устаревший requestPermissions с колбэком в onRequestPermissionsResult. Современный Android Studio рекомендует Activity Result API — лаунчер, который вы объявляете один раз, а потом просто вызываете launch(). Для одного разрешения есть готовый контракт RequestPermission.

private val requestPermissionLauncher =
    registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
        if (isGranted) {
            // разрешение получено
        } else {
            // пользователь отказал
        }
    }

Обратите внимание: лаунчер регистрируется до вызова onCreate, как свойство класса. Иначе при повороте экрана он потеряется.


Запрашиваем разрешение у пользователя

Когда нужно получить доступ, вызываем launch() с нужным разрешением. Система покажет стандартный диалог с кнопками «Разрешить» и «Отклонить».

requestPermissionLauncher.launch(Manifest.permission.CAMERA)

Результат придёт в ту лямбду, которую вы передали при регистрации лаунчера. Там вы можете сразу открыть камеру или показать сообщение, что без разрешения нельзя продолжить.


Запрос нескольких разрешений сразу

Иногда нужны сразу два-три разрешения, например, для геолокации: и точное, и приближённое местоположение. Для этого есть контракт RequestMultiplePermissions. Он возвращает Map<String, Boolean>, где для каждого разрешения указан результат.

private val requestMultiplePermissions =
    registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
        val allGranted = permissions.values.all { it }
        if (allGranted) {
            // все разрешения получены
        } else {
            // хотя бы одно отклонено
        }
    }

// вызов
requestMultiplePermissions.launch(
    arrayOf(
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION
    )
)

Если пользователь отказал: объясняем зачем

Если пользователь нажал «Отклонить», повторный запрос можно выполнить, но после нескольких отказов система может пометить разрешение как «Больше не спрашивать». В этом случае диалог больше не появится, и пользователю придётся вручную зайти в настройки приложения. Чтобы этого избежать, перед повторным запросом покажите объяснение — метод shouldShowRequestPermissionRationale.

if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
    // покажите диалог с объяснением, почему нужно разрешение
    AlertDialog.Builder(this)
        .setTitle("Доступ к камере")
        .setMessage("Камера нужна для сканирования QR-кодов")
        .setPositiveButton("Дать разрешение") { _, _ ->
            requestPermissionLauncher.launch(Manifest.permission.CAMERA)
        }
        .show()
} else {
    // либо запрашиваем впервые, либо пользователь поставил "Больше не спрашивать"
    requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}

Такой подход повышает шанс получить согласие и не раздражает пользователя пустыми запросами.


Полный пример: открытие камеры после согласия

Давайте объединим всё в одной Activity. При нажатии кнопки проверим разрешение, если его нет — запросим. Когда получен положительный ответ, откроем камеру через Intent.

class CameraActivity : AppCompatActivity() {
    private val cameraLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
            if (granted) openCamera()
        }

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

        btnOpenCamera.setOnClickListener {
            when {
                ContextCompat.checkSelfPermission(
                    this, Manifest.permission.CAMERA
                ) == PackageManager.PERMISSION_GRANTED -> openCamera()
                ActivityCompat.shouldShowRequestPermissionRationale(
                    this, Manifest.permission.CAMERA
                ) -> showRationaleDialog()
                else -> cameraLauncher.launch(Manifest.permission.CAMERA)
            }
        }
    }

    private fun openCamera() {
        // запуск намерения камеры
    }

    private fun showRationaleDialog() {
        AlertDialog.Builder(this)
            .setMessage("Камера используется для фото")
            .setPositiveButton("OK") { _, _ -> cameraLauncher.launch(Manifest.permission.CAMERA) }
            .show()
    }
}

Такой шаблон можно копировать для любых опасных разрешений: микрофон, контакты, местоположение.


Коротко об освоенном

  • Опасные разрешения нужно объявлять в манифесте и запрашивать во время выполнения.
  • ActivityResultContracts.RequestPermission — современный способ запроса одного разрешения.
  • RequestMultiplePermissions — для нескольких разрешений сразу.
  • Метод shouldShowRequestPermissionRationale помогает показать объяснение перед повторным запросом.
  • Всегда проверяйте, дано ли уже разрешение, чтобы не вызывать диалог повторно.

Теперь вы умеете корректно запрашивать разрешения в приложении на Kotlin. Этот навык — один из обязательных для каждого разработчика. Сделайте учебное приложение, которое запрашивает доступ к камере и делает фотографию — так вы закрепите материал и получите готовый компонент для будущих проектов.