Firebase FCM(Firebase Cloud Message) ์ฌ์ฉํด ๋ณด๊ธฐ
flutter_local_notifications | Flutter Package
awesome_notifications | Flutter Package
permission_handler | Flutter Package
2022.10 ์์ฑํ flutter_local_notification ํจํค์ง ๊ด๋ จ
flutter_local_notifications(Setting)
flutter_local_notifications(์ฌ์ฉ ๋ฐฉ๋ฒ)
flutter_local_notifications(Listener)
์ด๋ฒ ๊ธ์์๋ ์ฑ ๊ฐ๋ฐ์ ์์ด์ ํ์ ๊ธฐ๋ฅ์ผ๋ก ๊ฐ๋ฐ์ด ๋์ด์ผ ํ๋ Local Notifications์ ๋ํด์ ๋ค๋ค๋ณด๋๋ก ํ๊ฒ ๋ค.
์ด์ ์๋ Local Notification์ ๋ํด์ ์์ฑํ ๊ธ์ด ์๊ธด ํ์ง๋ง, ์ผ๋ถ ๋ด์ฉ์ด ๋๋ฝ๋๊ธฐ๋ ํ๊ณ , ์ข ๋ ๋ค์ํ ๊ธฐ๋ฅ์ ๋ค๋ฃจ์ง ์๋ ๋ฏ ํ์ฌ ์๋กญ๊ฒ ์์ฑํ๊ฒ ๋์๋ค.
์๋ ๊ธ ์์ฑ ํ๋ฉด์, awesome_notifications ํจํค์ง์ ๋น๊ต ํ๋ฉด์ ์ฐจ์ด์ ๊ณผ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ์์ธํ ๋ค๋ค๋ณด๋ ค๊ณ ํ๋๋ฐ, awesome_notifications ํจํค์ง์ flutter_local_notifications ํจํค์ง๋ฅผ ๋ชจ๋ ์ฌ์ฉํ๋ ค๊ณ ํ๋, ์ถฉ๋์ด ๋ฐ์ํด์ Local Notification ํจํค์ง ์ค ์ธ๊ธฐ๊ฐ ์ข์ flutter_local_notifications์ ๋ํด์๋ง ์์ธํ ๋ค๋ค๋ณด์๋ค.
awesome_notifications์ ๋ํ ๋ด์ฉ๋ ์ถํ ์๊ฐ๋ ๋์ ๋ค๋ค๋ณผ ์์ ์ด๋ค.
์ฑ์์ ์ฌ์ฉํ๋ ์๋ฆผ ์ข ๋ฅ๋ ํฌ๊ฒ Remote, Local ์ด๋ ๊ฒ 2๊ฐ์ง ์ ๋๊ฐ ์๋ค.
๊ฐ๊ฐ์ ์ฐจ์ด์ ์ ๋ฌด์์ผ๊น ?
๋ํ์ ์ธ ์ฐจ์ด์ ์ด๋ผ๋ฉด ๋ฐ์ก ์ฃผ์ฒด๊ฐ ๋ค๋ฅด๋ค๋ผ๋ ์ ์ด ์๊ณ , ๋ ํ๋๋ ๋ช ์นญ์์๋ ์ ์ ์๋ฏ์ด ๋ฐ์ก์ ๋ณด๋ด๋ ๋ฐฉ์์๋ ์ฐจ์ด๊ฐ ์๋ค๋ ๊ฒ์ ์ ์ ์๋ค.
์ฌ์ฉ ๋ฐฉ๋ฒ์ด๋ ์ค์ ๋ฐฉ๋ฒ์ ํฌ๊ฒ ์ฐจ์ด๊ฐ ์๋ค.
๋จผ์ Remote Push ๋ผ๊ณ ๋ถ๋ฆฌ๋ ํ์ ์ ๋ํด์ ๊ฐ๋จํ๊ฒ ์ดํด๋ณด์.
์ผ๋ฐ์ ์ผ๋ก ์ฑ์์ ํธ์๋ฅผ ๋ณด๋ธ๋ค๊ณ ํ๋ฉด, Remote Push๋ฅผ ์ฌ์ฉํด์ ๋ณด๋ด๊ฒ ๋๋ค.
Remote Push๋ฅผ ๊ตฌ์ถํ๊ธฐ ์ํด์๋ ๊ฐ ์๋น์ค์ ๋ง๋ ์์คํ
์ ๊ตฌ์ถํด์ผ ํ๋๋ฐ, ์ง์ ๊ตฌ์ถํ ์๋ ์๊ณ , Firebase Messaging ๊ธฐ๋ฅ์ ์ฌ์ฉํด์ ๊ตฌ์ถํ ์ ์๋ค.
Firebase Messaging๋ ์ผ๋ฐ์ ์ผ๋ก FCM(Firebase Cloud Messaging)์ด๋ผ๊ณ ๋ถ๋ฆฌ๋ฉฐ, ์ง์ ๊ตฌ์ถํ๋ ๋ฐฉ์๋ณด๋ค ๊ฐ๋ฐ ๋ฆฌ์์ค๊ฐ ์ ์ผ๋ฉฐ, ์ ์ก๋ฅ ์์๋ ์ฐจ์ด๊ฐ ๋ง์ด ๋ฐ์ํ์ฌ ๊ฐ์ฅ ์ธ๊ธฐ๊ฐ ์ข์ ๋ฐฉ์์ด๋ค.
Remote Push๋ ์๋ฒ์์ ๊ฐ ๋๋ฐ์ด์ค์ Token์ ์ฌ์ฉํด Google GCM, Apple APNs ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด ๊ฐ ํ๋ซํผ์์ ๋๋ฐ์ด์ค๋ก ํธ์๋ฅผ ์ ์กํ๊ฒ ๋๋ค.
Remote Push, FCM, Firebase Messaging ๋ฑ์ ๋ํด์ ์์ธํ ์ดํด๋ณด๊ณ ์ถ๋ค๋ฉด, ์ด์ ์ ์์ฑํ ๊ธ์ ์ฐธ๊ณ ํ์๊ธธ ๋ฐ๋๋ค.
๊ทธ ๋ค์์ผ๋ก ์ด๋ฒ ๊ธ์์ ๋ค๋ฃจ๊ฒ ๋ ๋ด์ฉ์ธ Local Notification์ด๋ค.
Local Push ๋๋ ๋ด๋ถ ํธ์๋ผ๊ณ ๋ถ๋ฆฌ๋ ์ข
๋ฅ์ธ๋ฐ, Local Push๋ Remote Push์๋ ๋ค๋ฅด๊ฒ ์๋ฒ์์ ์ ์ก์ ์์ฒญํ์ง ์๊ณ ๋๋ฐ์ด์ค์์ ์ ์ก์ ์์ฒญํ๊ธฐ์ ์๋ฒ๋ฅผ ๋ฐ๋ก ๊ตฌ์ถํ ํ์๊ฐ ์๋ค. ์ด ๋ถ๋ถ์ด ์ฅ์ ์ผ ์๋ ์๊ณ ๋จ์ ์ผ ์๋ ์๋๋ฐ, ์๋ฒ๋ฅผ ํตํด ์ ์ก๋์ง ์๊ธฐ ๋๋ฌธ์ ์ ์ก๋ฅ ์ ๊ฑฐ์ 100%๋ผ๊ณ ๋ด๋ ๋๋ค.
๋ค๋ง, ์ฑ์์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ฌ ํธ์๋ฅผ ์ ์กํด์ผ ํ๊ธฐ์, Remote Push ๋ณด๋ค๋ ์ปค์คํ
ํ ๋ด์ฉ์ ์ ์กํ๊ธฐ๋ ํ๊ณ๊ฐ ์๋ค.
Local Push๋ ์ค์ผ์ฅด๋ง์ด ๊ฐ๋ฅํ์ฌ, ๋งค์ผ, ๋งค์ฃผ, ๋งค์, ํน์ ๊ธฐ๊ฐ ๋ฑ์ ๋ฐ๋ผ ์ ์ก์ ์์ฝํ ์ ์์ผ๋ฉฐ, ์ฃผ๋ก ์๋ฆผ ๊ด๋ จ ์ฑ๋ค์ด Local Push๋ฅผ ์ฌ์ฉํด ์๋ฆผ์ ๋ณด๋ด์ฃผ๊ณ ์๋ค.
๋ํ Remote Push๋ก ์ ์ก๋ ์๋ฆผ์ด ๋๋ฐ์ด์ค์ ์ํ๊ฐ Foreground๋ผ๋ฉด, ์๋ฆผ์ ๋ณด๋ด์ง ์๊ฒ ๋๋ ๋ฌธ์ ๊ฐ ์๋๋ฐ, ์ด๋ฌํ ๋ถ๋ถ์์๋ Remote Push์ ์ ๋ณด๋ฅผ ๋ด์ payload๋ฅผ ์ป์ด Local Push๋ก ์ ์ก์ ์์ฒญํ๋ฉด Foreground์์๋ ์๋ฆผ์ ๋ณด๋ผ ์ ์๋ค๋ ์ฅ์ ์ด ์๋ค.
๋๋ถ๋ถ์ ์๋น์ค๋ ๊ผญ ํ๋์ Notification ํ์ ์ ๊ตฌ์ถํ๋ ๊ฒ์ด ์๋, Remote, Local ๋ ์์คํ ์ ๋ค ๊ตฌ์ถํด์ ์ ์ ํ ์ฌ์ฉํ๋ค๊ณ ๋ณด๋ฉด ๋๋ค.
์๋ฅผ ๋ค์ด, ์ฐ๋ฆฌ๊ฐ SNS ์ฑ์ ์ฌ์ฉํ๋ค๊ณ ๊ฐ์ ํด๋ณด์. ์ฌ์ฉ์์๊ฒ ๋งค์ผ ์ค์ 9์์ ์๋ฆผ์ ์ ์กํ๊ณ ์ถ๋ค๋ฉด, ์ด๋ด ๋ Local Notifications์ ์ค์ผ์ฅด๋ง์ ์ฌ์ฉํด์ ๋งค์ผ ์ค์ 9์์ ์๋ฆผ์ ๋ณด๋ด์ฃผ๋ฉด ๋๋ค. ๊ตณ์ด ์๋ฒ์์ ์๋ฆผ์ ์ ์กํ ํ์๊ฐ ์๋ค. ํ์ง๋ง, ์ฌ์ฉ์์๊ฒ ๋ค๋ฅธ ์ฌ์ฉ์๊ฐ ํ๋ก์ฐ ํ๋ค๋ ์๋ฆผ์ ๋ณด๋ด๊ณ ์ถ๋ค๋ฉด, ๋น์ฐํ ์๋ฒ์์ ์๋ฆผ์ ์ ์กํด์ผ ํ๊ธฐ์, Remote Push๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
์ด์ ๋ณธ๊ฒฉ์ ์ธ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ์์ ๋ชจ๋ ์๋ฆผ ์ข ๋ฅ๋ฅผ ์ฌ์ฉํ ๋์ ๊ณตํต์ ์ผ๋ก ์ค์ ์ด ๋๋ ๋ถ๋ถ์ด ์๋๋ฐ, ๋ฐ๋ก ๊ถํ์ ์์ฒญ ๋ฐ๋ ๋ฌธ์ ์ด๋ค.
Permission์ ์ฑ์์ ํ์์ ์ผ๋ก ์ดํดํ๊ณ ๊ฐ๋ฐํด์ผ ํ๋ ๋ถ๋ถ์ผ๋ก, ๊ถํ์ด ์๋ ์ฌ์ฉ์๋ ํด๋น ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์๊ธฐ์, ์ ์ ํ ์์ ์ ๊ถํ ์์ฒญ์ ํ์์ ์ด๋ค.
์ฌ๊ธฐ์๋ Notifications์ ๊ด๋ จ๋ ๊ถํ์ ์์ฒญ ๋ฐ์ ์ ์๋๋ก ๋จผ์ ์ค์ ์ ์งํํด ๋ณด์.
Permission ์์ฒญ์ ํ๊ณ ์ถ์ ๋๋, ๋ณดํต ๊ฐ ํจํค์ง์์๋ ๊ถํ์ ์์ฒญ ๋ฐ์ ์ ์์ง๋ง, ์ฑ์์ ์๋ฆผ์ ๋ํ ๊ถํ๋ง์ ์ฌ์ฉํ๋ ๊ฒ์ ์๋๊ธฐ์, ํ ๋ฒ์ Permission ๊ด๋ฆฌ๋ฅผ ํ ์ ์๋ ํจํค์ง๋ฅผ ์ฌ์ฉํ ๊ฒ์ด๋ค.
์ฐธ๊ณ ๋ก Permission ๊ด๋ จ๋ ๋ด์ฉ์ ์๋ฎฌ๋ ์ดํฐ์ ์ค์ ๋๋ฐ์ด์ค, ์ค์ ๋ Flutter ํ์ผ ๋ฐ ๋๋ฐ์ด์ค ๋ฒ์ ์ ๋ฐ๋ผ ์ผ๋ถ ๋ด์ฉ์ด ์กฐ๊ธ์ฉ ๋ค๋ฅผ ์ ์์ผ๋, ๊ตฌ๊ธ๋ง์ด ํ์ํ ์๋ ์๊ณ , ํด๊ฒฐ์ด ์๋์๋ ๋ถ๋ถ์ ๋๊ธ ๋จ๊ฒจ ๋์ผ์๋ฉด ์ต๋ํ ๋์ ๋๋ฆฌ๋๋ก ํ๊ฒ ๋ค.
permission_handler ํจํค์ง๋ฅผ ์ถ๊ฐํด ์ฃผ๋๋ก ํ์.
dependencies:
permission_handler: ^11.1.0
์๋ฆผ ํ์ฉ์ ์ํด์ ์ฑ์ด ๊ฐ์ฅ ์ต์๋จ ์์ ฏ์์ ์๋ฆผ์ ๋ํ ๊ถํ์ ์์ฒญํ๋๋ก ํด๋ณด์.
๊ถํ์ ์์ฒญํ๋๋ฐ, ์๋ฌด ๋ฐ์์ด ์๊ฑฐ๋ ์ฑ์ ํฌ๋์๊ฐ ๋ฐ์ํ์ ๊ฒ์ด๋ค. ํ๋ซํผ์ ๋ง๊ฒ ๊ถํ์ ์์ฒญํ๊ธฐ ์ํด ์ถ๊ฐ์ ์ผ๋ก ์ค์ ์ ํด์ฃผ์ง ์์์ ๊ทธ๋ฐ ๊ฒ์ด๋ค.
void initState() {
super.initState();
_permissionWithNotification();
}
void _permissionWithNotification() async {
await [Permission.notification].request();
}
๋จผ์ IOS ์ค์ ์ ์ถ๊ฐํด ์ฃผ๋๋ก ํ์. Podfile์ ์ฝ๋๋ฅผ ์ถ๊ฐํด ์ฃผ๋ฉด๋๋๋ฐ, ํ์์ ๋ฐ๋ผ Info.plist ํ์ผ๋ ์์ ํด์ผ ํ ์๋ ์๋ค.
ios > Podfile
Podfile ํ๋จ ๋ถ๋ถ์ ์๋ ์ฝ๋๋ฅผ ์ถ๊ฐํด ์ฃผ๋ฉด ๋๋ค. ์ฌ๊ธฐ์ ์ฃผ์ ์ฒ๋ฆฌ๋ ๋ถ๋ถ์ด ์๋๋ฐ, 'PERMISSION_NOTIFICATIONS=1' ํด๋น ๋ถ๋ถ์๋ง ์ฃผ์์ด ๋์ด ์์ง ์๋ค.
IOS๋ ์ด๋ ๊ฒ ์ฌ์ฉํ ํผ๋ฏธ์ ์ ์ฃผ์์ ์ ๊ฑฐํ์ฌ ์ฌ์ฉํ๋ฉด ๋๋๋ฐ, ์ฌ๊ธฐ์๋ Notifications์ ๋ํ ๊ถํ๋ง ์ฌ์ฉํ ๊ฒ์ด๊ธฐ์ ์๋ ์ฝ๋๋ง ์ฃผ์์ ํด์ ํด ์ฃผ์๋ค.
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
## dart: PermissionGroup.calendar
# 'PERMISSION_EVENTS=1',
## dart: PermissionGroup.reminders
# 'PERMISSION_REMINDERS=1',
## dart: PermissionGroup.contacts
# 'PERMISSION_CONTACTS=1',
## dart: PermissionGroup.camera
# 'PERMISSION_CAMERA=1',
## dart: PermissionGroup.microphone
# 'PERMISSION_MICROPHONE=1',
## dart: PermissionGroup.speech
# 'PERMISSION_SPEECH_RECOGNIZER=1',
# dart: PermissionGroup.photos
# 'PERMISSION_PHOTOS=1',
## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
# 'PERMISSION_LOCATION=1',
## dart: PermissionGroup.notification
'PERMISSION_NOTIFICATIONS=1',
## dart: PermissionGroup.mediaLibrary
# 'PERMISSION_MEDIA_LIBRARY=1',
## dart: PermissionGroup.sensors
# 'PERMISSION_SENSORS=1',
## dart: PermissionGroup.bluetooth
# 'PERMISSION_BLUETOOTH=1',
## dart: PermissionGroup.appTrackingTransparency
'PERMISSION_APP_TRACKING_TRANSPARENCY=1',
## dart: PermissionGroup.criticalAlerts
# 'PERMISSION_CRITICAL_ALERTS=1'
]
end
์์ ์ฝ๋๊ฐ ๋ค์ด๊ฐ์ผ ํ๋ ๋ถ๋ถ์ ์ ์ฒด ์ฝ๋์ธ๋ฐ, post_install ์ฝ๋๋ ๊ฐ๊ฐ์ ์ค์ ์ ๋ฐ๋ผ ํฌ๊ฒ ๋ค๋ฅผ ์ ์์ด์ ์ฐธ๊ณ ๋ง ํด์ฃผ์๋ฉด ๋๋ค.
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
## dart: PermissionGroup.calendar
# 'PERMISSION_EVENTS=1',
## dart: PermissionGroup.reminders
# 'PERMISSION_REMINDERS=1',
## dart: PermissionGroup.contacts
# 'PERMISSION_CONTACTS=1',
## dart: PermissionGroup.camera
# 'PERMISSION_CAMERA=1',
## dart: PermissionGroup.microphone
# 'PERMISSION_MICROPHONE=1',
## dart: PermissionGroup.speech
# 'PERMISSION_SPEECH_RECOGNIZER=1',
# dart: PermissionGroup.photos
# 'PERMISSION_PHOTOS=1',
## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
# 'PERMISSION_LOCATION=1',
## dart: PermissionGroup.notification
'PERMISSION_NOTIFICATIONS=1',
## dart: PermissionGroup.mediaLibrary
# 'PERMISSION_MEDIA_LIBRARY=1',
## dart: PermissionGroup.sensors
# 'PERMISSION_SENSORS=1',
## dart: PermissionGroup.bluetooth
# 'PERMISSION_BLUETOOTH=1',
## dart: PermissionGroup.appTrackingTransparency
'PERMISSION_APP_TRACKING_TRANSPARENCY=1',
## dart: PermissionGroup.criticalAlerts
# 'PERMISSION_CRITICAL_ALERTS=1'
]
end
end
end
์๋ ์ด๋ฏธ์ง ์ฐธ๊ณ ํ์๋ฉด ๋๋ค.
์ด์ ๋ค์ ์คํํด์ ๊ถํ์ ์์ฒญ ๋ฐ๋์ง ํ์ธํด ๋ณด์. ์ ์์ ์ผ๋ก ์์ฒญ์ ํ๋ค.
์ด๋ฒ์ Android ์ค์ ๋ ์ถ๊ฐํด์ฃผ์.
Android API 33๋ ๋ฒจ์ ๊ธฐ์ค์ผ๋ก ์๋ฆผ ๊ถํ์ ๋ํ ๋ด์ฉ์ ๋ณํ๊ฐ ์๋ค.
33๋ ๋ฒจ ์ด์ ์ธ ๊ฒฝ์ฐ ์๋ฆผ์ ๋ฐ๋ก ์์ฒญ๋ฐ์ ํ์ ์์ด, ์๋ฆผ ๊ถํ์ด ๋ชจ๋ ํ์ฉ ์ํ์๋๋ฐ, 33๋ ๋ฒจ ์ด์๋ถํฐ ๊ถํ์ ๋ฐ๋ก ์์ฒญํ๋ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝ์ด ๋์๋ค.
Remote Notifications ๋ง ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์๋ ์ถ๊ฐ์ ์ธ ์ค์ ์ด ํ์ ์์ง๋ง, Local Notifications๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ์ฌ๋ฌ ๊ถํ๋ค์ ๋ฑ๋กํด์ผ ์ฌ์ฉํ ์ ์๋ค. ํด๋น ๊ถํ์ ์๋์์ ๋ค๋ฃจ๊ธฐ๋ก ํ๊ฒ ๋ค.
Android๋ ๋ฐ๋ก ์ค์ ์์ด๋, ์์ฒญ์ด ๊ฐ๋ฅํ๋ค.
์ด์ ๊ถํ์ ์์ฒญํ๋ ๋ฐฉ๋ฒ์ ๋ํด์ ์ดํด๋ดค๋๋ฐ, IOS์ Android์ ๊ฐ ๊ถํ ์ํ๊ฐ ์ผ๋ถ ๋ค๋ฅด๊ณ , ๊ถํ์ ํ ๋ฒ ์์ฒญํ ์ํ์์๋ ์ถ๊ฐ์ ์ธ ๊ถํ ์์ฒญ์ ์๋ฌ๊ฐ ๋ฐ์ํ ์ ์์ด ์ ์ ํ๊ฒ ๊ถํ ์์ฒญ์ ๊ด๋ฆฌํด ์ฃผ์ด์ผ ํ๋ค.
์๋๋ ์ ๊ฐ ์ฃผ๋ก ์ฌ์ฉํ๋ ๋ฐฉ์์ ์ฝ๋๋ก, ๊ถํ ์ํ ๋ ๊ฐ๋ฅผ ์ฌ์ฉํด์ ์ ์ ํ๊ฒ ์์ฒญ ์์ ์ ๊ด๋ฆฌํ๊ณ ์๋ค.
Permission์ ์ ์ฒด์ ์ธ ๊ถํ ์ํ์ ๋ํด์๋ ๋ด์ฉ์ด ๋๋ฌด ๋ง๊ณ ๋ณต์กํด์ ์๊ฐ์ด ๋๋ฉด ๋ค๋ฅธ ๊ธ์์ ๋ ์์ธํ ๋ค๋ฃจ๊ฒ ๋ค.
Permission ์์ฒญ์ ์ฌ์ฉํ ์ฝ๋๋ฅผ ์์ ํด ์ฃผ์.
์ด๋ ๊ฒ ํ๋ฉด IOS์ธ ๊ฒฝ์ฐ ๊ถํ์ ์์ฒญ ๋ฐ์ง ์๋ ์ํ๋ผ๋ฉด ๊ถํ์ ์์ฒญํ๊ณ , ๊ฑฐ๋ถ ๋๋ ํ์ฉ๋ ์ํ์์๋ ์์ฒญํ์ง ์๋๋ค.
Android์ ๊ฒฝ์ฐ์๋ 33๋ ๋ฒจ ์ด์ ์์๋ ๊ถํ์ ๋ฌป์ง ์๊ณ , 33๋ ๋ฒจ ์ด์ ๋ถํฐ๋ ๊ถํ์ด ์๋ ๊ฒฝ์ฐ ๋๋ ๊ฑฐ๋ถ๋ ์ํ์ธ ๊ฒฝ์ฐ ์ต๋ 2๋ฒ๊น์ง ์์ฒญ์ ํ๊ฒ๋ ๊ฒ์ด๋ค.
์๋ ์ฝ๋๋ ์ค์ ์ด์ ์ฑ์์๋ ์ฌ๋ฌ ์ผ์ด์ค๋ค์ด ์ถ๊ฐ๋์ด ๊ด๋ฆฌ๋๊ณ ์์ผ๋ฏ๋ก, ์ฐธ๊ณ ๋ง ํ์๊ธธ ๋ฐ๋๋ค.
void _permissionWithNotification() async {
if (await Permission.notification.isDenied &&
!await Permission.notification.isPermanentlyDenied) {
await [Permission.notification].request();
}
}
Permission ๊ถํ๋ ์ ์์ ์ผ๋ก ์งํ ํ์ผ๋, ๋ณธ๊ฒฉ์ ์ผ๋ก flutter_local_notifications ํจํค์ง๋ฅผ ์ฌ์ฉํ ๋ก์ปฌ ํธ์์ ๋ํด์ ์ดํด๋ณด๋๋ก ํ์.
dependencies:
flutter_local_notifications: ^16.3.0
๋จผ์ ํจํค์ง ์ฌ์ฉ์ ์ด๊ธฐํ๋ฅผ ๋จผ์ ์งํํด์ผ ํ๋ค.
final FlutterLocalNotificationsPlugin _local = FlutterLocalNotificationsPlugin();
Android, IOS ๊ฐ๊ฐ์ ์ด๊ธฐํ ์ค์ ์ ์งํํด์ผ ํ๋ค.
Android ์ด๊ธฐํ ์ค์ ์ ์์ด์ฝ๋ง ์ค์ ํด ์ฃผ๋ฉด๋๋๋ฐ, ์ฑ ์์ด์ฝ์ ์ฌ์ฉํ๋ฉด ๋๊ธฐ์ ์๋์ ic_launcher๋ก ๋ฑ๋ก๋ ์์ด์ฝ์ ์ฌ์ฉํ๋๋ก ํด์ฃผ์๋ค.
IOS๋ ์ด๊ธฐํ์ ๊ถํ์ ์์ฒญํ ๊ฒ์ด๋๋ ์ต์ ์ธ๋ฐ, permission_handler๋ฅผ ์ฌ์ฉํด ์์ฒญ์ ๋ณ๋๋ก ํด์ค ๊ฒ์ด๊ธฐ์, ๋ชจ๋ false๋ก ํด์ฃผ์๋ค.
void _initialization() async {
AndroidInitializationSettings android =
const AndroidInitializationSettings("@mipmap/ic_launcher");
DarwinInitializationSettings ios = const DarwinInitializationSettings(
requestSoundPermission: false,
requestBadgePermission: false,
requestAlertPermission: false,
);
InitializationSettings settings =
InitializationSettings(android: android, iOS: ios);
await _local.initialize(settings);
}
์ฑ์ ์ต์๋จ ์์ ฏ์ initState์์ ์ด๊ธฐํ๋ฅผ ์งํํด ์ฃผ์.
void initState() {
super.initState();
_permissionWithNotification();
_initialization();
}
Test
์ด์ ํ ์คํธ ์ ์ก์ ํ ๋ฒ ํด๋ณด์.
๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ์ ์ก ๋ฐฉ๋ฒ์ด๋ค. ํด๋น show() ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ฉด ์ฆ์ ํธ์๊ฐ ์ ์ก๋๊ฒ ๋๋ค. ์๋ ์ฝ๋๋ฅผ ์ฌ์ฉํด์ ๋ฒํผ์ ๋ง๋ค์ด ์ค ๋ค ๋ฒํผ์ ํด๋ฆญํด๋ณด์.
์๋ฌด๋ฐ ๋ฐ์์ด ์๋ค. ์ ๊ทธ๋ด๊น ?
Foreground ํ๊ฒฝ์ ๋ํ ์ถ๊ฐ์ ์ธ ์ค์ ์ ํด์ฃผ์ง ์์์ ์ฑ ์คํ ์ค ํ๊ฒฝ์์๋ ํธ์๊ฐ ์ ์ก๋์ง ์๋ ๊ฒ์ด๋ค.
NotificationDetails details = const NotificationDetails(
iOS: DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
),
android: AndroidNotificationDetails(
"1",
"test",
importance: Importance.max,
priority: Priority.high,
),
);
await _local.show(1, "title", "body", details);
์ฑ ์คํ ์ค ํ๊ฒฝ์ธ Foreground ํ๊ฒฝ์์ ํธ์ ์๋ฆผ์ ๋ ธ์ถ ์ํค๋ ๊ธฐ๋ฅ์ heads up notifications๋ผ๊ณ ํ๋ค. ํด๋น ๊ธฐ๋ฅ์ด ์ ์์ ์ผ๋ก ์งํ์ด ๋๋ ค๋ฉด ๊ฐ ํ๋ซํผ ๋ณ๋ก ์ค์ ์ ์ถ๊ฐํด์ผ ํ๋ค.
android > app > src > main > AndroidManifest.xml
application ํ๊ทธ ์๋จ์ Permission์ ํ์ฑํํ๋ ์ฝ๋๋ฅผ ์ถ๊ฐํด ์ฃผ์.
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
activity ํ๊ทธ ์๋์ receiver ์ฌ์ฉ์ ๋ํ ์ฝ๋๋ฅผ ์ถ๊ฐํด ์ฃผ์.
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
</intent-filter>
</receiver>
์ ์ฒด ์ฝ๋์ด๋ค.
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
</intent-filter>
</receiver>
activity ํ๊ทธ ์์๋ ์ฝ๋๋ฅผ ์ถ๊ฐํด ์ฃผ์.
<activity
...
android:showWhenLocked="true"
android:turnScreenOn="true">
</activity>
์๋๋ ์์์ ์ค์ ํ ์ ์ฒด AndroidManifest ์ฝ๋์ด๋ค.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- local_notifications -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<!-- local_notifictaions -->
<application
android:label="flutter_local_notifications_sample"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
android:showWhenLocked="true"
android:turnScreenOn="true">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
</intent-filter>
</receiver>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
IOS ์ค์ ์ ์ถ๊ฐํด ์ฃผ์.
IOS ์ค์ ์ ์ํด XCode๋ฅผ ์ด์ด์ฃผ๋๋ก ํ์. ios ํด๋์ ์ฐ์ธก ๋ฒํผ์ ํด๋ฆญํ์ฌ "Open in Xcode"๋ฅผ ๋๋ฌ์ฃผ๋ฉด ํ๋ก์ ํธ์ ios ์์ค์ XCode๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
Runner > AppDelegate.swift
์๋ ์ฝ๋๋ฅผ ์ถ๊ฐํด ์ฃผ๋๋ก ํ์.
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
AppDelegate ์ ์ฒด ์ฝ๋์ด๋ค.
import UIKit
import Flutter
class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
๋ค์ ํ ์คํธ๋ฅผ ์งํํด ๋ณด์. ์ด์ Foreground ํ๊ฒฝ์์๋ ํธ์๊ฐ ์ ์์ ์ผ๋ก ๋ ธ์ถ์ด ๋๊ณ ์๋ค.
์ด์ flutter_local_notifications ํจํค์ง์๋ ์ด๋ค ๊ธฐ๋ฅ๋ค์ด ์๋์ง ํ๋์ฉ ์ดํด๋ณด๋๋ก ํ์.
๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ ๋ฐฉ๋ฒ์ด ํธ์๋ฅผ ์ฆ์ ์ ์กํ์ฌ ๋ ธ์ถ๋ ์ ์๋๋ก ํด์ฃผ๋ ๊ธฐ๋ฅ์ด๋ค. ํด๋น ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ฉด Remote Push๊ฐ ์์ ๋์์ ๋์, Foreground ํ๊ฒฝ์์ ์๋ฆผ์ ๋ณด์ฌ์ค ์ ์๊ฒ ๋๋ค.
NotificationDetails details = const NotificationDetails(
iOS: DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
),
android: AndroidNotificationDetails(
"show_test",
"show_test",
importance: Importance.max,
priority: Priority.high,
),
);
await _local.show(
0,
"ํ์ดํ์ด ๋ณด์ฌ์ง๋ ์์ญ์
๋๋ค.",
"์ปจํ
์ธ ๋ด์ฉ์ด ๋ณด์ฌ์ง๋ ์์ญ์
๋๋ค.\ntest show()",
details,
payload: "tyger://",
);
id : Unique Id, ์ค๋ณต๋์ง ์์์ผ ํจ. ์๋ฆผ ๊ทธ๋ฃน๋ณ๋ก id ๊ฐ์ ์ง์ ํ์ฌ ์ฌ์ฉํจ.
title : ์ ๋ชฉ ์์ญ์ด ๋ณด์ฌ์ง๋ ๋ถ๋ถ์ ๋ ธ์ถ๋๋ ์์ญ.
body : ์ ๋ชฉ ์๋ ๋ณธ๋ฌธ ๋ด์ฉ์ ๋ ธ์ถ๋๋ ์์ญ.
payload : ์๋ฆผ์ ํฐ์นํ์ฌ ์ฑ์ ์ง์ ํ์ ๋์, ์์ ๊ฐ๋ฅํ ๋ถ๋ถ์ผ๋ก, ์ฃผ๋ก ๋ค์๋ ๋ผ์ฐํฐ์ ์ฌ์ฉํ๋ path๋ฅผ ์ฌ์ฉํจ.
notificationDetails
ํ๋ซํผ (android, ios, macOS, linux) ๋ณ๋ก ๋ก์ปฌ ํธ์์ ์ธ๋ถ ์ค์ ์ ํ ์ ์๋ ๋ถ๋ถ์ด๋ค.
- android
channelId : ๊ณ ์ ํ id ๊ฐ์ ์ฌ์ฉ.
channelName : ์ฑ ์ค์ > ์๋ฆผ์ ๋ณด์ฌ์ง๋ ๋ค์.
channelDescription : ํด๋น ์ฑ๋์ ๋ํ ์ค๋ช
.
importance, priority : ์ค์๋๋ฅผ ์ค์ ํ๋ ๋ถ๋ถ์ผ๋ก ์๋์ ๊ฐ์ด ์ค์๋๋ฅผ ๋ํ์ ์ ์ก์ ํด์ผ์ง๋ง Foreground์์ ๋
ธ์ถ์ด ๊ฐ๋ฅํจ.
AndroidNotificationDetails(
"show_test",
"show_test",
channelDescription: "Test Local notications",
importance: Importance.max,
priority: Priority.high,
),
- ios
presentAlert, presentBadge, presentSound : ์ผ๋ฟ, ๋ฐฐ์ง, ์ฌ์ด๋ ํ์ฑํ.
badgeNumber : ๋ฐฐ์ง ์นด์ดํธ๋ฅผ ๋ฃ์ด์ฃผ๋ ๋ถ๋ถ์ผ๋ก, ์ฑ ์์ด์ฝ ๋ฐฐ์ง ๋๋ฒ์ด๋ค.
ios๋ ์ฑ ์์ด์ฝ ๋ฐฐ์ง ๋๋ฒ๋ฅผ ์ง์ ์ง์ ํด ์ฃผ์ง ์์ผ๋ฉด ์นด์ดํธ๋ฅผ ์์คํ ์์ ์ฒ๋ฆฌํด์ฃผ์ง ์๊ธฐ ๋๋ฌธ์, ์ง์ ํ์ง ์์ผ๋ฉด ์นด์ดํธ๋ ํ์๋์ง ์์ต๋๋ค.
DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
badgeNumber: 1,
),
์ด๋ฒ์ ๊ฐ๋จํ๊ฒ ํน์ ์ฃผ๊ธฐ๋ฅผ ๋ฐ๋ณตํด์ ๋ก์ปฌ ํธ์๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ๋ณด๋ผ ์ ์๋ ๊ธฐ๋ฅ์ ๋ํด์ ์ดํด๋ณด๋๋ก ํ๊ฒ ๋ค.
periodicallyShow()๋ ๊ฐ๋จํ๊ฒ ํธ์๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ์์ฑํ์ฌ ์ค์ผ์ฅด๋ง์ ํ ์ ์์ง๋ง, ๋ฏธ๋ฆฌ ์ง์ ๋ 4๊ฐ์ ํ์ ์ผ๋ก๋ง ์ค์ ํ ์ ์์ผ๋ฉฐ, ์๊ฐ๋ ๋ํ ์ค์ ํ ์ ์๋ค๋ ๋จ์ ์ด ์์ด์ ์์ฃผ ์ฌ์ฉ๋๋ ๊ธฐ๋ฅ์ ์๋ ๊ฒ ๊ฐ๋ค.
์๋ฆผ ์ฑ์ด๋ผ๋ฉด ๋งค ๋ถ๋ง๋ค ๋ฐ๋ณต์ ์ธ ์๋ฆผ์ ์ฝ๊ฒ ์ค์ ํ ์ ์๋ค๋ ๋ถ๋ถ์ ์ฅ์ ์ธ ๋ฏ ํ๋ค.
await _local.periodicallyShow(
type.id,
title,
body,
RepeatInterval.everyMinute,
details,
payload: type.deeplink,
);
RepeatInterval ํ์ ์ ์ด 4๊ฐ์ง๋ก ๋๋๋ฉฐ ๊ฐ๊ฐ 1๋ถ ์ฃผ๊ธฐ, 1์๊ฐ ์ฃผ๊ธฐ, ํ๋ฃจ ์ฃผ๊ธฐ, ํ ์ฃผ ์ฃผ๊ธฐ๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ๋ณด๋ผ ์ ์๋๋ก ์ค์ ํ๋ ๋ถ๋ถ์ด๋ค.
RepeatInterval.everyMinute
RepeatInterval.hourly
RepeatInterval.daily
RepeatInterval.weekly
์ต์
๋ ํ๋ผ๋ฏธํฐ๋ก androidScheduleMode๊ฐ ์๋๋ฐ, ํด๋น ๋ถ๋ถ์ ์๋๋ก์ด๋ ํ๋ซํผ ์ ์ฉ์ผ๋ก ์ฌ์ฉํ๋ ๊ธฐ๋ฅ์ด์ง๋ง, ํด๋น ํ๋ผ๋ฏธํฐ๋ ๋ฆด๋ฆฌ์ฆ ๋ฒ์ ์์ ์ ๊ฑฐ๋ ์์ ์ด๋ผ ํ์ฌ ๋ฐ๋ก ์ดํด๋ณด์ง ์์ ๊ฒ์ด๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก, ์ต์ flutter_local_notifications์์๋ androidAllowWhileIdle ์ต์
๋ ๋ ์ด์ ์ฌ์ฉ๋์ง ์์ผ๋ฏ๋ก ์ฌ์ฉํ์ง ์๋๋ก ํ๊ฒ ๋ค.
periodicallyShow์ ๋ํด์ ์ดํด๋ดค์ง๋ง, ๋งค๋ถ, ๋งค์๊ฐ ๋ฑ ์ ํด ๋์ ๊ท์น์ ์ํ ์ ์ก์ ์ฝ๊ฒ ๊ตฌํ์ด ๊ฐ๋ฅํ์ง๋ง, 5๋ถ ๋ง๋ค, 2์๊ฐ ๋ง๋ค... ์ด๋ฐ ๊ธฐ๋ฅ๋ค์ ๊ตฌํํ ์ ์๋ค.
์ด๋ฌํ ๊ธฐ๋ฅ๋ค์ ์ฌ์ฉํ๊ณ ์ ํ๋ค๋ฉด ์๋์์ ์ดํด๋ณผ Timezone์ ํ์ฉํ ์๊ฐ๋๋ฅผ ์ง์ ์์ฑํด์ ๊ตฌํํด์ผ ํ๋ค.
ํน์ ์์ ์ ์ ์ก ํ๊ฑฐ๋, ์ค์ผ์ฅด์ ์ค์ ํ์ฌ ๋งค์ผ, ๋งค์ฃผ, ๋งค์ ๋ฑ ์ผ์ ๊ธฐ๊ฐ์ ๋ฐ๋ผ ์ ์กํ ์ ์๋ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ค๋ฉด Timezone์ ์ค์ ํ์ฌ์ผ ํ๋ค.
Timezone์ Flutter์ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ด์ฅ๋์ด ์๋ ๋ด์ฅ ๊ฐ์ฒด๋ก ์ถ๊ฐ์ ์ผ๋ก ์ข ์์ฑ์ ์ถ๊ฐํ์ง ์์๋ ๋๋ค.
import 'package:timezone/data/latest_all.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
ํ์์กด์ ์ฌ์ฉํ๊ธฐ ์ ๋ฐ๋์ ์ด๊ธฐํ๋ฅผ ์งํํ์ฌ์ผ ํ๋ค.
tz.initializeTimeZones();
ํ์์กด์ ๋ก์ผ์ด์ ์ ์ค์ ํ ์ ์๋๋ฐ, ์ค์ ์ ํ์ง ์๋๋ค๋ฉด UTC ์๊ฐ๋๊ฐ ์ค์ ์ด ๋๋ค.
ํ์์กด ์ค์ ์ ๊ฐ ๋ก์ผ์ด์ ์ ๋ฃ์ด์ ์ฌ์ฉํ ์ ์๋ค. ๋ํ๋ฏผ๊ตญ์ Asis/Seoul ์ด๋ค.
tz.setLocalLocation(tz.getLocation('Asia/Seoul'));
์ด์ ํ์์กด์ ์ฌ์ฉํ์ฌ ์๊ฐ์ ๋ง๋ค์ด ์ฃผ๋๋ก ํด๋ณด์. DateTime ๊ฐ์ฒด์ฒ๋ผ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ๊ฑฐ์ ๋์ผํ์ฌ ํ์ฌ ์๊ฐ์ ๋ฐ์์ฌ ์๋ ์๊ณ , ์๊ฐ์ ๋ง๋ค์ด ์ค ์๋ ์๋ค.
๋ก์ปฌ ํธ์ ๊ธฐ๋ฅ์ ์์ด์๋ show() ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ฆ์ ์ ์ก, periodicallyShow()๋ฅผ ์ฌ์ฉํ ๊ธฐ๋ณธ์ ์ธ ์ฃผ๊ธฐ๋ก ์ ์กํ๋ ๊ฒ ๋ณด๋ค๋, ์๋์์ ์ดํด๋ณผ ํ์์กด์ ํ์ฉํ zonedSchedule() ๊ธฐ๋ฅ์ ๋ ๋ง์ด ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์, ํ์์กด์ ๋ํ ๋ถ๋ถ์ ์ ๊ธฐ์ตํ์๊ณ ์ฐ์ต์ ๋ง์ด ํด๋ณด์๊ธธ ๋ฐ๋๋ค.
TZDateTime์ ์ฌ์ฉํ์ฌ ์๊ฐ๋๋ฅผ ์์ฑํด ์ฃผ๋๋ฐ, ํ์ ๊ฐ์ผ๋ก ์ค์ ํด์ผ ํ ํ๋ผ๋ฏธํฐ๋ Locale์ด๋ค. ์ด๊ธฐํ์์ ๋ก์ผ์ด์ ์ค์ ์ ์งํ ํ์๊ธฐ์, ์ฌ์ฉ์์๋ ์ค์ ๋ local์ ๊ฐ์ ธ๋ค ์ฌ์ฉํ๋ฉด ๋๋ค.
tz.TZDateTime now = tz.TZDateTime.now(tz.local);
// 2024-01-22 00:00:00.000Z
tz.TZDateTime setting = tz.TZDateTime(tz.local, 2024, 1, 22);
// 2024-01-22 00:00:00.000Z
tz.TZDateTime duration = now.add(const Duration(hours: 3, minutes: 20));
// 2024-01-22 03:20:00.000Z
zonedSchedule์ ํ์์กด์ ์ฌ์ฉํด ์๊ฐ๋๋ฅผ ๋ง๋ค๊ณ , ์ผ์ ์ฃผ๊ธฐ๋ฅผ ๋ฐ๋ณตํด์ ํธ์๋ฅผ ๋ณด๋ด๋ ๋ฐฉ๋ฒ์ด๋ค. ๋ฌผ๋ก , ์ฃผ๊ธฐ๋ฅผ ๋ฃ์ด์ ๋ก์ปฌ ํธ์๋ฅผ ์์ฑํ ์ง .. ์๋๋ฉด ์ฃผ๊ธฐ ์์ด ์ผํ์ฑ์ผ๋ก๋ง ํธ์๋ฅผ ์์ฑํ ์ง๋ .. ํด๋น ๊ธฐ๋ฅ์ผ๋ก ๊ตฌํ ๊ฐ๋ฅํ๋ค.
๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด๋ค. ์ค์ ํ ํ์์กด์ ๋ก์ปฌ ํธ์๋ฅผ ์ ์กํ๊ฒ ๋๋ค. ๊ธฐ์กด์ ์ดํด๋ณธ ๋ค๋ฅธ ๊ธฐ๋ฅ๋ค๊ณผ ๊ฑฐ์ ์ ์ฌํ์ง๋ง ์ถ๊ฐ๋ ํ๋ผ๋ฏธํฐ๊ฐ scheduledDate, uiLocalNotificationDateInterpretation, matchDateTimeComponents ์ด๋ค.
tz.TZDateTime schedule = tz.TZDateTime.now(tz.local);
await _local.zonedSchedule(
id,
title,
body,
schedule,
details,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: null,
);
์ถ๊ฐ๋ ํ๋ผ๋ฏธํฐ์ ๋ํด์ ์ดํด๋ณด๋๋ก ํ์.
uiLocalNotificationDateInterpretation
absoluteTime, wallClockTime ์ ์ง์ ํ๋ ๋ถ๋ถ์ด๋ค. ์ ๋์ ์๊ฐ์ ์ฌ์ฉํ๋ฉด ๋๋ค.
[UILocalNotificationDateInterpretation.absoluteTime, UILocalNotificationDateInterpretation.wallClockTime]
matchDateTimeComponents
zonedSchedule ๊ธฐ๋ฅ์์ ๊ฐ์ฅ ์ค์ํ ๋ถ๋ถ์ด๋ผ๊ณ ํ ์ ์๋ค. ํด๋น ์ค์ ์ ๋ฐ๋ผ ์ฃผ๊ธฐ์ฑ์ ๊ฐ๋์ง ์ผํ์ฑ์ ๊ฐ๋์ง ๊ฒฐ์ ๋๊ฒ ๋๋ค.
DateTimeComponents.dateAndTime
DateTimeComponents.time
DateTimeComponents.dayOfWeekAndTime
DateTimeComponents.dayOfMonthAndTime
scheduledDate
์์์๋ ์ค๋ช
ํ ์๊ฐ๋๋ฅผ ์ค์ ํ๋ ๋ถ๋ถ์ด๋ค. ์๊ฐ๋ ์ค์ ์ TZTimeZone ๊ฐ์ฒด๋ก๋ง ์์ฑํด์ผ ํ๊ณ , ์ฌ๊ธฐ์ ์ค์ ๋๋ ์๊ฐ๋๋ก ์ ์ก๋๋ค.
ํ์ฌ ์๊ฐ์์ 5๋ถ ๋ค ํ ๋ฒ๋ง ๋ก์ปฌ ํธ์๋ฅผ ์ ์กํ ์ ์๋๋ก ํด๋ณด์. ํ ๋ฒ๋ง ์ ์กํ๊ณ ์ถ์ ๋๋ matchDateTimeComponents ์ต์ ์ ์ฌ์ฉํ์ง ์์ผ๋ฉด ๋๋ค. null์ ๋ฃ์ด๋ ๋๊ณ , DateTimeComponents.dateAndTime์ ๋ฃ์ด์ฃผ๋ฉด ์ฃผ๊ธฐ์ฑ์ ๊ฐ์ง ์๊ฒ ๋๋ค.
5๋ถ ํ๋ฅผ ๋ง๋๋ ค๋ฉด add ํจ์๋ฅผ ์ฌ์ฉํด์ ํ์ฌ ์๊ฐ๋์ 5๋ถ ์ ์ถ๊ฐํด์ฃผ๋ฉด 5๋ถ ๋ค ์๊ฐ์ผ๋ก ์ค์ ๋๋ค.
tz.TZDateTime schedule = tz.TZDateTime.now(tz.local).add(const Duration(minutes: 5));
await _local.zonedSchedule(
type.id,
title,
body,
schedule,
details,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: null,
);
๋งค์ผ ๋์ผ ์๊ฐ์ ์ ์ก์ ํ๊ธฐ ์ํด์๋ matchDateTimeComponents ํ์
์
DateTimeComponents.time ์ฃผ๋ฉด ์๊ฐ์ ๊ธฐ์ค์ผ๋ก ๋งค์ผ ๋ก์ปฌ ํธ์๋ฅผ ์ ์กํ ์ ์๊ฒ ๋๋ค.
tz.TZDateTime schedule = tz.TZDateTime.now(tz.local);
await _local.zonedSchedule(
type.id,
title,
body,
schedule,
details,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: DateTimeComponents.time,
);
๋งค์ฃผ ๋์ผ ์๊ฐ๋๋ฅผ ์ ์กํ๊ธฐ ์ํด์๋ DateTimeComponents.dayOfWeekAndTime ํ์ ์ผ๋ก ์ง์ ํด ์ฃผ๋ฉด ๋๋ค.
์ฌ๊ธฐ์ ํ ๊ฐ์ง๋ฅผ ๋ ์ดํด๋ณด๋ ค๊ณ ํ๋๋ฐ, ๋ฐ๋ก ์์์ ์ดํด๋ณธ periodicallyShow()์ repeat์ RepeatInterval.weekly๋ก ์ค์ ์ ํ์ ๋์ ๋์ผํ๋ค๋ ๊ฒ์ ๋๋ ์ ์๋ค.
๋ ๋ค ๊ฒฐ๊ณผ๋ ๋์ผํด ๋ณด์ด์ง๋ง ์ค์ ๋ก๋ ์์ ํ ๋ค๋ฅธ ๊ธฐ๋ฅ์ด๋ค.
periodicallyShow()๋ ๋งค์ฃผ ์ ์ก์ ๋ณด๋ผ ์ ์์ง๋ง ์๊ฐ์ ์ค์ ํ ์ ์๊ณ , ์์ฒญํ ์๊ฐ์ ๊ธฐ์ค์ผ๋ก๋ง ์ ์ก์ ๋ณด๋ผ ์ ์๋ ๋ฐ๋ฉด, zonedSchedule()์ ์ฌ์ฉํ๋ฉด TZDateTime์ ์์ฑํด ์๊ฐ๋๋ฅผ ๋ง๋ค ์ ์์ด ์ํ๋ ์๊ฐ๋์ ํธ์๋ฅผ ์ ์กํ ์ ์๋ค.
tz.TZDateTime schedule = tz.TZDateTime.now(tz.local);
await _local.zonedSchedule(
type.id,
title,
body,
schedule,
details,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: DateTimeComponents.dayOfWeekAndTime,
);
๋งค์ ๋์ผ ์๊ฐ๋ ์ ์ก์ DateTimeComponents.dayOfMonthAndTime ํ์ ์ ์ฌ์ฉํ๋ฉด ๋๋ค.
tz.TZDateTime schedule = tz.TZDateTime.now(tz.local);
await _local.zonedSchedule(
type.id,
title,
body,
schedule,
details,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: DateTimeComponents.dayOfMonthAndTime,
);
๋์ผ ์๊ฐ๋ ์ผ์ ์ฃผ๊ธฐ๋ฅผ ๊ธฐ์ค์ผ๋ก zonedSchedule() ๊ธฐ๋ฅ์ ๋ํด์ ์ดํด๋ดค๋ค.
์๊ฐ๋๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ์ ๋์ผํ๊ณ , ํ์ ๋ง ๋ณ๊ฒฝํด ์ฃผ๋ฉด ๋๋ค. ์ฌ๊ธฐ์ ํ ๊ฐ์ง ํ์คํ ์ดํดํด์ผ ํ๋ ๊ฒ์ zonedSchedule()์ ๋ฐ๋์ ๋งค์ผ์ ๊ธฐ๋ณธ์ผ๋ก ๋ณด๋ด๋ ๊ธฐ๋ฅ์ด์ง, ๋งค ์๊ฐ, 2๋ถ ๋ง๋ค, 3์๊ฐ ๋ง๋ค, 2์ผ ๋ง๋ค.. ์ด๋ฐ ๊ธฐ๋ฅ์ ์๋๋ค. ๋ฌด์กฐ๊ฑด ํ๋ฃจ๋ฅผ ๊ธฐ์ค์ผ๋ก ํ๋ค.
์๊ฐ๋์ ๋ํด์ ์์ธํ ์ดํด๋ณผ ๋ถ๋ถ์ด ์๋ค.
ํ์์กด์ ์ด๊ธฐํํ ๋์ Location์ ์ค์ ํด ์ฃผ๋ ๋ถ๋ถ์ด๋ค. ์ง๊ธ์ฒ๋ผ "Asia/Seoul"์ Location์ผ๋ก ์ง์ ํ๊ฒ ๋๋ฉด ๋ฌธ์ ๊ฐ ์๋ค.
tz.initializeTimeZones();
tz.setLocalLocation(tz.getLocation('Asia/Seoul'));
์ค์ปท ๊ธ๋ก๋ฒ ์๋น์ค ์ฑ์ ๊ฐ๋ฐ ํ๋ ์ค ํด์ธ ์ฌํ์ ๊ฐ์ ๋์ ๊ฐ๋ฐํ ์ฑ์์ ๋ก์ปฌ ํธ์๊ฐ ํ๊ตญ์์ ์ค์ ํ ์๊ฐ๋๋ก๋ง ์ค๋ ๋ฌธ์ ๊ฐ ์์๋ค.
๋ฐ๋ก Location์ ๋ํ๋ฏผ๊ตญ์ผ๋ก ์ง์ ํด ๋จ๊ธฐ ๋๋ฌธ์ธ๋ฐ, ๊ตญ๋ด๋ง ์๋น์ค ํ ๊ฑฐ๋ผ๋ฉด ๋ฌธ์ ๊ฐ ์๋ ์๋ ์์ง๋ง, ํด์ธ์์ ์ ์ํ๋ ์ฌ์ฉ์๋ ์๊ฐ๋๊ฐ ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ ์ด ๋ถ๋ถ์ ๊ณ ๋ คํ์ง ์์ ์ ์๊ฒ ๋๋ค.
Location์ ์ค์ ํ์ง ์์ผ๋ฉด ๊ธฐ๋ณธ Location์ UTC ํ์์กด์ด ๋๊ธฐ ๋๋ฌธ์, UTC ํ์์กด์ ์ฌ์ฉํ์ฌ ์๊ฐ๋๋ฅผ ๋ง๋ค์ด ์ฃผ๋ฉด ์ด๋ฌํ ๋ฌธ์ ๋ ํด๊ฒฐ์ด ๋๋ค.
SetLocalData
ํ์ฌ ์๊ฐ๋์ ํ์์กด์ UTC ํ์์กด๊ณผ์ ์ฐจ์ด๋ฅผ ์ด์ฉํ์ฌ, ๋ก์ปฌ ํธ์๋ก ์ค์ผ์ฅด๋ง์ ์งํํ ๋์ UTC ํ์์ผ๋ก ๋ณํํด์ ์ค์ ์ ํด์ฃผ๋ฉด ๋๋ค.
tz.TZDateTime _setDate(DateTime date) {
Duration offSet = DateTime.now().timeZoneOffset;
DateTime local = date.add(-offSet);
return tz.TZDateTime(tz.local, local.year, local.month, local.day,
local.hour, local.minute, local.second);
}
์ด๊ธฐํ์์ Location์ ์ง์ ํด์ฃผ๋ ์ฝ๋๋ ์ ๊ฑฐํ๋๋ก ํ๊ฒ ๋ค.
// tz.setLocalLocation(tz.getLocation('Asia/Seoul'));
UTC ํ์์ผ๋ก ๋ณํ์ DateTime์์ ์ ๊ณตํด์ฃผ๋ UTC ๋ณํ์ ์ฌ์ฉํด๋ ๋๋ค.
์ด ๋ถ๋ถ์ ์์ธํ ์ดํด๋ณด๋ฉด, ๋จผ์ ํ์ฌ ์๊ฐ๋์ UTC ํ์์กด๊ณผ์ ์๊ฐ์ฐจ๋ฅผ ๊ฐ์ ธ์ค์. ๋ํ๋ฏผ๊ตญ์ 9์๊ฐ์ด ๋น ๋ฅด๋ค.
Duration offSet = DateTime.now().timeZoneOffset;
print(offSet);
// 9:00:00.000000
์ค์ ํ๊ณ ์ ํ๋ ์๊ฐ๋์์ UTC ํ์์กด ๊ฐ์ ์ฐจ์ด๋งํผ ์๊ฐ๋๋ฅผ ๋ค์ ์์ฑํด ์ฃผ์.
์ด๋ ๊ฒ ๋๋ฉด ์ํ๋ ์๊ฐ๋์ ํธ์๊ฐ ์ ํํ๊ฒ ์ ์ก์ด ๋๋ค.
print(DateTime.now());
// 2024-01-20 13:40:17.146397
DateTime local = date.add(-offSet);
print(local);
// 2024-01-20 04:40:00.000
Location ์ง์ ์์ด, ๊ฐ ํ์์กด ์๊ฐ์ ๋ง๊ฒ ๋ก์ปฌ ํธ์๊ฐ ์ค์ผ์ฅด๋ง ๋๊ธธ ์ํ๋ค๋ฉด Location ์ค์ ์ ๋ณ๋๋ก ํด์ฃผ์ง ์๊ณ , UTC ํ์์ผ๋ก ๋ก์ปฌ ํธ์๋ฅผ ์ ์กํ๋ฉด ๋๋ค.
๋ก์ปฌ ํธ์ ๋ฟ๋ง ์๋๋ผ ์ฑ์์ ์๊ฐ์ ์ฌ์ฉํ ๋์๋ UTC ํ์์ ๊ธฐ์ค์ผ๋ก ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ถ์ฒํ๋ค.
SetWeeklyDate
์ด๋ฒ์๋ ์ฃผ๊ฐ ๋ง๋ค ๋ก์ปฌ ํธ์๊ฐ ์ ์ก๋์ผ ํ ๋์, ์์ผ์ ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ์ ๋ํด์ ์ดํด๋ณด๋๋ก ํ์. ์ด ๋ถ๋ถ์ ๊ตฌ๊ธ๋ง ํด๋ณด๋ฉด ๋ค๋ฅธ ๋ฐฉ๋ฒ๋ ๋ง์ผ๋ ํธํ์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์๋ฉด ๋๋ค.
0์ด ์์์ผ ์ด๋ค.
์ค๋์ด ์์์ผ์ธ๋ฐ, ์์์ผ์ ๊ธฐ์ค์ผ๋ก ๋งค์ฃผ ๋ก์ปฌ ํธ์ ์ค์ผ์ฅด๋ง์ ํด์ผํ๋ค๋ฉด, ๋ค๊ฐ์ค๋ ์์์ผ ๋๋ ์์์ผ์ ํด๋นํ๋ ๋ ์ง๋ก ํธ์๊ฐ ์์ฝ๋์ด์ผ ์์์ผ์ ๊ธฐ์ค์ผ๋ก ๋งค์ฃผ ๋ก์ปฌ ํธ์๋ฅผ ์ ์กํ ์ ์๋ค.
void _setWeekDate() {
Duration offSet = DateTime.now().timeZoneOffset;
DateTime local = DateTime.now().add(-offSet);
DateTime start = local.subtract(Duration(days: local.weekday - 0));
DateTime weekDate = switch (index) {
0 => start.add(const Duration(days: 1)),
1 => start.add(const Duration(days: 2)),
2 => start.add(const Duration(days: 3)),
3 => start.add(const Duration(days: 4)),
4 => start.add(const Duration(days: 5)),
5 => start.add(const Duration(days: 6)),
6 => start.add(const Duration(days: 0)),
_ => start,
};
}
zonedSchedule() ๊ธฐ๋ฅ์ flutter_local_notifications์ ์ ์ก ์ฃผ๊ธฐ์ ์๊ฐ๋๋ฅผ ๋ค๋ฃจ๋๋ฐ์ ์์ด์ ์ค์ํ ๋ถ๋ถ์ด ๋ง์ผ๋, ๊ผญ ์ฌ๋ฌ๋ฒ ํ ์คํธ๋ฅผ ์งํํ๋ฉด์ ์ฌ์ฉํด ๋ณด์๊ธธ ๋ฐ๋๋ค.
๋ก์ปฌ ํธ์์ ์ค์ผ์ฅด์ ๋ฑ๋กํ๋ ๋ถ๋ถ์ ๋ํด์ ์ดํด๋ดค๋ค.
๊ทธ๋ ๋ค๋ฉด ๋ฑ๋ก๋ ์ค์ผ์ฅด ๋ชฉ๋ก์ ์ป์ผ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น?
flutter_local_notifications ํจํค์ง์์๋ ๊ฐ๋จํ๊ฒ ํจ์๋ฅผ ํธ์ถํ๋ฉด ๋ฐฐ์ด์ ๋ด์์ ์ค์ผ์ฅด ๋ชฉ๋ก์ ๋ฐํํด์ค๋ค.
pendingNotificationRequests() ํจ์๋ง ํธ์ถํด์ฃผ๋ฉด ๋๊ณ , ๋น๋๊ธฐ๋ก ๋ฐํ๋๋ ํจ์์ด๋ฏ๋ก ๋น๋๊ธฐ์ ๋ํ ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ๋ฉด ๋๋ค.
List<PendingNotificationRequest> notifications = await local.pendingNotificationRequests();
PendingNotificationRequest ๊ฐ์ฒด๋ ์ด๋ค ์ ๋ณด๋ค์ ๋ฐํํด ์ฃผ๋์ง ์ฝ์์ ์ถ๋ ฅํด๋ณด๋ฉด, title, body, payload, id ์ด๋ ๊ฒ ์ ๋ณด๋ฅผ ์ ๊ณตํด์ค๋ค.
์ด๋ฒ์๋ ๋ฑ๋ก์ด ์๋ ๋ฑ๋ก๋ ์ค์ผ์ฅด์ ์ ์ก์ ์ทจ์ํ๋ ๋ฐฉ๋ฒ์ ๋ํด์ ์ดํด๋ณด๋๋ก ํ๊ฒ ๋ค.
์ทจ์๋ ๊ฐ๋จํ๋ค. ๋จ์ผ ํธ์ ์ค์ผ์ฅด์ ์ทจ์ํ ์๋ ์๊ณ ํ ๋ฒ์ ์ ์ฒด ์ค์ผ์ฅด์ ์ทจ์ํ ์๋ ์๋ค.
cancel
๋จ์ผ ํธ์ ์ค์ผ์ฅด์ ์ทจ์ํ๋ ๋ฐฉ๋ฒ์ด๋ค. ํ ๊ฐ์ ์ค์ผ์ฅด์ ์ทจ์ํ๊ธฐ ์ํด์ ๋ฐ๋์ ์๊ณ ์์ด์ผ ํ๋ ๋ฐ์ดํฐ๋ ๋ฐ๋ก id์ด๋ค.
id๋ ์ฑ ๋ด์์ ์ ํด์ง ๊ท์น์ผ๋ก ์์ฑํ์ฌ ๊ด๋ฆฌํด๋ ๋๊ณ , ์์์ ์ดํด๋ณธ PendingNotificationRequest ๊ฐ์ฒด๋ฅผ ์ป์ด์ id๋ฅผ ๊ด๋ฆฌํด๋ ๋๋ค.
final FlutterLocalNotificationsPlugin local = FlutterLocalNotificationsPlugin();
local.cancel(id);
cancelAll
ํ ๋ฒ์ ๋ชจ๋ ์ค์ผ์ฅด์ ์ ๋ถ ์ทจ์ํ๋ ค๋ฉด ๊ฐ๋จํ cancelAll() ํจ์๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
final FlutterLocalNotificationsPlugin local = FlutterLocalNotificationsPlugin();
local.cancelAll();
์ง๊ธ๊น์ง flutter_local_notifications ์ค์ ๋ถํฐ, ๋ก์ปฌ ํธ์๋ฅผ ์ ์กํ๊ณ , ์ค์ผ์ฅด์ ๋ฑ๋กํ๊ณ ์ทจ์ํ๋ ๋ฐฉ๋ฒ๊น์ง ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ๋ํด์ ์ดํด๋ดค๋ค.
์์ ๋ฐ์ ํธ์๋ฅผ ์ฌ์ฉ์๊ฐ ํด๋ฆญํด์ ์ฑ์ด ์คํ ๋์์ ๋์, ์ํ๋ ์๋์ ํ ์ ์์ผ๋ ค๋ฉด ํธ์ ์๋ฆผ์ ํด๋ฆญํด์ ์ง์ ํ ์ฌ์ฉ์๋ผ๋ ๊ฒ์ ์์์ผํ๊ณ , ์ถ๊ฐ์ ์ผ๋ก ํธ์ ์๋ฆผ์ ์ด๋ค ์ ๋ณด๊ฐ ๋ด๊ฒจ ์๋์ง๋ ํ์ ํ์ฌ์ผ ์ฑ ๋ด์์ ์ํ๋ ์ด๋ฒคํธ๋ฅผ ์คํ์ํฌ ์ ์์ ๊ฒ์ด๋ค.
์ฑ์์ ์ฌ์ฉํ๋ ์๋ฆผ์ ๋ํด ๋ชจ๋ฅด์๋ ๋ถ๋ถ์ ์์ผ์๊ฒ ์ง๋ง, ํน์๋ผ๋ ๋ชจ๋ฅด์๋ ๋ถ๋ค์ ๋ฅ๋งํฌ์ ์ฑ์ ์ํ์ ๋ํด์ ์ดํด๋ฅผ ํ๊ณ ์์ผ์ ์ผ ํ๋ค.
์ด์ ์ ์ฑ์์ ์ฌ์ฉํ๋ ๋งํฌ์ ๋ํ ์ฌ์ฉ ๋ฐฉ๋ฒ๊ณผ ์ค๋ช ์ ์์ฑํ ๊ธ์ด ์์ผ๋ ํด๋น ๋ด์ฉ ์ฐธ๊ณ ํ์๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค.
URL Scheme ์ฒด๊ณ ์ดํดํด ๋ณด๊ธฐ
Firebase DynamicLinks ์ฌ์ฉํด ๋ณด๊ธฐ
Firebase FCM(Firebase Cloud Message) ์ฌ์ฉํด ๋ณด๊ธฐ
์ฐ๋ฆฌ๊ฐ ์์์ ํธ์๋ฅผ ์ ์กํ ๋์ payload ๋ผ๋ ๋ถ๋ถ์ ๋ฐ์ดํฐ๋ฅผ ๋ฃ์ด์ค ์ ์๊ฒ ๋์ด์๋๋ฐ, payload์ ๋ฃ์ด์ฃผ๋ ๋ฐ์ดํฐ๊ฐ ๋ฐ๋ก ์์ ๋ฐ๋ ๋ฐ์ดํฐ๊ฐ ๋๋ค.
์ผ๋ฐ์ ์ผ๋ก ํธ์๋ฅผ ๋ณด๋ด๋ ๋ถ๋ถ์ ์ฌ์ฉ์์๊ฒ ํน์ ์๋ฆผ์ ์ฃผ๊ธฐ ์ํด ์ ์ก์ ํ ๊ฒ์ธ๋ฐ, ์ํ๋ ๊ฒ์ ๊ฒฐ๊ตญ ์ฌ์ฉ์๋ฅผ ํน์ ์์ญ์ผ๋ก ์ด๋์์ผ์ผ ํ๋ค๋ ๊ฒ์ด๋ค.
Flutter์์ ์ฑ์ ํ์ด์ง๋ฅผ ์ด๋ํ ๋์ Named Router๋ฅผ ์ฌ์ฉํ๋๋ฐ, payload์ ํ์ด์ง ๋ค์์ ๋ฃ์ด์ ์ฌ์ฉํ๋ฉด ํธ์ ์คํ ์ฆ์ ์ํ๋ ์์ญ์ผ๋ก ๋ผ์ฐํ ์ ๋ณด๋ผ ์ ์์ ๊ฒ์ด๋ค.
payload๋ฅผ ์ด๋ป๊ฒ ์ฌ์ฉํ ์ง๋ ๊ฐ๋ฐ์ ์์ ๋, ์ํ๋ ์ด๋ฒคํธ๋ฅผ ๋ง๋ค์ด ์ฃผ๋ฉด๋๊ณ ์ฐ์ ํธ์ ํด๋ฆญ์ ๋ฐ์ดํฐ๋ฅผ ์์ ํ ์ ์๋๋ก ๊ฐ๋ฐ์ ํด๋ณด์.
์ถ๊ฐ์ ์ผ๋ก ํธ์ ์๋ฆผ์ ์ฌ์ฉํ๋ ์ฑ์ ์ํ๋ 3๊ฐ์ง๊ฐ ์๋ค. ์ฑ ์คํ ์ค ํ๊ฒฝ, ์ฑ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ํ, ์ฑ ์ข ๋ฃ ์ํ ์ด๋ ๊ฒ 3๊ฐ๋ฅผ ์ฌ์ฉํ๋๋ฐ, ๊ฐ ํ๊ฒฝ์์ ํธ์ ์๋ฆผ์ ์์ ํ๋ ๋ฐฉ๋ฒ์ ์ฐจ์ด๊ฐ ์๋ค.
์ฑ ์คํ ์ค ์ํ์ธ Foreground์ Background ์ํ๋ ๋์ผํ ๋ฆฌ์ค๋๋ก ์์ ์ด ๊ฐ๋ฅํ๋ค.
์ถ๊ฐ์ ์ผ๋ก Foreground, Background๋ฅผ ๊ตฌ๋ถํด์ผ ํ๋ค๋ฉด AppLifeCycle์ ์ฌ์ฉํ ์ํ ๊ด๋ฆฌ๊ฐ ์ถ๊ฐ์ ์ผ๋ก ํ์ํ๋ค.
FlutterLocalNotificationsPlugin์ ์ต์ด ์ด๊ธฐํ์์ initialize() ํจ์๋ฅผ ์คํ์์ผ ์ฃผ์ด์ผ ํ๋๋ฐ, ์ต์ ํ๋ผ๋ฏธํฐ๋ก onDidReceiveNotificationResponse ์ต์ ์ด ์๋ค.
๋ฐ๋ก ์ฌ๊ธฐ์ ๋ฐฑ๊ทธ๋ผ์ด๋, ํฌ์ด๊ทธ๋ผ์ด๋ ์ํ๋ฅผ ๋ฐ์๋ณผ ์ ์๋ค.
์ด๊ธฐํ๋ ์ฑ์ ์ต์๋จ ์์ ฏ ํธ๋ฆฌ์์ ์งํํด ์ฃผ๋ฉด ํ์ ์์ ฏํธ๋ฆฌ ์ด๋ ์์ญ์ ์์ด๋ ํด๋น ๊ธฐ๋ฅ์ด ์๋๋๋, ์์ ฏํธ๋ฆฌ๋ฅผ ๊ณ ๋ คํด์ ์ํ๋ ์์น์์ ์ด๊ธฐํ๋ฅผ ์์ผ์ฃผ๋ ๊ฒ์ด ์ค์ํ๋ค.
์ ๋ MaterialApp() ์คํ ํ initialPage๊ฐ ์ด๊ธฐํ ๋ ๋์ FlutterLocalNotificationsPlugin๋ ์ด๊ธฐํ๋ฅผ ์งํํด ์ค๋ค.
๋ฆฌ์ค๋๊ฐ ์คํ๋๋ฉด NotificationResponse ๊ฐ์ฒด๋ฅผ ๋ฐํํด ์ฃผ๋๋ฐ, ํด๋น ๊ฐ์ฒด์์ payload ๊ฐ์ด ๋ด๊ฒจ์ ธ ์์ด์ ์ํ๋ ์ด๋ฒคํธ๋ฅผ ์คํํด ์ฃผ๋ฉด ๋๋ค.
final FlutterLocalNotificationsPlugin local = FlutterLocalNotificationsPlugin();
await local.initialize(
settings,
onDidReceiveNotificationResponse: (NotificationResponse details) {
if (details.payload != null) {
print(details.payload);
}
},
);
์ฑ ์ข ๋ฃ ์ํ์์์ ๋ก์ปฌ ํธ์ ๋ฆฌ์ค๋๋ฅผ ๊ตฌ๋ ํ๋ ๋ฐฉ๋ฒ์ ๋ํด์ ์ดํด๋ณด์. ์ฑ ์ข ๋ฃ ์ํ๋ ์ฑ์ด ์์ ํ ์ข ๋ฃ๋ ์ํ์์ ์์ ๋๋ ๊ธฐ๋ฅ์ด์ด์ ์ฑ์ ์ต์๋จ์์ ์ต์ด ์ด๊ธฐํ ๋ ๋์ ํ ๋ฒ๋ง ์คํํ๋ฉด ๋๋ค.
ํ ๋ฒ๋ง ์คํ๋๋ ํธ๋ฆฌ๊ฐ ์๋ ์ฌ๋ฌ๋ฒ ์ฌ ์คํ ๋๋ ๋ถ๋ถ์ ํด๋น ํจ์๋ฅผ ์คํ์ํค๋ฉด ๊ณ์ ์์ ๋๋, ์ฑ ์คํ ์ต์ด ํ ๋ฒ๋ง ์คํํ๋ ๊ฒ์ด ์ค์ํ๋ค.
getNotificationAppLaunchDetails() ํจ์๋ฅผ ์คํ ์ํค๋ฉด ๋ก์ปฌ ํธ์๋ฅผ ํด๋ฆญ ํ์ ๋์ ๋ฐ์ดํฐ๋ฅผ ํด๋น ํจ์๊ฐ ๋ฐํํด ์ค๋ค.
NotificationAppLaunchDetails? details = await local.getNotificationAppLaunchDetails();
if (details != null) {
if (details.notificationResponse != null) {
if (details.notificationResponse!.payload != null) {
//
}
}
}
๋ฆฌ์ค๋ ๋ถ๋ถ์ด ์ ์๋๋์ง ์๊ฑฐ๋, ์ถ๊ฐ์ ์ผ๋ก Remote Push, Links ์ฌ์ฉ ๋ฑ์ผ๋ก ์ด๋ป๊ฒ ํด์ผ ํ๋์ง ์ ๋ชจ๋ฅด์๋ ๋ถ๋ค์ ๋๊ธ ๋จ๊ฒจ์ฃผ์๋ฉด ์ต๋ํ ๋์ ๋๋ฆฌ๊ฒ ์ต๋๋ค.
์ง๊ธ๊น์ง flutter_local_notifications๋ฅผ ์ฌ์ฉํ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ๋ฐฉ๋ฒ์ ๋ํด์ ์ดํด๋ดค๋ค.
๊ธฐ๋ณธ์ ์ธ ๋ด์ฉ์ด๋ผ๊ณ ํด๋ ์ฌ์ค ์์์ ์ดํด๋ณธ ๋ด์ฉ๋ง ์์ด๋ ๋ก์ปฌ ํธ์ ๊ตฌ์ฑ์ ๋๋ฌ๋ค๊ณ ๋ด๋ ๋๋ค. ์ด์ ๋ถํฐ ์ค๋ช ํ ๋ด์ฉ์ ์ต์ ๋ํ ๋ถ๋ถ์ด๋, ๋ก์ปฌ ํธ์ ๊ตฌ์ฑ์ ๋ค์ํ๊ฒ ํด๋ณด์ค ๋ถ์ด๋ผ๋ฉด ๊ผญ ๋ด์ผํ ๋ด์ฉ์ ์๋ ์๋ ์๋ค.
์๋๋ ์ด์ ๋ถํฐ ์๊ฐํ ๋ด์ฉ์ ์ฌ์ฉํด๋ณด๊ณ ์ ๋ฆฌํ๊ธฐ ์ํด ๊ธ์ ์์ฑํ๋ ค๊ณ ํ๊ฒ์ธ๋ฐ, ๋ฐฐ๋ณด๋ค ๋ฐฐ๊ผฝ์ด ๋ ์ปค์ ธ๋ฒ๋ ธ๋ค... ๋ก์ปฌ ํธ์๋ ํ ๋ฒ ์ธํ ํ๊ณ ํ ์คํธ ํ๊ธฐ๊น์ง ์ค๋ ์๊ฐ์ด ๊ฑธ๋ฆฌ๊ณ , ๋ด์ฉ๋ ๋ง๋ค...
์ด๋ฒ์ ์์ฑํ๋ฉด์ ๋ก์ปฌ ํธ์์ ๊ธฐ๋ฅ๋ค์ ๋ด์ ์ฑ์ ํ๋ ๊ฐ๋ฐ ํ์๋๋ฐ, ํ์ํ์ ๋ถ์ ๊ธ ๋ง์ง๋ง์ ์์ฑํ ๋ด์ฉ์ ์ฐธ๊ณ ํ์ ์ APK๋ก ์๋๋ก์ด๋์์ ์ง์ ์ฑ์ ์คํํด ๋ณด์ค ์ ์๋ค.
์ ์ด์ ๋ก์ปฌ ํธ์๋ฅผ ์ข ๋ ๋ฉ์ง๊ฒ ๋ง๋ค์ด ๋ณด์.
* ์๋ ๋ด์ฉ์ ์ ํํ ํ ์คํธ๋ฅผ ์ ๋ถ ์งํํ์ง๋ ๋ชปํด์ ์ผ๋ถ ๋ด์ฉ์ด ๋ค๋ฅผ ์ ์์ผ๋, ๋ ์์ธํ๊ฒ ์๊ณ ์ถ์ผ์ ๋ด์ฉ์ด ์๋ค๋ฉด ํธํ๊ฒ ๋๊ธ ๋จ๊ฒจ์ฃผ์ธ์! ์ถ๊ฐ์ ์ผ๋ก ํ ์คํธ ํ ๋ด์ฉ ์์ ํ๋๋ก ํ๊ฒ ์ต๋๋ค !
์ฑ์ ์ฌ์ฉํ๋ฉด์ ํธ์ ์๋ฆผ์ ๋ฐ์ ๋ณด๋ฉด ์ด๋ฏธ์ง๊ฐ ์๋ ํธ์๋ค์ด ์์์ ๊ฒ์ด๋ค. ์ด๋ฏธ์ง๋ ์ด๋ป๊ฒ ๋ณด๋ด์ผ ํ ๊น ?
Remote ํธ์์ ๊ฒฝ์ฐ FCM์ ํตํด ์ด๋ฏธ์ง๋ฅผ ๋ณด๋ด๋ ๊ฒ์ด ๊ฐ๋จํ๋ค.
๋ก์ปฌ ํธ์์์๋ ์ด๋ ต์ง ์๊ฒ ์ด๋ฏธ์ง๋ฅผ ์ค์ด ๋ณด๋ผ ์ ์๋๋ฐ, ์ด ๋ถ๋ถ์ ์ดํด๋ณด๋๋ก ํ์.
๋ก์ปฌ ํธ์์์ ์ด๋ฏธ์ง๋ฅผ ์ค์ด์ ์ ์กํ๋ ค๋ฉด, NotificationDetails ๊ฐ์ฒด์ ํ๋ซํผ๋ณ ์ธํ ์์ ์ด๋ฏธ์ง ์ ๋ณด๋ฅผ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค.
NotificationDetails ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ค๋ ์๋ฏธ๋ ํ๋ซํผ๋ณ ์ฌ์ฉ ๋ฐฉ๋ฒ์ด ๋ค๋ฅผ ์ ์๋ค๋ ์๋ฏธ๊ธฐ๋ ํ๋ค.
๋จผ์ , ios์ DarwinNotificationDetails๋ฅผ ์ดํด๋ณด๋ฉด, attachments ์ต์
๋ ํ๋ผ๋ฏธํฐ๊ฐ ๋ณด์ธ๋ค. ๋ฐฐ์ด๋ก DarwinNotificationAttachment ๊ฐ์ฒด๋ฅผ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค๊ณ ํ๋ค.
DarwinNotificationAttachment๋ ํ์ ํ๋ผ๋ฏธํฐ๋ก filePath๋ฅผ ๋ฐ์์ค๊ฒ ๋์ด์๋ค. filePath๋ File ์ ์ฅ์์ ์ ์ฅ๋ ์ด๋ฏธ์ง์ ๊ฒฝ๋ก์ด๋ค.
์ด๋ฒ์๋ android ๋ถ๋ถ์ ์ดํด๋ณด์.
AndroidNotificationDetails ๊ฐ์ฒด์ styleInformation ํ๋ผ๋ฏธํฐ ๋ถ๋ถ์ ์ดํด๋ณด๋ฉด ํธ์ ์ ์ก์ ํธ์ ํ
ํ๋ฆฟ ๋ณ๊ฒฝ์ ์ฌ์ฉ๋๋ค๊ณ ํ๋ค.
StyleInformation ๊ฐ์ฒด๋ InBox, Media, Default, BigPicture ๋ฑ ๋ค์ํ ํํ์ ํ์
์ ๊ฐ์ฒด๋ค์ด ์ฌ์ฉ๋๊ณ ์๋๋ฐ, ์ฌ๊ธฐ์ ์ด๋ฏธ์ง์ ์ฌ์ฉ๋๋ ๊ฒ์ BigPictureStyleInformation ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํด์ฃผ๋ฉด ๋๋ค.
BigPictureStyleInformation ๊ฐ์ฒด๋ ํ์ ๊ฐ์ผ๋ก AndroidBitmap์ ๋๊ฒจ ๋ฐ๊ฒ ๋๋๋ฐ, ์ด๊ฒ๋ ios์ ๋์ผํ๊ฒ File ์ ์ฅ์์ ์ ์ฅ๋ ์ด๋ฏธ์ง ๊ฒฝ๋ก๋ฅผ ํ์๋ก ํ๋ค.
์ด์ ์ด๋ฏธ์ง๊ฐ ๋ ธ์ถ๋ ์ ์๋๋ก ํธ์ ์ ์ก์ ์ด๋ค ๋ฐ์ดํฐ๊ฐ ํ์ํ์ง ์ดํด๋ดค๋ค.
๋ก์ปฌ ํธ์๋ฅผ ์ ์กํ๊ธฐ ์ ์ ์ฐ์ ์ ์ผ๋ก ์ด๋ฏธ์ง๋ฅผ File๋ก ๋ณํํ๋ ๋ฐฉ๋ฒ์ ๋ํด์ ์์๋ณด์. ์ด ๋ถ๋ถ์ ์ด๋ฏธ ๋๋ฆฌ ์ฌ์ฉ๋๋ ๋ฐฉ๋ฒ์ด๊ธฐ์ ๊ฐ๋จํ๊ฒ๋ง ๋ณด๊ฒ ๋ค.
์๋ ํจํค์ง๋ค์ ์ถ๊ฐํด์ฃผ์.
http : HTTP ํต์ ์ ์ฌ์ฉ
path_provider : ์ฑ File system ๊ฒฝ๋ก๋ฅผ ํ์ธํ๊ธฐ ์ํด ์ฌ์ฉ
dependencies:
http: ^1.2.0
path_provider: ^2.1.2
AssetToFile
Asset ์ด๋ฏธ์ง๋ฅผ File ์ ์ฅ์์ ์ ์ฅํ ํ ๊ฒฝ๋ก๋ฅผ ๊ฐ์ ธ์ค๋ฉด ๋๋ค.
import 'package:path_provider/path_provider.dart';
final String asset = "your_asset_image";
final ByteData byteData = await rootBundle.load(asset);
final Directory directory = await getTemporaryDirectory();
final File file = File('${directory.path}/${asset.split('/').last}');
await file.writeAsBytes(byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes));
print(file.path);
NetworkImageToFile
Network ์ด๋ฏธ์ง๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ ์ด๋ฏธ์ง๋ฅผ ๋ถ๋ฌ์ File system ๊ฒฝ๋ก์ ์ ์ฅํ๋ฉด ๋๋ค.
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
try {
final http.Response response = await http.get(Uri.parse(url));
final Directory directory = await getTemporaryDirectory();
final String name = "${directory.path}/${url.split('/').last}.png";
final File file = File(name);
await file.writeAsBytes(response.bodyBytes);
print(file.path);
} catch (_) {
return;
}
๋ก์ปฌ ํธ์ ์ ์ก์ ios์ attachments, android์ styleInformation์ ์ฌ์ฉํด์ ์ด๋ฏธ์ง๋ฅผ ์ ์กํ๋ฉด ๋๋ค.
ios์ attachments๋ ๋ฐฐ์ด์ ์ ๋ฌํ ์ ์๊ฒ ๋์ด์๋๋ฐ, ์ฒซ ๋ฒ์งธ ๋ฐฐ์ด๋ง ์ฌ์ฉ์ด ๋๊ธฐ ๋๋ฌธ์, ๋จ ํ๊ฐ์ DarwinNotificationAttachment ๊ฐ์ฒด๋ฅผ ๋ฃ์ด์ ์ฌ์ฉํด์ฃผ์.
NotificationDetails(
iOS: DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
badgeNumber: 1,
attachments: [
DarwinNotificationAttachment(filePath)
],
),
android: AndroidNotificationDetails(
send.type.channelId,
send.type.channelName,
channelDescription: send.type.channelDescription,
importance: Importance.max,
priority: Priority.high,
styleInformation: BigPictureStyleInformation(
FilePathAndroidBitmap(filePath),
),
),
);
๊ฐ๊ฐ ํ๋ซํผ ๋ณ๋ก ์ ์ก ํ ์คํธ๋ฅผ ์งํํด๋ณด์. IOS๋ ์ด๋ฏธ์ง ์ฌ์ด์ฆ์ ๋ง์ถฐ์ ์๋ฆผ์ ์ฌ์ด์ฆ๊ฐ ๋์ ์ผ๋ก ๋ณํ๋ ๋ฐ๋ฉด, Android๋ ์ง์ ๋ ํธ์ ์ด๋ฏธ์ง ์ฌ์ด์ฆ ๊ท๊ฒฉ์ด ์์ด ํด๋น ์ฌ์ด์ฆ๋ก๋ง ๋ ธ์ถ๋๋ ๊ฒ์ผ๋ก ํ์ธํ ์ ์๋ค.
Android BigPicture ํฌ๋งท์ ๋ํด์ ์ข ๋ ์ดํด๋ณผ ๊ธฐ๋ฅ์ด ์๋ค. ๋ฐ๋ก ์๋ฆผ์ ํผ์ณค์ ๋์ ๋ ธ์ถ ๋๋ ํฐ ์ฌ์ด์ฆ์ ์์ด์ฝ๊ณผ, ํ์ดํ, ๋ณธ๋ฌธ ๋ด์ฉ์ ๋ฐ๊ฟ ์ ์๋ค๋ ๊ฒ์ด๋ค.
์ฌ์ฉ ๋ฐฉ๋ฒ์ ๊ฐ๋จํ๋ฐ, ์์ด์ฝ์ ์์์ ์ด๋ฏธ์ง๋ฅผ File ์์คํ ์ ์ ์ฅํด์ ์ฌ์ฉํ ๊ฒ๊ณผ ๋์ผํ ๋ฐฉ๋ฒ์ ์ฌ์ฉํด ์ฃผ๋ฉด ๋๋ค. ํ์ดํ๊ณผ ๋ณธ๋ฌธ ๋ด์ฉ์ ๊ฐ๊ฐ contentTitle, summaryText ํ๋ผ๋ฏธํฐ๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
BigPictureStyleInformation(
FilePathAndroidBitmap(filePath),
largeIcon: FilePathAndroidBitmap(iconFilePath),
summaryText: "Expand Summary Text",
contentTitle: "Expand Content Title",
),
ํธ์ ์๋ฆผ์ ํผ์น๋ฉด ํ์ดํ๊ณผ ๋ด์ฉ์ด ๋ณํ๋ค. ์์ฝ๊ฒ๋ IOS๋ ํด๋น ํ ํ๋ฆฟ์ ํธ์ ์๋ฆผ์ ์์ด์ Android์์๋ง ์ฌ์ฉํ๋ฉด ๋๋ค.
์ด๋ฒ์๋ ์๋ฆผ ์นดํ ๊ณ ๋ฆฌ ๊ธฐ๋ฅ์ ๋ํด์ ์์๋ณด๋๋ก ํ์.
์นดํ ๊ณ ๋ฆฌ ๊ธฐ๋ฅ์ ๋ํด์ ๋ชจ๋ฅด์๋ ๋ถ๋ค์ด ์์ ์ ์์ด ์ด๋ค ๊ธฐ๋ฅ์ธ์ง์ ๋ํด ๊ฐ๋จํ๊ฒ๋ง ์ดํด๋ณด๋ฉด, ์๋ ์ด๋ฏธ์ง์ ๊ฐ์ด ์ฑ์ ์คํ์ํค์ง ์๊ณ ๋ ํธ์ ์๋ฆผ์ ๋ํ ์๋ต์ ๋ฐ์ ์ ์๋ ๊ธฐ๋ฅ์ด๋ค.
์ฌ์ค ์ด ๊ธฐ๋ฅ์ ์์๋ ์ ์ฌ์ฉํ์ง ์๋ ๊ธฐ๋ฅ์ผ๋ก ์๊ณ ์๋ค. ๋ช ๋ ์ ๊น์ง๋ง ํด๋ ํธ์ ์๋ฆผ์ ์ฌ์ฉ์๋ค์๊ฒ ์ํ๋ ์ก์ ์ ์ป๊ธฐ ์ํด ์ ์กํ๋ ๊ฒฝ์ฐ๊ฐ ์์์ง๋ง, ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์์ ์ ์งํํด์ผ ํ๋ค๋ ๋ถ๋ถ๊ณผ ์ก์ ์ ์ฒ๋ฆฌํ๋๋ฐ์ ์ด๋ ค์์ด ์์ด ํ์ฌ๋ ์๋ฆผ์ ๋ํ ์์ ์ฒ๋ฆฌ ๊ธฐ๋ฅ์ ํธ์ ํ์ ์ ์ฌ์ฉํ์ง ์๋๋ค.
์ด๋ค ๊ธฐ๋ฅ์ธ์ง์ ๋ํด์๋ ์๊ณ ์์ผ๋ฉด ๋์ ๊ฒ ์๊ธฐ ๋๋ฌธ์ ์์๋ณด์.
์ด ๊ธฐ๋ฅ ์ญ์ IOS, Android ํ๋ซํผ ๋ณ๋ก ์ค์ ๊ณผ ์ฌ์ฉ ๋ฐฉ๋ฒ์ด ๋ค๋ฅด๋ค.
๋จผ์ Android๋ ์ถ๊ฐ์ ์ธ ์ค์ ์ ํด์ฃผ์ง ์์๋ ๋๊ณ IOS๋ง ์ถ๊ฐ์ ์ธ ๊ธฐ๋ฅ์ ํ์ฑํ ํด์ฃผ๋ฉด ๋๋ค.
AppDelegate
ios ํด๋์ AppDelegate.swift ํ์ผ๋ก ๊ฐ์ ์ฃผ์ ์๋์ ์ฝ๋๋ฅผ ์ถ๊ฐํด์ฃผ๋ฉด ๋๋ค.
// Add
import flutter_local_notifications
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
// Add
FlutterLocalNotificationsPlugin.setPluginRegistrantCallback { (registry) in
GeneratedPluginRegistrant.register(with: registry)
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
์ถ๊ฐ๋ฅผ ํด์ฃผ์์ผ๋ฉด, Local Push๋ฅผ ์ด๊ธฐํ ํ๋ ๋ถ๋ถ์ ์ฌ์ฉํ ์ก์ ์ ๋ํด์ ์ค์ ์ ํด์ฃผ๋ฉด ๋๋ค.
notificationCategories ํ๋ผ๋ฏธํฐ์ ์ฌ์ฉํ ์นดํ
๊ณ ๋ฆฌ๋ฅผ ๋ฑ๋กํด ์ฃผ๋ฉด ๋๋ค.
์ฌ๋ฌ ์ข
๋ฅ์ ํธ์์ ์ฌ์ฉ๋ ์นดํ
๊ณ ๋ฆฌ๋ ๋ชจ๋ ์ฌ๊ธฐ์ ์ถ๊ฐ๋ฅผ ํด์ฃผ์ด์ผ ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค.
DarwinInitializationSettings(
requestSoundPermission: false,
requestBadgePermission: false,
requestAlertPermission: false,
notificationCategories: [
...
],
),
์ ๋ ํ ์คํธ๋ก ํ ๊ฐ์ ์นดํ ๊ณ ๋ฆฌ ๊ทธ๋ฃน์ ์ถ๊ฐํด ์ฃผ์๋ค.
๋จผ์ DarwinNotificationCategory๋ ํ์๋ก identifier๋ฅผ ์ ๊ณต ๋ฐ์์ผ ํ๋๋ฐ, ํธ์๋ฅผ ์ ์ก์ ๊ตฌ๋ถ ์๋ณ์๋ก ์ฌ์ฉํ ๊ฐ์ ์ ๊ณตํด์ฃผ๋ฉด ๋๋ค.
action์๋ ์ฌ์ฉํ Action์ ๋ฃ์ ์ ์๋ค.
์ก์
ํ์
์ text, plain ํ์
์ด ์๊ณ , text๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉ์๊ฐ ํค๋ณด๋๋ฅผ ํตํด์ ์
๋ ฅํ ์ ์๋๋ก ํด์ฃผ๊ณ , plain์ ์ก์
์ด๋ฒคํธ๋ฅผ ๋ฐ๊ฒ๋ ๋ฒํผ ํ์
์ด๋ผ๊ณ ๋ณด๋ฉด ๋๋ค.
๊ฐ ํ์
์๋ ๊ณ ์ ํ ์๋ณ์๋ฅผ ์ฌ์ฉํด์ผ ํ๋ฉฐ, text ํ์
์ ๋ฒํผ๋ช
๊ณผ Placeholder๋ฅผ ์ง์ ํ ์ ์๋๋ก ๋์ด์๋ค.
options๋ฅผ ๋ณด๋ฉด DarwinNotificationCategoryOption ํ์ ์ ์ง์ ํ ์ ์๋๋ก ๋์ด์๋๋ฐ, ์ฌ์ฉ์๊ฐ ์ฑ์ ์๋ฆผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ๋นํ์ฑํํ ๊ฒฝ์ฐ์๋ ์ ๋ชฉ์ ๋ ธ์ถ ์ํค๊ณ ์ถ๋ค๋ฉด hiddenPreviewShowTitle, ๋ถ์ ๋ชฉ์ ๋ ธ์ถ ์ํค๊ณ ์ถ๋ค๋ฉด hiddenPreviewShowSubtitle ์ ์ง์ ํด ์ฃผ๋ผ๊ณ ํ๋ค.
notificationCategories: [
DarwinNotificationCategory(
"categoryId",
actions: <DarwinNotificationAction>[
DarwinNotificationAction.text("categoryInput", "Input Text",
buttonTitle: "SEND", placeholder: "Input your text..."),
DarwinNotificationAction.plain("categoryAccept", "Accept"),
DarwinNotificationAction.plain("categoryDecline", "Decline"),
],
options: <DarwinNotificationCategoryOption>{
DarwinNotificationCategoryOption.hiddenPreviewShowTitle,
},
),
],
์ฌ์ฉํ ์นดํ ๊ณ ๋ฆฌ ๋ฑ๋ก์ ์๋ฃ ๋์๋ค. ์ฌ์ฉ์ ํ๋ ค๋ฉด ๋ก์ปฌ ํธ์ ์ ์ก์ ์ฌ์ฉํ๋ details์ ์ถ๊ฐ๋ง ํด์ฃผ๋ฉด ๋๋ค.
categoryId๋ฅผ ์๋ณ์๋ก ๋ฑ๋ก ํ๊ธฐ์, ํด๋น ๊ฐ์ categoryIdentifier์ ๋ฃ์ด์ฃผ๋ฉด ๋์ด๋ค !
DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
badgeNumber: 1,
attachments: attachments,
categoryIdentifier: "categoryId",
),
Android๋ ์ฌ์ฉํ๊ณ ์ ํ๋ ์นดํ ๊ณ ๋ฆฌ๋ฅผ details์ ์ง์ ๊ตฌ์ฑํด์ฃผ๊ธฐ๋ง ํ๋ฉด ๋๋ค.
Android๋ id๋ฅผ ์ง์ ํด์ฃผ๊ณ , ์ก์ ์ ์ฌ์ฉํ ํ์ดํ์ ์ง์ ํด ์ฃผ๋ฉด๋๋ค.
AndroidNotificationDetails(
channelId,
channelName,
channelDescription: channelDescription,
importance: Importance.max,
priority: Priority.high,
actions: const <AndroidNotificationAction>[
AndroidNotificationAction(
"categoryAccept",
"Accept",
showsUserInterface: true,
),
AndroidNotificationAction(
"categoryDecline",
"Decline",
showsUserInterface: true,
inputs: [
AndroidNotificationActionInput(
label: "Input your text",
)
],
),
],
),
์นดํ ๊ณ ๋ฆฌ ์ก์ ์ด๋ฒคํธ๋ฅผ ์์ ๋ฐ๊ธฐ ์ํด์๋ ์ต์์ ๋๋ ์ ์ ๋ฉ์๋๋ก ๊ตฌ์ฑํด์ผ ํ๋ค๊ณ ํ๋ค. ์ฑ์ด ์คํ๋์ง ์์ ๊ฒฝ์ฐ ํด๋น ์ด๋ฒคํธ๋ฅผ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ฒ๋ฆฌํด์ผ ํ๊ธฐ ๋๊ธฐ์ ์ด ๋ถ๋ถ์ ์๊ฐํด์ ๋ด๋ถ ์ด๋ฒคํธ๋ฅผ ๊ตฌ์ฑํ์๋ฉด ๋๋ค.
('vm:entry-point')
void notificationTapBackground(NotificationResponse notificationResponse) {
// handle action
print(notificationResponse.actionId);
print(notificationResponse.id);
print(notificationResponse.input);
print(notificationResponse.payload);
}
Local Push ์ด๊ธฐํ์์ onDidReceiveBackgroundNotificationResponse์์ ์์ ์ ๋ฐ์ผ๋ฉด ๋๋ค.
await local.initialize(
settings,
onDidReceiveNotificationResponse: (NotificationResponse details) {
...
},
onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
);
์นดํ ๊ณ ๋ฆฌ ์์ ์ ๊ธฐ๋ณธ์ ์ธ ๊ตฌ์ฑ ๋ฐฉ๋ฒ๊ณผ ์ฌ์ฉ๋ฒ์ ๋ํด์๋ง ๊ฐ๋ณ๊ฒ ๋ค๋ค์ผ๋, ์์ ๋ด์ฉ์ ์ฐธ๊ณ ํ์ฌ ์ํ์๋ ๊ธฐ๋ฅ์ ๋ง๋์๋ฉด ๋๋ค.
IOS, Android ๋ ๋ค ํธ์์ SubTitle์ ์ถ๊ฐํ ์ ์๊ณ , details ์ต์ ์ ์ถ๊ฐํ์ฌ ์ฌ์ฉํ ์ ์๋ค.
IOS
DarwinNotificationDetails(
...
subtitle: "SubTitle",
),
Android
AndroidNotificationDetails(
...
subText: "SubTitle",
),
์ด๋ฒ์๋ Android์์๋ง ์ง์ํ๋ ๊ธฐ๋ฅ์ธ ์งํ๋ฅ ํ์๊ธฐ์ด๋ค.
์์ฝ๊ฒ๋ IOS์์๋ ์ง์ํ์ง ์๋ ๊ธฐ๋ฅ์ด๋, ์๋๋ก์ด๋ ์ ์ฉ์ผ๋ก ์ฌ์ฉํ์๋ฉด ๋๋ค.
์๋๋ก์ด๋ ์ฌ์ฉ์๋ ์ต์ํ ๊ธฐ๋ฅ์ผ ๊ฒ์ด๋ค. ์๋น์ค์์ ๋ฌด์ธ๊ฐ๋ฅผ ๋ค์ด๋ก๋ ๋ฐ์ ๋ ์ฃผ๋ก ํ์๋๋ Progress bar์ด๋ค.
์ด ๊ธฐ๋ฅ์ ์๊ฐํ๊ธฐ ์์, ํ ๊ฐ์ง ํ์คํ ์์๋ฌ์ผ ํ ์ ์ ์ด๋ฏธ ์ ์ก๋ ํธ์์ ๋ด์ฉ์ ๋ณ๊ฒฝํ์ง ๋ชปํ๋ค๋ ๊ฒ์ด๋ค. Remote, Local ํธ์ ๋ชจ๋ ํ ๋ฒ ์ ์ก๋๋ฉด ๋ด์ฉ์ ์์ ํ ์๊ฐ ์๋ค.
์ ๊ทธ๋ ๋ค๋ฉด ์ด๋ป๊ฒ ์งํ๋ฅ ์ด ๋จ๊ณ์ ์ผ๋ก ํ์๋๋ ๊ฒ์ผ๊น ?
๋ฐฉ๋ฒ์ ๊ฐ๋จํ๋ฐ, ํธ์๋ฅผ ๊ณ์ ๋ณด๋ด๋ ๊ฒ์ด๋ค. ํธ์๋ฅผ ์ ์ก์ id ๊ฐ์ ์ง์ ํ์ฌ ํธ์๋ฅผ ์ ์กํ๊ฒ ๋๋๋ฐ, ๋์ผ id ๊ฐ์ผ๋ก ํธ์๋ฅผ ์ฌ๋ฌ๋ฒ ๋ณด๋ด๊ฒ ๋๋ฉด ์๋ฆผ ๋ฆฌ์คํธ์๋ ํ ๊ฐ์ ํธ์๋ง ๋ณด์ด๊ฒ ๋๋ฏ๋ก ์ด๋ฌํ ๊ธฐ๋ฅ์ ๊ตฌํํ ์๊ฐ ์๋ค.
๋ค์ด๋ก๋๋ฅผ ๋ฐ๋๋ฐ, ์ฒ์ 10% ๋ฐ์์ ๋์ ํธ์๋ฅผ ๋ณด๋ด๊ณ , ๋ค์์ผ๋ก 25%๊ฐ ๋ค์ด๋ก๋ ๋ฌ๋ค๋ฉด ๊ทธ ๋ ๋์ผ id๋ฅผ ์ฌ์ฉํด ๋ ํธ์๋ฅผ ์ ์กํ๋ ๊ฒ์ด๋ค.
AndroidNotificationDetails์ maxProgress, progress, showProgress, silent ๊ฐ์ ํ์ฉํ๋ฉด ์ด ๊ธฐ๋ฅ์ ๋ง๋ค ์ ์๋ค.
maxProgress : ์ ์ฒด ์งํ๋ฅ
progress : ํ์ฌ ์งํ๋ฅ
showProgress : ์งํ๋ฅ ํ์๊ธฐ๋ฅผ ๋ ธ์ถํ ์ง ์ฌ๋ถ
silent : ์๋ฆผ์ ๋ ธ์ถ์ํค์ง ์๊ณ ๋ด์ญ์๋ง ๋ ธ์ถ์ํค๊ณ ์ถ์ ๊ฒฝ์ฐ ์ฌ์ฉ
maxProgress๋ฅผ 100์ผ๋ก ์ง์ ํ๊ณ , progress๋ฅผ 15๋ก ์ง์ ํ๋ฉด 15%์ ์งํ๋ฅ ํ์๊ธฐ๊ฐ ๋ ธ์ถ๋๋ค.
showProgress๋ฅผ true๋ก ํ์ง ์์ผ๋ฉด ๋ ธ์ถ๋์ง ์์ผ๋, ๋ฐ๋์ true๋ก ์ง์ ํด์ผ ๋ ธ์ถ์ด ๋๋ค.
silent๋ ์๋ฆผ์ ๋ฐฐ๋๋ก ๋
ธ์ถํ์ง ์๊ณ , ๋ด์ญ์์๋ง ๋
ธ์ถ๋๋๋ก ํ๋ ๊ธฐ๋ฅ์ธ๋ฐ, silent ๊ฐ์ ์ฌ์ฉํ์ง ์์ผ๋ฉด ํธ์๊ฐ ๋ฐฐ๋์ ๋
ธ์ถ๋๋ฉด์, ์ง๋์ด ํ์ฑํ ๋๊ฒ ๋๋ค. ์ด๋ ๊ฒ ๋๋ฉด, ๊ณ์ ์ง๋์ด ๋ฐ์ํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ์ฌ์ฉ์ ํผ๋ก๋๊ฐ ๋์์ง๊ฒ ๋๋ฏ๋ก silent ๊ฐ์ true๋ก ์ง์ ํ์ฌ ์งํ๋ฅ ํ์๊ธฐ๋ฅผ ๋ด์ญ์์๋ง ๋
ธ์ถ๋ ์ ์๋๋ก ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค.
๋ฌผ๋ก , ๊ฐ๋ฐ์๋ถ๋ค์ด ๊ฐ๋ฐํ๊ธฐ ๋๋ฆ์ด๋ silent๋ฅผ false๋ก ๋ณด๋ด๋ ๋ฌธ์ ๋ ์๋ค.
์งํ๋ฅ ํ์๊ธฐ๋ details์์ ์ค์ ํ ์ ์๋ค.
AndroidNotificationDetails(
maxProgress: 100,
progress: 15,
showProgress: true,
silent: true,
),
Flutter์์ ํ์ฌ ๋ค์ด๋ก๋ ์งํ ์ํฉ์ ๋ง๋ค์ด์ฃผ๋ ํจํค์ง๋ ์ฌ๋ฌ ์์ผ๋, ํด๋น ๊ธฐ๋ฅ๊ณผ ๊ฒฐํฉํ์ฌ ์ฌ์ฉํ์๋ฉด ๋๋ค.
์ ๋ ๊ฐ๋จํ๊ฒ 1์ด์ 10%์ฉ ๋ค์ด๋ก๋ ๋ฐ๋ ์ฝ๋๋ฅผ ๊ตฌํํด ๋ดค๋ค. ์ฝ๋๋ฅผ ๋ณด์๋ฉด ์ฝ๊ฒ ์ดํดํ ์ ์์ ๊ฒ์ด๋ค.
for (int i = 0; i < 10; i++) {
await Future.delayed(const Duration(seconds: 1), () async {
await _local.show(
1,
"[PROGRESS] [${(i + 1) * 10}/100]",
"[TEST] flutter_local_notifications packages with Local Push",
NotificationDetails(
iOS: ios,
android: AndroidNotificationDetails(
"progressId",
"progressName",
importance: Importance.max,
priority: Priority.high,
maxProgress: 100,
progress: (i+1)*10,
showProgress: true,
silent: true,
),
);
);
});
}
์ด๋ฒ์๋ ์๋๋ก์ด๋ ์ ์ฉ์ธ ์ง๋ ํจํด์ ๋ํด์ ์ดํด๋ณด๋๋ก ํ๊ฒ ๋ค.
์๋๋ก์ด๋์ ํธ์ ์ ์ก์ ์ง๋ ํจํด์ ๋ณ๊ฒฝํ ์ ์๋๋ฐ, IOS๋ ์ง๋ ํจํด์ ๋ณ๊ฒฝํ ์ ์๋ค.
์ง๋ ํจํด์ AndroidNotificationDetails ์ต์ ์ vibrationPattern์ ์ฌ์ฉํด์ Int64List๋ก ํจํด์ ๋ง๋ค ์ ์๋ค.
AndroidNotificationDetails(
...
vibrationPattern: Int64List.fromList([0, 1500, 500, 2000, 500, 1500]),
),
Int64List.fromList([0, 3000, 1000, 3000]);
Int64List.fromList([0, 1000, 2000, 3000, 500, 1000]);
์ง๋ ํจํด์ ์ต์ด ์์ฑ๋ channelId ๊ฐ์ ๊ธฐ์ค์ผ๋ก ํ๊ธฐ ๋๋ฌธ์, ์ฑ์ ์ฌ์ค์น ํ๋ฉด์ ํ ์คํธ๋ฅผ ์งํํ์๋ฉด ๋๋ค. ๋ง์ผ ๊ธฐ์กด channelId๋ฅผ ์ฌ์ฉํ๋ค๋ฉด, ์ฑ๋์ด ์์ฑ๋ ๋์ ์ง๋ ํจํด์ ๋ฐ๋ผ๊ฐ๊ธฐ ๋๋ฌธ์ ๋ฌด์กฐ๊ฑด ์ด๊ธฐ ์คํ์ ํจํด์ ๋ง๋ค์ด์ผ ํ๋ฉฐ, ๊ฐ์ channelId๋ก๋ ํจํด์ ๋ณ๊ฒฝํ ์ ์๋ค.
์ง๋ ํจํด์ ์ด์ด์ ์ฌ์ด๋๋ฅผ ๋ณ๊ฒฝํด๋ณด์.
์ฌ์ด๋ ๋ณ๊ฒฝ์ ์ง๋ ํจํด๊ณผ๋ ๋ค๋ฅด๊ฒ IOS์์๋ ์ฌ์ฉ ๊ฐ๋ฅํ ๊ธฐ๋ฅ์ด๋ค.
์ฌ์ด๋ ๋ณ๊ฒฝ์ ์ํด์ ์ค๋์ค ํ์ผ์ด ํ์ํ๋ฐ, ์ฌ์ฉํ ์ค๋์ค ํ์ผ์ flutter_local_notifications ํจํค์ง ์์ ์ฑ์ ์๋ ํ์ผ๋ก ์ฌ์ฉ์ ํด๋ณด๋๋ก ํ์.
MP3ํ์ผ์ ์๋ ๋งํฌ์ Git ์ ์ฅ์์์ ๋ฐ์ ์ ์๋ค.
์๋๋ก์ด๋๋ ๋จผ์ resource๋ฅผ ์์ฑํด ์ฃผ์ด์ผ ํ๋ค.
android > app > src > main > res
ํด๋น ๊ฒฝ๋ก๋ก ์ด๋ํ์ฌ raw ํด๋๋ฅผ ์์ฑํด์ฃผ๊ณ , rawํด๋์ ๋ค์ด๋ฐ์ mp3 ํ์ผ์ ๋ฃ์ด์ฃผ์.
์ฌ์ฉ ๋ฐฉ๋ฒ์ ๊ฐ๋จํ๋ค. ์ฃผ์ํ ์ ์ ์์์ ์ง๋ ํจํด์ ์์ฑํ ๊ฒ๊ณผ ๊ฐ์ด channelId๋ก ์ด๋ฏธ ์์ฑ๋ ๋ก์ปฌ ํธ์๊ฐ ์๋ค๋ฉด ์๋ก์ด ์ค๋์ค ํ์ผ๋ก ์ฌ์ด๋ ์ฌ์์ด ์๋๊ธฐ ๋๋ฌธ์, ์๋ก์ด channelId๋ฅผ ์์ฑํ๊ฑฐ๋ ์๋๋ฉด ์ฑ์ ์ง์ ๋ค๊ฐ ๋ค์ ๋น๋ํ๋ฉด ๋๋ค.
AndroidNotificationDetails ์ต์ ์ค sound์ ๋ฆฌ์์ค ํ์ผ ๊ฒฝ๋ก๋ฅผ ์ฐพ์ ์ ์๋๋ก ์ฝ๋๋ฅผ ์ถ๊ฐํด์ฃผ์.
๋ง์ผ ์ฌ์์ด ๋์ง ์๋๋ค๋ฉด, playSound ๊ฐ์ด true๋ก ๋์ด ์๋์ง๋ ํ์ธํด๋ณด์.
AndroidNotificationDetails(
...
sound: const RawResourceAndroidNotificationSound("slow_spring_board"),
),
์ด์ด์ IOS์์๋ ์ฌ์ด๋๋ฅผ ๋ณ๊ฒฝํด๋ณด์.
IOS๋ mp3ํ์ผ์ด ์๋ aiff ํ์ผ์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ IOS์ ์ฌ์ฉํ ์ค๋์ค ํ์ผ๋ ์๋ ๋งํฌ์์ ๋ค์ด๋ก๋ ํด์ฃผ์.
๋จผ์ XCode๋ฅผ ์ด์ด์ฃผ์.
Runner ํ์์ ๋ค์ด๋ฐ์ ์ค๋์ค ํ์ผ์ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค.
DarwinNotificationDetails ์ต์ ์ sound์ ํ์ผ ์ด๋ฆ์ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค. ๋์ด๋ค.
DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
sound: "slow_spring_board.aiff",
),
์ง๊ธ๊น์ง flutter_local_notifications ํจํค์ง๋ฅผ ์ฌ์ฉํ ๋ก์ปฌ ํธ์ ๊ธฐ๋ฅ์ ๋ํด์ ์ค์ ๋ถํฐ ์ฌ์ฉ ๋ฐฉ๋ฒ๊น์ง ์์๋ณด์๋ค.
flutter_local_notifications ํจํค์ง์๋ ์ด ์ธ์๋ ๋ง์ ๊ธฐ๋ฅ๋ค์ด ์ ๊ณต๋๊ณ ์์ผ๋, ํ ๋ฒ์ฉ ์ฌ์ฉํด ๋ณด์๋ฉด์ ์์ธํ ์ดํด๋ณด์๊ธธ ๋ฐ๋๋ค.
์ด๋ฒ ๊ธ์ ๋ด์ฉ์ด ๋๋ฌด ๊ธธ์ด์ ธ, ์ฃผ์ํ ๊ธฐ๋ฅ๋ง ์์ฑํ๊ฒ ๋์์ผ๋ ์ถ๊ฐ์ ์ผ๋ก ๊ถ๊ธํ์ ๋ถ๋ถ์ ์์ฒญ ์ฃผ์๋ฉด ์ด ๊ธ์ ์ถ๊ฐํ๊ฑฐ๋ ์๋ก์ด ๊ธ๋ก ์์ฑํด ๋ณด๋๋ก ํ๊ฒ ๋ค
์ฐธ๊ณ ๋ก ์๋ฆผ ๊ธฐ๋ฅ์ PlugIn ํจํค์ง์ด๋ค ๋ณด๋, ์ค์ ๋ฐ ๋ฒ์ ์ ๋ฐ๋ผ ์๋์ด ์๋์ค ์๋ ์์ผ๋, ์๋ฆผ์ ์์ฑํ๊ณ ์ค์ ์๋ฆผ์ด ์ค๋์ง ๊ผญ ํ ์คํธ ํด๋ณด์๋ฉด์ ๊ฐ๋ฐ ํ์๊ธธ ๋ฐ๋๋ค.
์ง๊ธ๊น์ง ๋ด์ฉ ์ค ๊ถ๊ธํ์ ๋ด์ฉ์ ๋๊ธ ๋จ๊ฒจ์ฃผ์๋ฉด ์ต๋ํ ๋น ๋ฅธ ๋ต๋ณ ๋๋ฆฌ๋๋ก ํ๊ฒ ์ต๋๋ค !!
์ด๋ฒ์ flutter_local_notifications ํจํค์ง์ ๋ํด์ ๋ณด๋ค ์์ธํ ๊ธ์ ์์ฑํ๊ฒ ๋๋ฉด์, ๋์ค์๋ ํ์ํ ๊ธฐ๋ฅ์ด ์์ผ๋ฉด ๊ทธ๋๋ง๋ค ํ๋ก์ ํธ๋ฅผ ์์ฑํ๊ธฐ์ ์ค์ ํด์ผ ํ๋ ๋ถ๋ถ์ด ๋ง์์ ํ ์คํธ์ฉ ์ฑ์ ํ๋ ๋ง๋ค์ด ๋ดค๋ค.
๊ฐ๋จํ๊ฒ ๊ธฐ๋ฅ์ ์ฌ์ฉํด๋ณด๊ณ ์ถ์ผ์ ๋ถ๋ค์ Git Repository ๋ค์ด๊ฐ์ ์ ํ๋ก์ ํธ ์คํํด ๋ณด์๋ฉด ๋์์ด ๋ ๊ฒ๋๋ค.
์๋ ํ์ธ์. ๋ธ๋ก๊ทธ ์ฐธ๊ณ ํ๋ฉฐ flutter์ fcm ๋ฐ local notification์ ํ์ฉํ์ฌ push ์๋ฆผ์ ๊ตฌํํ์์ต๋๋ค. ๋๋ฐ์ด์ค๊ฐ ์ ๊ธ ์ํ์ผ ๋, ์ฆ ํ๋ฉด์ด ๊บผ์ ธ ์๋ ์ํ์์ ์๋ฆผ์ด ๋์ฐฉํ๋ ๊ฒฝ์ฐ์ ์๋ฆฌ๋ง ๋ค๋ ค์ ํ๋ฉด์ ๊ฐ์ด ๊นจ์ฐ๋ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ณ ์ถ์๋ฐ, ์ด์ ๊ด๋ จํ์ฌ ๋์์ ๋ฐ์ ์ ์์๊น์? terminate ์ํ์ผ ๋ ์๋ฆผ์ด ์๋ค๋ ๊ฒ์ ๋๋ฐ์ด์ค ํ๋ฉด์ ๊นจ์๋๊ณ ๋ณด๊ณ ์์ง ์๋ ์ด์ ํ์ธํ๊ธฐ ์ด๋ ค์ด ์ฌ์ฉ์ ๊ฒฝํ์ด๋ผ ์๊ฐ๋์ด์์.
์์ธํ ์ค๋ช
๋๋ฌด ๋๋ฌด ๊ฐ์ฌํฉ๋๋ค!!!!
ํน์ ์ถ๊ฐ์ ์ธ ๋ช ๊ฐ์ง ์์ธํ ์ง๋ฌธ์ด ์๋๋ฐ ๋ฉ์ผ์ด๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ผ๋ก ์ฌ์ญค๋ณผ ๋ฐฉ๋ฒ์ด ์์๊น์?
์๋ ํ์ธ์. ์ง๋ฌธ์ด ์์ต๋๋ค. ์ฑ์ด ์ข ๋ฃ๋ ์ํ์์๋ Local Notifications ๋ฅผ ์ด์ฉํด์ ํธ์ฌ๋ฅผ ๋ฐ์กํ ์ ์์๊น์? ์ฃผ๊ธฐ์ ์ธ ํธ์ฌ ๋ฐ์ก๋ ๊ฐ๋ฅํ ๊น์?
์ฑ ๋ฐ์ฒ ์์ด์ฝ์ ์๋์์ด ๋ฑ์ง์ ์ซ์๋ง๋ง ๋๋ฆด ์๋ ์๋์?
๊ธฐ์กด์ ์๋ flutter_app_badger ํจํค์ง๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋๋ฐ local nofitication์ผ๋ก๋ ๋์ผํ ๊ธฐ๋ฅ์ด ๊ตฌํ์ด ๊ฐ๋ฅํ์ง ๊ถ๊ธํฉ๋๋ค
flutter_app_badger๋ Discontinued๋ผ๊ณ ๋์์ ์ฌ์ฉํ๋ฉด ์๋ ๊ฒ ๊ฐ์์์
์๋
ํ์ธ์! ๋ง์ ๋์์ด ๋๊ณ ์์ต๋๋ค.!
์ง๋ฌธ ์ฌํญ์ด ์๋๋ฐ ์ ๊ฐ ๊ฐค๋ญ์ ๋
ธํธ10์ผ๋ก ํ
์คํธ๋ฅผ ํด๋ณด๊ณ ์๋๋ฐ AndroidManifest.xml์ ์ด๋ค ์ค์ ๋ ํ์ง ์๊ณ
permission_handler: ^11.3.1
flutter_local_notifications: ^17.2.3
๋ฒ์ ์ ์ค์นํ๊ณ ๋ฐ๋ก ํ ์คํธ๋ฅผ ์งํํด ๋ดค๋๋ฐ(VMD๋ก api33 ๋ฒ์ ์ผ๋ก ํ ์คํธํด๋ด๋) fore, back, terminate ์ํ ๋ชจ๋ local notification์ด ์ ๋ถ ๋์ํ์์ต๋๋ค.
AndroidManifest.xml์ ์ค์ ์ ํด์ฃผ๋ ์ด์ ๊ฐ ํ์ ๋ฒ์ ์ android์์๋ ๋์ํ๊ธฐ ์ํจ์ธ์ง ๊ถ๊ธํฉ๋๋ค.!
์๋ ํ์ธ์ ๊ตฌ๊ธ๋ง ํ๋ค๋ณด๋ฉด ์์ฑ์๋ ๊ธ์ ์์ฃผ ์ฐธ๊ณ ํ๊ฒ ๋๋๋ฐ ํญ์ ๊ฐ์ฌํ๊ฒ ์๊ฐํ๋ฉฐ ์ ๋ณด๊ณ ์์ต๋๋ค.
ios - fore, bg, terminate | aos - fore ๋ค๋ฅธ๊ฑด ๋ค ์ ๋๋๋ฐ์...
์๋๋ก์ด๋์์ bg, terminate์ heads up notification์์ด ์ํ๋ฐ์ ๋ง ํ์๊ฐ ๋๋๋ฐ ํน์ ๋ญ๊ฐ ๋ฌธ์ ์ง ์์ค๊น์..?
์๋ ํ์ธ์. ๊ธ ์ ๋ณด์์ต๋๋ค. ๊ฐ์ฌํฉ๋๋ค.
์ ๋ ํ์ฌ flutter ์์ ๊ฒ์๊ธ ๋๊ธ์ ์๋ฆผ์ ์ํด local_notification์ ์ฌ์ฉํ๋ ค๊ณ ํฉ๋๋ค.
์๊ธฐ์์ ํํ ํน์ ํ ์ด๋ฒคํธ(์๋ก, ๋ฒํผํด๋ฆญ)๊ฐ ๋ฐ์ํ์์๋๋ ๊ตฌํ์ด ์ ๋์์ต๋๋ค. ๊ฐ์ฌํฉ๋๋ค.
ํ์ง๋ง ํน์ ์ฌ์ฉ์ํํ ๋ณด๋ด๋๊ฒ์ด ์ดํด๊ฐ ๋์ง ์์ ๊ธ์ ๋จ๊ฒผ์ต๋๋ค.
์ฌ์ฉ์A๊ฐ ์ฌ์ฉ์B์ ๊ฒ์๊ธ์ ๋๊ธ์ ๋ฌ์๋ค๋ฉด, ์ฌ์ฉ์Bํํ ์๋ฆผ์ ๋ณด๋ผ์๋ ์๋๊ฑด๊ฐ์?