Как проверить и запросить разрешения (permissions) во фрагменте (Fragment) Android на Kotlin?
Проблема и правильное решение
Вы столкнулись с классической проблемой Android-разработки. Старый метод onRequestPermissionsResult действительно устарел и может работать нестабильно во фрагментах, особенно если вы используете библиотеки, которые перехватывают результат (например, Navigation Component или ViewPager). Правильный и современный подход — использовать Activity Result API, который был добавлен в AndroidX Activity 1.2.0 и Fragment 1.3.0.
Activity Result API предоставляет типобезопасный способ запроса разрешений и получения результатов. Вам больше не нужно переопределять onRequestPermissionsResult — вы просто регистрируете "контракт" на запрос разрешения и получаете результат через лямбду или колбэк.
Пошаговая реализация
Вот как правильно запросить разрешение камеры во фрагменте с использованием Activity Result API:
class CameraFragment : Fragment() {
private lateinit var binding: FragmentCameraBinding
// 1. Создаём контракт для запроса разрешения
private val requestCameraPermission = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
// Разрешение получено — открываем камеру
openCamera()
} else {
// Разрешение не получено — показываем сообщение
Toast.makeText(requireContext(), "Разрешение на камеру не получено", Toast.LENGTH_SHORT).show()
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentCameraBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonOpenCamera.setOnClickListener {
// 2. Проверяем, есть ли уже разрешение
if (ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
) {
openCamera()
} else {
// 3. Если разрешения нет — запрашиваем его
requestCameraPermission.launch(Manifest.permission.CAMERA)
}
}
}
private fun openCamera() {
// Логика открытия камеры
Toast.makeText(requireContext(), "Камера открыта", Toast.LENGTH_SHORT).show()
}
// ВАЖНО: метод onRequestPermissionsResult больше НЕ НУЖЕН!
}
Почему это работает и в чём преимущества
- Типобезопасность: Вы передаёте конкретное разрешение (String) и получаете Boolean. Нет риска перепутать requestCode.
- Локализация во фрагменте: Результат приходит непосредственно во фрагмент, а не в Activity. Вам не нужно писать код в Activity для передачи результата.
- Совместимость с жизненным циклом:
registerForActivityResultавтоматически отменяет регистрацию, когда фрагмент уничтожается. Это предотвращает утечки памяти. - Нет необходимости в override: Код становится чище и короче.
Дополнительно: объяснение пользователю (Rationale)
Иногда пользователь может один раз отклонить разрешение. В этом случае стоит показать диалог с объяснением, зачем нужно разрешение. Activity Result API позволяет легко обработать этот сценарий:
private val requestCameraPermission = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
openCamera()
} else {
// Проверяем, нужно ли показать объяснение
if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
// Показываем диалог с объяснением
AlertDialog.Builder(requireContext())
.setTitle("Разрешение на камеру")
.setMessage("Для работы приложения необходим доступ к камере. Пожалуйста, разрешите его в настройках.")
.setPositiveButton("OK") { _, _ ->
requestCameraPermission.launch(Manifest.permission.CAMERA)
}
.setNegativeButton("Отмена", null)
.show()
} else {
Toast.makeText(requireContext(), "Разрешение отклонено навсегда", Toast.LENGTH_SHORT).show()
}
}
)
Важные замечания
- Не забудьте добавить разрешение в
AndroidManifest.xml:<uses-permission android:name="android.permission.CAMERA" /> - Если вы используете несколько разрешений одновременно, используйте
ActivityResultContracts.RequestMultiplePermissions()— он возвращаетMap<String, Boolean>. - Для API < 23 (Android 6.0) разрешения выдаются автоматически при установке, поэтому проверка не нужна. Activity Result API сам обрабатывает это.
Теперь ваш код будет работать надёжно во всех сценариях, и вам не придётся беспокоиться о передаче результатов между Activity и Fragment.
<<Альтернативный подход: использование делегата из библиотеки KTX
Если вы используете Kotlin и хотите ещё более лаконичный код, можно воспользоваться библиотекой AndroidX Activity KTX и Kotlin-делегатами. Однако учтите, что этот способ требует дополнительной зависимости в build.gradle:
implementation "androidx.activity:activity-ktx:1.8.0"
Вот пример использования с делегатом rememberLauncherForActivityResult (этот подход больше подходит для Jetpack Compose, но может быть адаптирован и для View-фрагментов):
class CameraFragment : Fragment() {
private val cameraPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
openCamera()
}
}
// ... остальной код
}
На самом деле, это тот же Activity Result API, просто оформленный через функцию-расширение. Разница минимальна, но если вы привыкли к функциональному стилю, можете использовать ActivityResultLauncher напрямую без registerForActivityResult в некоторых случаях, передавая его как параметр.
Когда это может быть полезно?
- Если у вас много фрагментов с одинаковыми запросами разрешений — вы можете вынести логику в базовый класс или утилитарную функцию.
- Если вы используете архитектуру MVVM и хотите запрашивать разрешения из ViewModel — в этом случае Activity Result API не подходит (ViewModel не имеет доступа к контексту Activity), и нужно использовать другие подходы (например, Event-ы).
В любом случае, основной рекомендуемый способ для фрагментов — это
Ответов пока нет.
Войдите чтобы оставить ответ