
모바일 앱에서 푸시 알림은 사용자 참여를 유도하고 중요한 정보를 전달하는 핵심 도구입니다.
하지만 많은 사용자들이 앱 설치 직후 표시되는 알림 권한 요청을 즉시 거부하는 경향이 있어, 효과적인 전략이 필요합니다.
이번 글에서는 React Native 앱의 푸시 알림 권한 요청 시스템을 개선한 사례를 공유합니다.
우리 앱의 기존 알림 권한 요청 방식은 단순했습니다:
AsyncStorage에서 pushPermissionShown 값을 확인pushPermissionShown과 notificationSettings 값을 저장// 기존 가이드 화면 코드
const GuideScreen = () => {
const [showPermissionModal, setShowPermissionModal] = useState(false);
useEffect(() => {
checkPushPermissionShown();
}, []);
const checkPushPermissionShown = async () => {
try {
const hasShown = await AsyncStorage.getItem("pushPermissionShown");
if (!hasShown) {
setShowPermissionModal(true);
}
} catch (error) {
console.error("Failed to check push permission status:", error);
}
};
const handleModalClose = async () => {
try {
await AsyncStorage.setItem("pushPermissionShown", "true");
setShowPermissionModal(false);
} catch (error) {
console.error("Failed to save push permission status:", error);
}
};
return (
<SafeAreaView style={styles.container}>
<LoggedInHeader />
<ScrollView>
<GuideHeader />
<QuickGuide />
<DetailGuide />
<FaqSection />
<ContactSection />
</ScrollView>
<PushPermissionModal
isVisible={showPermissionModal}
onClose={handleModalClose}
/>
</SafeAreaView>
);
};
기존 PushPermissionModal은 자체적으로 상태를 관리했습니다:
const PushPermissionModal = () => {
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
checkPushPermissionShown();
}, []);
const checkPushPermissionShown = async () => {
try {
const hasShown = await AsyncStorage.getItem("pushPermissionShown");
if (!hasShown) {
setIsVisible(true);
}
} catch (error) {
console.error("Failed to check push permission status:", error);
}
};
// 알림 허용 또는 거부 처리 로직...
if (!isVisible) return null;
return (
<Modal transparent={true} animationType="fade" visible={isVisible}>
{/* 모달 콘텐츠 */}
</Modal>
);
};
이 방식의 문제점은:
1. 한 번 거부하면 사용자에게 다시 권한을 요청할 기회가 없음
2. 알림 기능의 가치와 중요성을 충분히 설명하지 못함
3. 사용자가 자신의 선택이 어떤 영향을 미치는지 인지하지 못함
새로운 시스템은 다음과 같은 변화를 적용했습니다:
NotificationAlertBox 컴포넌트는 알림 설정 상태를 확인하고 필요한 경우 알림 박스를 표시합니다:
const NotificationAlertBox = () => {
const [showAlert, setShowAlert] = useState(false);
const [showPermissionModal, setShowPermissionModal] = useState(false);
useEffect(() => {
checkNotificationSettings();
}, []);
const checkNotificationSettings = async () => {
try {
const notificationSettings = await AsyncStorage.getItem(
"notificationSettings"
);
// 설정값이 명시적으로 'false'인 경우만 알림 박스 표시
setShowAlert(notificationSettings === "false");
} catch (error) {
console.error("알림 설정 확인 중 오류:", error);
}
};
const handleRequestPermission = () => {
setShowPermissionModal(true);
};
const handlePermissionModalClose = () => {
setShowPermissionModal(false);
// 모달이 닫힌 후 설정이 변경되었을 수 있으므로 상태 다시 확인
checkNotificationSettings();
};
if (!showAlert) return null;
return (
<>
<View style={styles.container}>
<View style={styles.iconContainer}>
<Bell size={20} color={colors.primary} />
</View>
<View style={styles.textContainer}>
<Text style={styles.title}>매치 알림을 받아보세요</Text>
<Text style={styles.description}>
매치 확정, 참여 수락 등 중요 알림을 받지 못하고 있습니다.
</Text>
</View>
<TouchableOpacity
style={styles.button}
onPress={handleRequestPermission}
>
<Text style={styles.buttonText}>설정</Text>
</TouchableOpacity>
</View>
<PushPermissionModal
isVisible={showPermissionModal}
onClose={handlePermissionModalClose}
/>
</>
);
};
export default NotificationAlertBox;
PushPermissionModal 컴포넌트는 외부에서 완전히 제어되도록 개선했습니다:
type PushPermissionModalProps = {
isVisible: boolean;
onClose: () => void;
};
const PushPermissionModal = ({
isVisible,
onClose,
}: PushPermissionModalProps) => {
// FCM 토큰 얻기
const registerForPushNotificationsAsync = async () => {
// 기존 코드 유지...
};
const requestPermission = async () => {
try {
// 토큰 얻기
const token = await registerForPushNotificationsAsync();
if (!token) {
onClose();
return;
}
try {
// FCM 토큰 서버에 등록
await axiosInstance.post("/device", {
fcmToken: token,
deviceType: Platform.OS.toUpperCase(),
status: true,
});
// AsyncStorage에 푸시 알림 설정 상태 저장
await AsyncStorage.setItem("pushPermissionShown", "true");
await AsyncStorage.setItem("notificationSettings", "true");
console.log("알림 토큰 등록 성공:", token);
} catch (error: any) {
console.error("알림 토큰 등록 실패:", error.response?.data || error);
if (error.response?.status === 409) {
await AsyncStorage.setItem("pushPermissionShown", "true");
await AsyncStorage.setItem("notificationSettings", "true");
}
}
} catch (error) {
console.error("푸시 알림 권한 요청 실패:", error);
} finally {
onClose();
}
};
const skipPermission = async () => {
try {
await AsyncStorage.setItem("pushPermissionShown", "true");
await AsyncStorage.setItem("notificationSettings", "false");
} catch (error) {
console.error("푸시 알림 설정 저장 실패:", error);
} finally {
onClose();
}
};
return (
<Modal transparent={true} animationType="fade" visible={isVisible}>
{/* 모달 콘텐츠 */}
</Modal>
);
};
마이페이지에 알림 박스를 추가하여 사용자가 자주 방문하는 화면에서 알림의 중요성을 상기시킵니다:
export const MyPageScreen = ({
navigation,
}: {
navigation: MyPageNavigationProp;
}) => {
// 기존 상태와 함수들...
return (
<SafeAreaView style={styles.container}>
<LoggedInHeader />
<ScrollView nestedScrollEnabled>
<NotificationAlertBox />
<View style={styles.header}>
<Text style={styles.title}>마이페이지</Text>
<Text style={styles.subtitle}>내 정보와 활동 내역을 관리하세요</Text>
</View>
{/* 나머지 UI 요소들... */}
</ScrollView>
</SafeAreaView>
);
};
컴포넌트 분리와 재사용성
NotificationAlertBox와 PushPermissionModal을 독립적인 컴포넌트로 분리지속적인 알림 관리
사용자 중심 설계
상태 관리 개선
이러한 개선을 통해 다음과 같은 효과를 얻었습니다:
이 접근 방식은 사용자의 자율성을 존중하면서도 앱에 중요한 푸시 알림 기능의 가치를 효과적으로 전달하는 균형을 제공합니다.
푸시 알림 권한 요청은 단순한 기술적 구현이 아닌 사용자 경험 설계의 중요한 부분입니다.
사용자가 처음 앱을 사용할 때 바로 권한을 요청하는 것보다, 앱의 가치를 먼저 경험하게 한 후 지속적으로 알림의 중요성을 상기시키는 방법이 효과적입니다.
이러한 패턴은 푸시 알림뿐만 아니라 위치 정보, 카메라 액세스 등 다양한 권한 요청에도 적용할 수 있으며, 사용자 참여와 만족도를 높이는 데 도움이 됩니다.