- 해당 문제에서는 안드로이드 12 이하에서는 POST_NOTIFICATIONS 속성으로 알람에 대한 권한 체크가 불가능함이 확인되었습니다. - 아래와 같이 앱에 대한 여러 가지 권한을 체크하는 로직입니다. 해당 로직에서 ‘granted’ 상태가 아닌 경우에는 배열을 반환해 주는 형태입니다.
/**
* 권한 체크에서 모두 '허용(granted)'을 하였는지 여부를 체크합니다.
*
* @param {Permission[} permsCodeArr
* @returns {Promise<Permission[]>} 권한 중 '허용'을 모두 한 경우 빈 배열이며 '허용'을 하지 않으면 배열로 반환합니다.
*/
const cmmPermsArrCheck = async (permsCodeArr: Permission[]): Promise<Permission[]> => {
let notGrantedArr: Permission[] = [];
// [STEP1] 전달 받은 배열을 순회하면서 권한을 허용했는지 체크합니다.
for (let permsItem of permsCodeArr) {
// [STEP2] 동일한 플랫폼(andriod, ios)의 코드가 아니면 undefined가 발생합니다. 이를 제외하고 수행합니다.
if (permsItem != undefined) {
//[STEP3] react-native-permissions 함수를 이용해 권한을 체크합니다.
let permsCheck = await check(permsItem);
switch (permsCheck) {
// [CASE1] 권한 상태가 수락(granted) 상태인 경우
case "granted":
break;
// [CASE2] 권한 상태가 수락되지 않은 상태 : 배열을 저장합니다.
case "blocked":
case "denied":
case "limited":
case "unavailable":
notGrantedArr.push(permsItem);
break;
}
}
}
return notGrantedArr;
}
💡 아래와 같이 알람에 대한 권한에 대해 분기 처리 없이 POST_NOTIFICATIONS를 확인한 뒤 통과 여부를 반환 받는것을 확인하고 있습니다.
- 우선적으로 안드로이드 13 이상 버전의 경우는 아래의 문서와 같이 알림에 대한 권한이 중지된 상태로 시작이 됩니다. - 즉 안드로이드 12에서는 앱을 설치하면 앱에 대한 알림 권한이 켜진 상태가 되었지만, 안드로이드 13에서는 앱을 설치하여 실행하면 앱에 대한 알림 권한이 꺼진 상태로 시작이 됩니다.
💡 해당 함수에서는 안드로이드이며 12 이하 버전의 경우 checkNotifications() 함수를 통해 status 상태를 확인합니다.
const devicesOS = DeviceInfo.getSystemName(); // 디바이스의 시스템 이름을 반환받습니다.(Android or iOS)
const devicesOsVer = parseInt(DeviceInfo.getSystemVersion()); // 디바이스의 OS 버전을 확인합니다.
if (deviceOS === "Android" && deviceOsVer < 13) {
await checkNotifications()
.then(({ status, settings }) => {
console.log("안드로이드 12 버전의 경우 알람 권한 체크 상태 :: ", status)
});
}
1.1. 결과 확인
💡 결과 확인
- 안드로이드 12 버전의 경우 POST_NOTIFICATIONS로 권한 체크를 했던 부분은 여전히 ‘unavailable’이 출력이 되었지만 checkNotifications()를 이용하여 체크하였을 시 권한 허용(granted)나 권한 차단(blocked)이 출력이 잘됨을 확인되었습니다.
2. 알람에 대한 요청처리 : 설정 페이지로 이동
💡 알람에 대한 요청처리 : 설정 페이지로 이동 - 안드로이드 12 이하에서는 알람 속성 인 ‘POST_NOTIFICATIONS’에 대해 사용할 수 없으므로 권한을 확인하면 설정하는 페이지로 이동하여 알람을 허용하도록 해야합니다.
💡 toggleAlarm
1. 안드로이드 12 이하일 때의 버전처리
- react-native-permissions 라이브러리의 checkNotifications() 메서드를 호출하여 권한을 한번 더 체크합니다. - 만약 권한이 허용상태가 아니면 Linking.openSettings()를 통해서 설정 페이지로 이동시킵니다.
2. 안드로이드 13 이상일 때의 버전 처리
- android.permission.POST_NOTIFICATIONS 권한에 따라 권한을 체크하고 각각에 따라 처리를 수행합니다.
/**
* 알람 스위치를 눌렀을때의 동작 처리
*/
const toggleAlarm = async () => {
const devicesOS = DeviceInfo.getSystemName(); // 디바이스의 시스템 이름을 반환받습니다.(Android or iOS)
const devicesOsVer = parseInt(DeviceInfo.getSystemVersion()); // 디바이스의 OS 버전을 확인합니다.
// 안드로이드 일 경우 수행합니다.
if (devicesOS === "Android") {
// [CASE1] Andriod 12 버전인 경우 : 다시한번 권한을 체크하고 권한 허용이 안된 경우 핸드폰 상 설정 페이지를 띄어준다.
if (devicesOsVer < 13) {
await checkNotifications()
.then(({ status, settings }) => {
if (status === "granted") {
setPermisStateObj({ camera: permisStateObj.camera, alarm: !permisStateObj.alarm });
} else {
Linking.openSettings(); // 핸드폰 상 설정 페이지
setPermisStateObj({ camera: permisStateObj.camera, alarm: !permisStateObj.alarm, });
}
});
}
// [CASE2] Android 13 이상 버전인 경우 : POST_NOTIFICATIONS 를 이용하여 권한 체크
else {
await requestMultiple(["android.permission.POST_NOTIFICATIONS"])
.then((resPremissInfo) => {
const resultStatus = resPremissInfo["android.permission.POST_NOTIFICATIONS"];
if (resultStatus === "granted") {
setPermisStateObj({ camera: permisStateObj.camera, alarm: !permisStateObj.alarm });
}
else {
Linking.openSettings(); // 핸드폰 상 설정 페이지
}
});
}
}
}
3. 설정 페이지 이동 후 Switch 버튼 처리방법 : AppState 활용
💡 설정 페이지 이동 후 Switch 버튼 처리방법 : AppState 활용
- 설정 페이지에서 권한을 ‘허용’한 뒤에 앱으로 돌아왔을 때, Switch 버튼이 움직어야 합니다. - 그때 AppState를 이용하여 앱으로 다시 복귀했을 때 권한을 체크하여서 permisStateObj의 State를 갱신해 주어서 Switch를 on으로 변경 처리하기 위한 방법입니다.
import React, { useEffect, useRef, useState } from "react";
import { AppState, Linking, Switch, Text, View } from "react-native";
import DeviceInfo from "react-native-device-info";
import { checkNotifications, requestMultiple } from "react-native-permissions";
const TestComponent = () => {
// 현재의 상태를 알려주는것
const appState = useRef(AppState.currentState);
// 권한의 종류 및 상태 관리
const [permisStateObj, setPermisStateObj] = useState({
camera: false, // 카메라 권한
alarm: false // 알람 권한
})
/**
* 앱이 foregroud / background인 경우에 따라서 처리
*/
useEffect(() => {
// 사용자가 앱의 상태가 변경 되었을 경우 실행이 된다.
const appStateListener = AppState.addEventListener('change', handleAppStateChange);
return () => {
// 리스너 수행 이후 소멸시킵니다.
return appStateListener.remove();
}
}, [])
/**
* 앱 권한 팝업이 열려 있을때 수행이 됩니다.
* '알람' 권한을 처리하며 설정에서 해당 권한 처리를 변경한 이후에 State를 갱신시켜줍니다.
* @param {any} nextAppState
* @return {Promise<void>}
*/
const handleAppStateChange = async (nextAppState: any): Promise<void> => {
console.log("appState.current ::: ", appState?.current);
console.log("nextAppState ::: ", nextAppState)
// 설정 이후 다시 앱으로 들어오는 경우 권한을 다시 한번 체크해서 갱신한다.
if (appState.current === "active" && nextAppState === "active") {
// 해당 값이 존재하지 않으면 권한이 허용된 상태, 아니면 권한 미 허용
const permisCameraArr = await PermissionUtil.cmmPermsArrCheck([...APP_PERMISSION_CODE.camera])
// [STEP2] 카메라 권한 및 알람 권한을 체크합니다.
checkNotifications()
.then(({ status, settings }) => {
status === "granted"
? setPermisStateObj({ camera: permisCameraArr.length === 0, alarm: true })
: setPermisStateObj({ camera: permisCameraArr.length === 0, alarm: false })
});
}
}
return (
<View>
<View>
<View >
<Text >카메라 (필수)</Text>
<Switch
onValueChange={() => permissionHandler.toggleSwitch("camera")}
value={permisStateObj.camera}
/>
</View>
</View>
<View>
<View >
<Text >알람 (선택)</Text>
<Switch
onValueChange={() => permissionHandler.toggleSwitch("alarm")}
value={permisStateObj.alarm}
/>
</View>
</View>
</View>
)
}
3) 최종 소스코드
💡 최종 소스코드 - 해당 소스코드에서는 카메라와 알람에 대한 권한을 체크하는 예시입니다.
1. permissionHandler.checkPermission() - 최초 앱이 실행되면 권한 체크를 합니다.
2. permissionHandler.toggleSwitch() - 스위치를 변경하면 수행이 되며, 카메라와 알람에 대한 권한을 체크합니다.
3. handleAppStateChange() - 앱의 상태에 따라서 수행하며 background 상태에서 foreground 상태로 돌아오는 경우에 수행이 됩니다.
import { APP_PERMISSION_CODE } from "common/utils/codes/CommonCode";
import DeviceInfoUtil from "common/utils/DeviceInfoUtil";
import PermissionUtil from "common/utils/PermissionUtil";
import React, { useEffect, useRef, useState } from "react";
import { AppState, Linking, Switch, Text, View } from "react-native";
import DeviceInfo from "react-native-device-info";
import { checkNotifications, requestMultiple } from "react-native-permissions";
const TestComponent = () => {
// 현재의 상태를 알려주는것
const appState = useRef(AppState.currentState);
// 권한의 종류 및 상태 관리
const [permisStateObj, setPermisStateObj] = useState({
camera: false, // 카메라 권한
alarm: false // 알람 권한
})
/**
* 앱이 foregroud / background인 경우에 따라서 처리
*/
useEffect(() => {
// [STEP1] 권한을 체크합니다.
permissionHandler.checkPermission();
// 사용자가 앱의 상태가 변경 되었을 경우 실행이 된다.
const appStateListener = AppState.addEventListener('change', handleAppStateChange);
return () => {
// 리스너 수행 이후 소멸시킵니다.
return appStateListener.remove();
}
}, [])
/**
* 앱 권한 팝업이 열려 있을때 수행이 됩니다.
* '알람' 권한을 처리하며 설정에서 해당 권한 처리를 변경한 이후에 State를 갱신시켜줍니다.
* @param {any} nextAppState
* @return {Promise<void>}
*/
const handleAppStateChange = async (nextAppState: any): Promise<void> => {
console.log("appState.current ::: ", appState?.current);
console.log("nextAppState ::: ", nextAppState)
// 설정 이후 다시 앱으로 들어오는 경우 권한을 다시 한번 체크해서 갱신한다.
if (appState.current === "active" && nextAppState === "active") {
// 해당 값이 존재하지 않으면 권한이 허용된 상태, 아니면 권한 미 허용
const permisCameraArr = await PermissionUtil.cmmPermsArrCheck([...APP_PERMISSION_CODE.camera])
// [STEP2] 카메라 권한 및 알람 권한을 체크합니다.
checkNotifications()
.then(({ status, settings }) => {
status === "granted"
? setPermisStateObj({ camera: permisCameraArr.length === 0, alarm: true })
: setPermisStateObj({ camera: permisCameraArr.length === 0, alarm: false })
});
}
}
/**
* 디바이스 권한을 관리하는 Handler입니다.
*/
const permissionHandler = (() => {
const _cameraPermission = APP_PERMISSION_CODE.camera
const _alarmPermission = APP_PERMISSION_CODE.alarm
const deviceOS = DeviceInfo.getSystemVersion();
const deviceOsVer = parseInt(DeviceInfo.getSystemVersion())
return {
/**
* 최초 앱 접근 시 디바이스의 접근 권한을 체크합니다.
*/
checkPermission: async () => {
let isGrantedCamera = false;
let isGrantedAlarm = false;
// [STEP1] 카메라 권한을 체크합니다.
const permisCameraArr = await PermissionUtil.cmmPermsArrCheck([..._cameraPermission])
if (permisCameraArr.length === 0) isGrantedCamera = true;
// [STEP] 안드로이드 알람 권한을 12 이하와 13이상으로 분리하여 체크합니다.
if (deviceOS === "Android") {
// Android 12이하 버전인 경우 요청 처리 및 상태 변경
if (deviceOsVer < 13) {
await checkNotifications()
.then(({ status, settings }) => {
if (status === "granted") isGrantedAlarm = true;
});
}
// Andriod 13이상 버전인 경우 요청 처리 및 상태 변경
else {
const permisAlarmArr = await PermissionUtil.cmmPermsArrCheck([..._alarmPermission]);
if (permisAlarmArr.length === 0) isGrantedAlarm = true;
}
}
setPermisStateObj({ camera: isGrantedCamera, alarm: isGrantedAlarm });
},
/**
* 권한 허용에 대한 스위치를 관리합니다
* true로 바꿨을 경우 요청 팝업을 띄웁니다.
* false로 바꿨을때 처리 없음.
* @param toggleId
*/
toggleSwitch: async (toggleId: "camera" | "alarm") => {
// [CASE1] 카메라 권한 체크
if (toggleId === "camera") {
if (permisStateObj.camera === false) {
await requestMultiple([..._cameraPermission])
.then((resPremissInfo) => {
const resultStatus = resPremissInfo["android.permission.CAMERA"];
if (resultStatus === "granted") {
setPermisStateObj({
camera: !permisStateObj.camera,
alarm: permisStateObj.alarm
});
}
});
}
}
// [CASE2] 알람 권한 체크
else if (toggleId === "alarm") {
if (permisStateObj.alarm === false) {
const osVer = parseInt(DeviceInfoUtil.getDeviceOsVer())
if (osVer < 13) {
await checkNotifications()
.then(({ status, settings }) => {
if (status === "granted") {
setPermisStateObj({
camera: permisStateObj.camera,
alarm: !permisStateObj.alarm,
});
} else {
Linking.openSettings(); // 핸드폰 상 설정 페이지
setPermisStateObj({
camera: permisStateObj.camera,
alarm: !permisStateObj.alarm,
});
}
});
} else {
await requestMultiple(["android.permission.POST_NOTIFICATIONS"])
.then((resPremissInfo) => {
const resultStatus = resPremissInfo["android.permission.POST_NOTIFICATIONS"];
if (resultStatus === "granted") {
setPermisStateObj({
camera: permisStateObj.camera,
alarm: !permisStateObj.alarm,
});
}
else {
Linking.openSettings(); // 핸드폰 상 설정 페이지
}
});
}
} else {
Linking.openSettings(); // 핸드폰 상 설정 페이지
}
}
}
}
})();
return (
<View>
<View>
<View >
<Text >카메라 (필수)</Text>
<Switch
onValueChange={() => permissionHandler.toggleSwitch("camera")}
value={permisStateObj.camera}
/>
</View>
</View>
<View>
<View >
<Text >알람 (선택)</Text>
<Switch
onValueChange={() => permissionHandler.toggleSwitch("alarm")}
value={permisStateObj.alarm}
/>
</View>
</View>
</View>
)
}
export default TestComponent;