2026. 4. 20. 06:30ㆍAI Product Building/Lotto App
안녕하세요! '로또 사고 싶은날'(현 바이브 로또) 개발자입니다. 지난 7편에서는 우리 앱의 '기억장치'인 Hive DB를 활용해 스캔한 로또 내역을 스마트하게 저장하는 방법을 알아봤습니다. 이제 앱은 데이터를 기억할 수 있게 되었지만, 여전히 수동적입니다. 사용자가 앱을 켜야만 작동하니까요. 로또 매니아들에게 가장 중요한 순간은 언제일까요? 바로 매주 토요일 저녁 8시 45분경, 당첨 번호가 추첨되는 그 순간입니다. 이 시간을 깜빡 지나치고 며칠 뒤에야 당첨 사실을 알고 아쉬워하는 사용자들이 없도록, 앱이 먼저 다가가 소식을 전해야 합니다. 이번 편에서는 서버 없이 기기 자체적으로 정확한 시간에 알림을 울리는 Flutter Local Notifications 구현기를 공유합니다.
1. 서버 없이 구현하는 예약 알림(Scheduled Notification)의 매력
처음 알림 기능을 기획했을 때, 가장 먼저 든 고민은 "서버를 구축해야 하나?"였습니다. Firebase Cloud Messaging(FCM) 같은 기술을 쓰면 서버에서 원격으로 푸시를 보낼 수 있지만, 저같은 1인 개발자에게 서버 유지비와 관리 복잡도는 큰 부담입니다. 더욱이 '매주 토요일 정해진 시간'에 보내는 알림은 굳이 원격 서버가 필요 없습니다. 사용자의 스마트폰 내부에 "토요일 8시 45분에 이 메시지를 띄워줘"라고 미리 예약만 해두면 됩니다. 이것이 바로 로컬 알림(Local Notification)입니다.
로컬 알림은 서버 비용이 전혀 들지 않으며, 사용자가 오프라인 상태여도 기기의 시계에 맞춰 정확하게 작동합니다. '바이브 로또'는 이 기능을 적극 활용하여 매주 토요일 저녁, 사용자가 구매한 복권의 당첨 여부를 확인할 수 있는 '골든 타임'을 놓치지 않도록 설계했습니다. 이는 앱의 재방문율(Retention)을 높이는 가장 효과적인 방법이기도 합니다.
2. TimeZone 대응과 반복 알림 설정의 기술적 난관
구현 과정에서 가장 까다로운 부분은 세계 각국의 시간대(TimeZone)와 안드로이드/iOS의 반복 알림 정책 대응이었습니다. 단순히 "지금부터 7일 뒤"에 알림을 맞추면, 서머타임이나 기기 재부팅 등의 변수로 인해 시간이 틀어질 수 있습니다. 이를 해결하기 위해 timezone 패키지를 활용하여 한국 시간(KST)을 기준으로 정확한 토요일 오후 8시 45분을 계산하고, DateTimeComponents.dayOfWeekAndTime 옵션을 통해 매주 해당 요일과 시간에 알림이 반복되도록 설정했습니다. 아래는 그 핵심 서비스 코드입니다.
// 핵심 소스 코드: 로컬 알림 예약 서비스 (notification_service.dart)
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/timezone.dart' as tz;
import 'package:timezone/data/latest.dart' as tz_data;
class NotificationService {
// 싱글톤 패턴 적용
static final NotificationService _instance = NotificationService._internal();
factory NotificationService() => _instance;
NotificationService._internal();
final FlutterLocalNotificationsPlugin _notifications = FlutterLocalNotificationsPlugin();
Future<void> init() async {
tz_data.initializeTimeZones(); // 타임존 데이터 초기화
// ... 안드로이드/iOS 초기화 설정 ...
}
Future<void> scheduleWeeklyNotification() async {
// 매주 토요일 20시 45분 (KST) 계산
final tz.TZDateTime now = tz.TZDateTime.now(tz.local);
tz.TZDateTime scheduledDate = tz.TZDateTime(tz.local, now.year, now.month, now.day, 20, 45);
// 만약 이미 예사 시간이 지났다면 다음주 토요일로 설정
if (scheduledDate.isBefore(now)) {
scheduledDate = scheduledDate.add(const Duration(days: 7));
}
// 다음 토요일을 찾을 때까지 하루씩 더함 (더 정확한 방법)
while (scheduledDate.weekday != DateTime.saturday) {
scheduledDate = scheduledDate.add(const Duration(days: 1));
}
await _notifications.zonedSchedule(
845, // 알림 ID (Unique)
'오늘의 주인공은 당신!',
'방금 로또 추첨이 끝났어요. 당첨 결과를 확인해보세요!',
scheduledDate,
const NotificationDetails(
android: AndroidNotificationDetails(
'lotto_check_channel', '로또 당첨 확인 알림',
channelDescription: '매주 토요일 추첨 시간 알림입니다.',
importance: Importance.max, priority: Priority.high,
showWhen: true,
),
iOS: DarwinNotificationDetails(presentAlert: true, presentBadge: true, presentSound: true),
),
uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: DateTimeComponents.dayOfWeekAndTime, // 매주 반복 설정
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, // 잠자기 모드에서도 정확히 작동
);
}
}
3. 사용자 UX를 고려한 알림 권한 요청 전략
안드로이드 13(API 33)부터 알림 권한이 '옵트인(Opt-in)' 방식으로 바뀌었습니다. 즉, 사용자가 명시적으로 허용해야만 알림을 보낼 수 있습니다. 앱을 처음 설치하자마자 "알림을 허용하시겠습니까?"라는 팝업을 띄우는 것은 최악의 UX입니다. 사용자는 앱의 가치를 느끼기도 전에 거부감부터 갖게 되어 권한을 거부할 확률이 높습니다.
'바이브 로또'는 전략을 바꿨습니다. 앱 실행 시에는 권한을 요청하지 않습니다. 대신, 사용자가 QR 코드를 스캔하여 데이터를 처음 저장하는 순간이나, 환경 설정 메뉴의 '알림 설정' 항목을 사용자가 직접 터치했을 때 permission_handler 패키지를 통해 권한을 요청합니다. "매주 토요일 당첨 확인을 잊지 않도록 알림을 보내드릴까요?"라는 맥락(Context)을 제공한 뒤 요청하는 것이죠. 이 덕분에 권한 허용률을 크게 높일 수 있었습니다.
4. 알림 클릭 그 이후, 딥링크(Deep Link)로 연결되는 설렘
알림이 정확한 시간에 울리는 것만큼이나 중요한 것은, 사용자가 그 알림을 눌렀을 때의 경험입니다. 단순히 앱의 메인 화면을 띄우는 것은 불친절합니다. 사용자는 알림을 누르는 순간 '내 복권의 당첨 여부'가 궁금한 것입니다. '바이브 로또'는 onDidReceiveNotificationResponse 콜백 함수를 활용하여, 사용자가 알림을 탭하면 즉시 데이터를 분석하고 앱 내의 'QR 스캔 화면'이나 '마지막 스캔 내역 화면'으로 바로 이동(Deep Linking)하도록 구현했습니다.
앱이 백그라운드에 있거나 아예 종료된 상태에서도 이 로직이 작동하도록 보장하기 위해 getNotificationAppLaunchDetails 함수를 사용해 앱 시작 시 알림을 통해 들어왔는지 확인하는 절차를 추가했습니다. 덕분에 사용자는 단 한 번의 터치로 토요일 밤의 긴장감을 바로 결과로 확인할 수 있게 되었습니다. 이러한 작은 디테일이 사용자를 앱에 오랫동안 머물게 만드는 비결입니다.
매주 토요일의 설렘, '바이브 로또'와 함께 놓치지 마세요!
👉 바이브 로또(구 MyFeelLotto) 다운로드 바로가기이번 회차에서는 사용자와의 약속을 지키는 '로컬 알림' 기능 구현기를 다뤄봤습니다. 이제 앱은 잊지 않고 사용자를 찾아갈 수 있습니다. 하지만 휴대폰을 분실하거나 교체한다면, 기기에만 저장된 그동안의 소중한 스캔 내역은 어떻게 될까요? 다음 9편에서는 사용자들의 데이터를 안전하게 지키기 위한 V3 아키텍처 기반의 로컬 데이터 백업 및 복원 시스템 구현기로 돌아오겠습니다. 많은 기대 부탁드립니다!
'AI Product Building > Lotto App' 카테고리의 다른 글
| '로또 사고 싶은날' 개발기 10편: 앱으로 수익 창출하기! 구글 애드몹(AdMob) 광고 연동의 모든 것 (1) | 2026.04.23 |
|---|---|
| '로또 사고 싶은날' 개발기 9편: 내 소중한 기록을 안전하게! 백업 및 복원 시스템 구축기 (0) | 2026.04.22 |
| '로또 사고 싶은날' 개발기 7편: 스캔만 하면 끝? No! Hive를 이용한 스마트한 내역 저장 (1) | 2026.04.19 |
| '로또 사고 싶은날' 개발기 6편: 앱의 생명! 초고속 QR 코드 스캐너 기능 구현 (CameraX & ML Kit) (1) | 2026.04.17 |
| '로또 사고 싶은날' 개발기 5편: 동행복권 오픈 API 연동을 통한 실시간 당첨 결과 파싱하기 (0) | 2026.04.16 |