-
Notifications
You must be signed in to change notification settings - Fork 1
Notification Behavior
jjoonleo edited this page May 7, 2026
·
1 revision
This document describes the current notification behavior in OnTime across push (FCM) and local notifications, based on implementation as of March 21, 2026.
- App startup checks current notification permission first.
-
NotificationService.initialize()runs at startup only when permission is alreadyAuthorizationStatus.authorized. - If startup permission is not authorized, the app does not initialize notification handlers/tokens from
main.dart. - During authenticated redirects from sign-in/onboarding entry routes, router logic checks permission and redirects to
/allowNotificationwhen permission is not authorized. - Permission can later be requested from:
- Notification allow screen (
/allowNotification) - My Page > App Settings > Allow App Notifications
- Notification allow screen (
Implementation notes:
-
main.dartgates initialization bycheckNotificationPermission(). -
go_router.dartperforms the/allowNotificationredirect check. -
notification_allow_screen.dartandmy_page_screen.dartrun permission request/setting flows and callNotificationService.initialize()once authorized.
-
FirebaseMessaging.onMessagelistener is registered inNotificationService._setupMessageHandlers(). - Incoming push messages are converted into in-app local notifications through
showNotification(message). - Title/body resolution supports both
message.notificationandmessage.datakeys (e.g.,title,content,body, plus case variants).
-
FirebaseMessaging.onMessageOpenedApptriggers_handleBackgroundMessage(message). - Navigation is decided from payload fields:
-
typecontains5min-> push/scheduleStartwithextra: {'isFiveMinutesBefore': true} -
typestarts withschedule_orpreparation_, orscheduleIdexists -> push/alarmScreen
-
-
FirebaseMessaging.getInitialMessage()is read on initialization. - If present, it is passed through the same
_handleBackgroundMessage(message)routing logic as background open.
-
_firebaseMessagingBackgroundHandlerinitializes Firebase, ensures local notification plugin setup, and callsshowNotification(message).
- Local notifications for preparation steps are emitted by
ScheduleBlocvia_notifyPreparationStepcallback (default:NotificationService.instance.showPreparationStepNotification). - Trigger condition:
- Current preparation step changed (
oldCurrentStep.id != newCurrentStep.id) - New step is not null
- New step is not the first step
- Preparation is not fully done
- Step has not already been notified for the same schedule
- Current preparation step changed (
- Deduping:
-
ScheduleBloctracks notified step IDs in_notifiedStepIdsByScheduleId. - Each step is notified once per schedule instance.
-
- Notification content:
- Title:
[$scheduleName] $preparationName - Body: localized (
ko: "이어서 준비하세요.",en: "Continue preparing") - Payload:
{'type': 'preparation_step', 'scheduleId': ..., 'stepId': ...}
- Title:
- Local notification taps are handled through
FlutterLocalNotificationsPlugin.initialize(... onDidReceiveNotificationResponse ...). - Payload JSON is parsed in
_handleLocalNotificationTap. - Routing rules match push open behavior:
-
typecontains5min->/scheduleStartwithisFiveMinutesBefore -
typestarts withschedule_orpreparation_, orscheduleIdexists ->/alarmScreen
-
- Push-open routing (
onMessageOpenedApp,getInitialMessage) and local-tap routing both converge on the same payload-driven destination rules above.
- On notification service initialization:
-
FirebaseMessaging.getToken()is called. - If token exists, app posts to backend endpoint
/firebase-tokenwith payload{ "firebaseToken": "<token>" }.
-
- On token refresh:
-
FirebaseMessaging.onTokenRefresh.listen(...)posts refreshed token to the same endpoint.
-
- Failures are logged and do not crash app flow.
-
POST_NOTIFICATIONSpermission declared in Android manifest. - App creates Android channel:
- ID:
high_importance_channel - Importance: high
- Used for both push-mirrored and app-triggered local notifications.
- ID:
-
Info.plistincludesUIBackgroundModeswithfetchandremote-notification. -
AppDelegate.swiftsets APNs token withMessaging.messaging().apnsToken = deviceToken. - Foreground display options are explicitly enabled (
alert,badge,sound) in notification service initialization.
- Web JS bridge exposes:
-
_requestNotificationPermission->Notification.requestPermission() _isInStandaloneMode
-
-
index.htmlregistersfirebase-messaging-sw.js. - Service worker listens for
pushevents and shows browser notification from payload data (data.data.title,data.data.content) when permission is granted.
- Startup gap when permission is granted later:
- Since
main.dartonly auto-initializes when already authorized, users who grant permission later depend on in-app paths (/allowNotificationor My Page flow) to callNotificationService.initialize().
- Since
- Redirect scope nuance:
- Router permission redirect to
/allowNotificationis applied in authenticated flow when coming through sign-in/onboarding entry routes, not as a global guard on every route.
- Router permission redirect to
- Payload-shape dependency:
- Routing depends on
typeand/orscheduleIdpayload keys; unexpected payload formats may not navigate.
- Routing depends on
- No scheduled local alarms:
- Local notifications in current implementation are immediate
show(...)calls, not time-scheduled notifications.
- Local notifications in current implementation are immediate
lib/core/services/notification_service.dartlib/presentation/app/bloc/schedule/schedule_bloc.dartlib/presentation/shared/router/go_router.dartlib/presentation/notification_allow/screens/notification_allow_screen.dartlib/presentation/my_page/my_page_screen.dartlib/main.dartlib/data/data_sources/notification_remote_data_source.dart- Web notification bridge + worker:
web/functions.jsweb/firebase-messaging-sw.js
- Platform notification config:
android/app/src/main/AndroidManifest.xmlios/Runner/Info.plistios/Runner/AppDelegate.swift
Use this checklist when validating behavior manually or during regression checks.
- Permission not authorized on launch:
- Confirm startup does not initialize notification service automatically.
- Permission granted from allow screen:
- Confirm permission request flow can initialize service and proceed to home.
- Foreground push display:
- Confirm push while app is foregrounded appears as local notification.
- Tap routing:
-
typecontaining5minroutes to/scheduleStartwith five-minute variant. -
schedule_*/preparation_*/ payload withscheduleIdroutes to/alarmScreen.
-
- Local step-change notification:
- Confirm first step does not notify, subsequent step transitions notify once each.
- Token registration:
- Confirm token post on initialize and on token refresh (
/firebase-token).
- Confirm token post on initialize and on token refresh (
Reference test:
-
test/presentation/app/bloc/schedule/schedule_bloc_test.dartstep change notification fires for non-first transitions only once