Flutter 앱이 화면 꺼져도 알림 울리게 하기 — zonedSchedule과 정확 알람 권한 함정
Timer 반복으로 만든 완료 알림이 백그라운드에서 안 울리는 이유와, zonedSchedule로 완료 시각에 미리 예약해 잠금화면에서도 정확히 울리게 만든 실제 방법을 정리했습니다. 안드로이드 정확 알람 권한 함정까지 다룹니다.
안녕하세요. Jay입니다!
타이머나 알람 앱을 만들다 보면 꼭 부딪히는 벽이 있습니다. 앱을 켜두면 알림이 잘 울리는데, 화면을 끄거나 다른 앱으로 넘어가면 정작 중요한 순간에 조용한 현상이죠. 제가 운영하는 타이머 앱에서도 같은 문제를 겪었고, 오늘은 이걸 어떻게 해결했는지 공유합니다.
🔍 왜 백그라운드에서 안 울릴까
처음엔 흔한 방식으로 만들었습니다. Timer.periodic으로 몇 초마다 남은 시간을 확인하다가 0이 되면 알림을 쏘는 구조요. 문제는 iOS와 안드로이드 모두 백그라운드로 넘어간 앱의 타이머를 정지시킨다는 점입니다. 배터리를 아끼려는 OS의 정상 동작이라, 앱이 포그라운드에 있을 때만 동작하는 foreground-only 한계가 생깁니다.
즉 "앱을 켜두면 울리는데 잠그면 안 울린다"는 건 버그가 아니라, 접근 방식 자체를 바꿔야 하는 신호입니다.
🛠 해결 — 완료 시각에 알림을 "미리 예약"한다
핵심 전환은 이겁니다. 타이머가 끝날 때 알림을 쏘지 말고, 시작하는 순간 끝날 시각에 알림을 예약해둔다. flutter_local_notifications의 zonedSchedule가 이 역할을 합니다. 예약은 OS(정확히는 알람 매니저)가 관리하므로 앱이 죽어 있어도 지정 시각에 울립니다.
준비물은 두 가지입니다.
timezone패키지 —zonedSchedule는TZDateTime을 요구합니다.tz.initializeTimeZones()로 초기화하고 기기 로컬 타임존을 잡아줍니다.- 예약/취소 한 쌍 — 타이머 시작·재개 시
완료 시각 = 지금 + 남은 시간으로zonedSchedule호출, 일시정지·리셋·삭제 시 반드시cancel로 예약을 지웁니다. 이 취소를 빼먹으면 이미 멈춘 타이머가 유령처럼 울립니다.
await plugin.zonedSchedule(
notificationId,
title, body,
tz.TZDateTime.now(tz.local).add(remaining),
details,
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
);
⚠️ 안드로이드 "정확 알람" 권한 함정
여기서 안드로이드가 한 번 더 발목을 잡습니다. 위 exactAllowWhileIdle처럼 정확한 시각 알람을 쓰려면 안드로이드 14+에서 SCHEDULE_EXACT_ALARM / USE_EXACT_ALARM 권한이 필요한데, 이게 Play Console 정책 심사 대상입니다. 아무 앱이나 정확 알람을 쓰면 반려되죠.
다행히 타이머·알람 시계류 앱은 정책상 적격입니다. Play Console의 정확 알람 권한 선언에서 용도를 "알람 시계(alarm clock)" 로 선택하면 통과됩니다. 저도 이 게이트에서 잠깐 멈췄는데, 용도만 정확히 고르면 재빌드 없이 넘어갔습니다. (초 단위 정확도가 필요 없다면 부정확 알람으로 권한 자체를 피하는 선택지도 있습니다.)
🤔 하나 더 — 실기기에서 꼭 확인하세요
이건 시뮬레이터로 검증이 안 되는 영역입니다. 시뮬레이터는 백그라운드 정확 알람 동작을 그대로 재현하지 못하거든요. 저는 무선 디버깅 실행이 실패해서 결국 flutter install로 실기기에 직접 설치해, 앱을 잠그고 완료 시각에 알림이 오는지 눈으로 확인했습니다.
결론
완료 알림은 "끝날 때 쏘는 것"이 아니라 "시작할 때 예약하는 것"입니다.
Timer 반복 루프의 백그라운드 한계에 부딪혔다면 zonedSchedule로 전환하세요. 안드로이드 정확 알람 권한은 타이머 앱이라면 "알람 시계" 용도로 통과되고, 마지막 확인은 반드시 실기기에서 하시길 권합니다. 다음에도 유익한 포스팅으로 찾아오겠습니다. 감사합니다!