Command Palette

Search for a command to run...

Hướng dẫn xin quyền truy cập (Permissions) trong React Native

Hướng dẫn xin Quyền truy cập (Permissions) trong React Native

Từ đầu series đến giờ, chúng ta đã làm việc với giao diện, với chuyển trang và dữ liệu mạng. Nhưng có một thứ làm nên sự khác biệt hoàn toàn giữa ứng dụng Mobile (Native App) và một trang Web: Khả năng điều khiển phần cứng.

Bạn muốn ứng dụng chụp một bức ảnh? Lấy tọa độ GPS để gọi xe? Hay lưu một file vào bộ nhớ? Để làm được những điều đó, bạn phải vượt qua "người gác cổng" cực kỳ khó tính mang tên: Hệ điều hành (iOS & Android).

Trong bài này, chúng ta sẽ học cách xin phép (Request Permissions) người dùng một cách thanh lịch, chuyên nghiệp và tránh mọi rủi ro khiến ứng dụng bị văng (Crash).

1. Tại sao việc xin quyền lại đáng sợ?

Hệ điều hành iOS của Apple và Android của Google được thiết kế với tính năng bảo mật quyền riêng tư đặt lên hàng đầu.

Nếu ứng dụng của bạn cố tình bật Camera hoặc lấy vị trí GPS mà chưa được người dùng cho phép, hệ điều hành sẽ ngay lập tức "rút điện" và đóng băng (crash) ứng dụng của bạn ngay lập tức để bảo vệ người dùng.

Nguyên tắc vàng của Permissions:

  1. Trạng thái mặc định luôn là: Chưa xác định (Undetermined) hoặc Từ chối (Denied).
  2. Bạn chỉ có thể hiển thị hộp thoại xin quyền (Popup) MỘT LẦN DUY NHẤT trên iOS. Nếu người dùng lỡ tay bấm "Từ chối", hộp thoại đó sẽ vĩnh viễn không bao giờ hiện lên nữa.
  3. Nếu muốn dùng tính năng, bạn BẮT BUỘC phải kiểm tra trạng thái quyền trước khi thực thi lệnh.

2. Sự "tuyệt chủng" của thư viện expo-permissions

Nếu bạn tìm kiếm các bài viết hướng dẫn cũ trên mạng (từ năm 2021 trở về trước), bạn sẽ thấy người ta sử dụng một thư viện chung tên là expo-permissions để xin mọi loại quyền.

Xin lưu ý: Thư viện này ĐÃ BỊ XÓA BỎ trong các phiên bản Expo mới nhất!

Tại sao Expo lại làm vậy? Vì Google và Apple yêu cầu ứng dụng chỉ được khai báo những quyền nó thực sự dùng. Nếu bạn cài một thư viện xin quyền "tất cả trong một", ứng dụng của bạn sẽ bị hệ thống nghi ngờ và đánh rớt khi duyệt lên App Store.

Cách làm hiện đại (Chuẩn Expo mới nhất): Bạn dùng tính năng nào, thư viện của tính năng đó sẽ tự đi kèm hàm xin quyền.

  • Muốn dùng GPS? Cài expo-location và dùng hàm xin quyền của nó.
  • Muốn chụp ảnh? Cài expo-camera và dùng hàm xin quyền của nó.

3. Thực hành: Xin quyền vị trí (GPS)

Hãy cùng thực hành với một trong những quyền phổ biến nhất: Quyền lấy tọa độ GPS.

Xin quyền truy cập vị trí (GPS)

Bước 1: Cài đặt thư viện vị trí

Mở Terminal và chạy lệnh:

npx expo install expo-location

Bước 2: Viết logic xin quyền và lấy tọa độ

Chúng ta sẽ xây dựng một màn hình có nút bấm. Khi bấm vào, app sẽ xin quyền. Nếu thành công, app sẽ in ra kinh độ và vĩ độ hiện tại.

import React, { useState } from 'react'
import { View, Text, TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native'
import * as Location from 'expo-location' // 1. Import thư viện

export default function LocationScreen() {
  const [location, setLocation] = useState(null)
  const [errorMsg, setErrorMsg] = useState(null)
  const [isFetching, setIsFetching] = useState(false)

  const getLocation = async () => {
    setIsFetching(true)
    setErrorMsg(null)

    try {
      // 2. Yêu cầu quyền truy cập Vị trí (Khi app đang mở - Foreground)
      const { status } = await Location.requestForegroundPermissionsAsync()

      // 3. Kiểm tra xem người dùng có đồng ý không
      if (status !== 'granted') {
        setErrorMsg('Bạn đã từ chối cấp quyền vị trí. Không thể thực hiện chức năng này!')
        setIsFetching(false)
        return // Dừng lại ngay, không lấy tọa độ nữa
      }

      // 4. Nếu đồng ý, bắt đầu lấy tọa độ (Có thể mất 1-2 giây)
      const currentLocation = await Location.getCurrentPositionAsync({})
      setLocation(currentLocation.coords)
    } catch (error) {
      setErrorMsg('Đã có lỗi xảy ra khi lấy vị trí.')
    } finally {
      setIsFetching(false)
    }
  }

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Tính Năng Tìm Quanh Đây</Text>

      <TouchableOpacity style={styles.button} onPress={getLocation} disabled={isFetching}>
        <Text style={styles.buttonText}>{isFetching ? 'Đang định vị...' : '📍 Lấy Tọa Độ Của Tôi'}</Text>
      </TouchableOpacity>

      {/* Hiển thị vòng xoay nếu đang tải */}
      {isFetching && <ActivityIndicator size="large" color="#0984e3" style={{ marginTop: 20 }} />}

      {/* Hiển thị lỗi nếu bị từ chối */}
      {errorMsg && <Text style={styles.errorText}>{errorMsg}</Text>}

      {/* Hiển thị tọa độ nếu thành công */}
      {location && (
        <View style={styles.resultBox}>
          <Text style={styles.resultText}>Vĩ độ: {location.latitude}</Text>
          <Text style={styles.resultText}>Kinh độ: {location.longitude}</Text>
        </View>
      )}
    </View>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 },
  title: { fontSize: 22, fontWeight: 'bold', marginBottom: 30 },
  button: { backgroundColor: '#00b894', paddingVertical: 12, paddingHorizontal: 25, borderRadius: 10 },
  buttonText: { color: 'white', fontSize: 16, fontWeight: 'bold' },
  errorText: { color: '#d63031', marginTop: 20, textAlign: 'center' },
  resultBox: { marginTop: 30, padding: 20, backgroundColor: '#dfe6e9', borderRadius: 10 },
  resultText: { fontSize: 16, fontWeight: '500', marginVertical: 5 },
})

4. Nghệ thuật cứu vãn: Khi người dùng lỡ bấm "Từ chối"

Như đã nói ở trên, nếu người dùng bấm "Từ chối", hệ điều hành sẽ chặn không cho hộp thoại xin quyền hiện lên ở các lần bấm tiếp theo. Vậy ứng dụng của bạn coi như "vứt đi" tính năng đó sao?

Giải pháp (Best Practice) ở đây là: Điều hướng người dùng vào thẳng trang Cài đặt (Settings) của hệ điều hành để họ tự bật lại bằng tay.

React Native cung cấp thư viện Linking để làm việc này cực kỳ dễ dàng. Hãy bổ sung logic này vào phần kiểm tra trạng thái quyền ở trên:

import { Linking, Alert } from 'react-native' // Bổ sung Linking và Alert

// ... (Bên trong hàm getLocation) ...
const { status } = await Location.requestForegroundPermissionsAsync()

if (status !== 'granted') {
  // Thay vì chỉ hiện dòng chữ lỗi, hãy hiển thị Hộp thoại hướng dẫn
  Alert.alert(
    'Thiếu Quyền Truy Cập',
    'Ứng dụng cần quyền vị trí để tìm kiếm quanh bạn. Hãy vào Cài đặt và cấp quyền cho ứng dụng nhé!',
    [
      { text: 'Bỏ qua', style: 'cancel' },
      { text: 'Vào Cài Đặt', onPress: () => Linking.openSettings() }, // Nút thần thánh
    ],
  )
  return
}

Hàm Linking.openSettings() sẽ lập tức thoát ứng dụng và mở đúng trang cấu hình quyền của ứng dụng bạn bên trong máy điện thoại. Đây là trải nghiệm UX (User Experience) tuyệt vời nhất mà mọi app chuyên nghiệp đều phải có!

5. Khi nào thì nên xin quyền? (Quy tắc vàng UX)

Rất nhiều ứng dụng mắc một sai lầm chết người: Vừa mở app lên là quăng liên tục 3-4 cái popup xin quyền GPS, Camera, Thông báo. Người dùng sẽ cảm thấy bị đe dọa, khó chịu và 90% họ sẽ bấm "Từ chối tất cả" hoặc xóa luôn app.

Hãy tuân thủ quy tắc: Chỉ xin quyền khi người dùng thực sự cần đến nó.

  • Đừng xin quyền GPS ở trang Đăng nhập. Hãy xin khi họ bấm nút "Tìm cửa hàng gần tôi".
  • Đừng xin quyền Camera ở Trang Chủ. Hãy xin khi họ bấm nút "Chụp ảnh đại diện".
  • Hãy hiển thị một dòng Text/Modal giải thích rõ lý do tại sao ứng dụng lại cần quyền đó trước khi kích hoạt hệ thống xin quyền của điện thoại.

Kết luận: Quản lý quyền truy cập là một nghệ thuật

Quản lý quyền truy cập không chỉ là vấn đề kỹ thuật (để tránh Crash app), mà còn là nghệ thuật giao tiếp với người dùng. Hãy ghi nhớ:

  1. Luôn dùng hàm kiểm tra quyền đi kèm với thư viện tính năng (như expo-location).
  2. Luôn xử lý trường hợp bị từ chối bằng Linking.openSettings().
  3. Chỉ xin quyền khi tính năng đó chuẩn bị được kích hoạt.

Bây giờ ứng dụng của bạn đã biết cách "gõ cửa" hệ điều hành một cách lịch sự. Ở bài tiếp theo, chúng ta sẽ chính thức bước qua cánh cửa đó để xây dựng tính năng hấp dẫn nhất của mọi mạng xã hội: Chụp và đăng tải hình ảnh!

Bài viết liên quan

Hướng dẫn tạo Bottom Tabs & Drawer Navigation trong Expo Router

Hướng dẫn chi tiết cách tạo thanh menu Bottom Tabs và menu vuốt Drawer Navigation cực mượt với Expo Router. Nắm vững bí mật của file _layout.tsx.

Cách làm chủ StyleSheet & Flexbox Layout trong React Native

Nắm vững nghệ thuật tạo kiểu với StyleSheet và làm chủ hệ thống Flexbox trong React Native. Hướng dẫn chi tiết flexDirection, justifyContent, alignItems.

Hướng dẫn tích hợp Push Notifications với React Native và Expo

Hướng dẫn chi tiết cách cài đặt và cấu hình Push Notifications trong React Native sử dụng hệ thống Expo. Nắm vững cách xin quyền, lấy Token và gửi thông báo kiểm thử.

Expo Router là gì? Kỷ nguyên File-Based Routing trong React Native

Bối rối với Navigation trong React Native? Bài viết giải thích chi tiết khái niệm File-based routing của Expo Router, cấu trúc thư mục app/ và sức mạnh của nó.