๐Ÿ”” [Flutter] Local Notifications ์‚ฌ์šฉํ•ด ๋ณด๊ธฐ

Tygerยท2024๋…„ 2์›” 3์ผ
5

Flutter

๋ชฉ๋ก ๋ณด๊ธฐ
51/57
post-thumbnail

๐Ÿ”” Local Notifications ์‚ฌ์šฉํ•ด ๋ณด๊ธฐ

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์— ๋Œ€ํ•œ ๋‚ด์šฉ๋„ ์ถ”ํ›„ ์‹œ๊ฐ„๋  ๋•Œ์— ๋‹ค๋ค„๋ณผ ์˜ˆ์ •์ด๋‹ค.

Push Type

์•ฑ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์•Œ๋ฆผ ์ข…๋ฅ˜๋Š” ํฌ๊ฒŒ Remote, Local ์ด๋ ‡๊ฒŒ 2๊ฐ€์ง€ ์ •๋„๊ฐ€ ์žˆ๋‹ค.

๊ฐ๊ฐ์˜ ์ฐจ์ด์ ์€ ๋ฌด์—‡์ผ๊นŒ ?

๋Œ€ํ‘œ์ ์ธ ์ฐจ์ด์ ์ด๋ผ๋ฉด ๋ฐœ์†ก ์ฃผ์ฒด๊ฐ€ ๋‹ค๋ฅด๋‹ค๋ผ๋Š” ์ ์ด ์žˆ๊ณ , ๋˜ ํ•˜๋‚˜๋Š” ๋ช…์นญ์—์„œ๋„ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด ๋ฐœ์†ก์„ ๋ณด๋‚ด๋Š” ๋ฐฉ์‹์—๋„ ์ฐจ์ด๊ฐ€ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

์‚ฌ์šฉ ๋ฐฉ๋ฒ•์ด๋‚˜ ์„ค์ • ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ ์ฐจ์ด๊ฐ€ ์—†๋‹ค.

Remote Notifications

Remote Push๋ž€ ?

๋จผ์ € 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 Notifications

๊ทธ ๋‹ค์Œ์œผ๋กœ ์ด๋ฒˆ ๊ธ€์—์„œ ๋‹ค๋ฃจ๊ฒŒ ๋  ๋‚ด์šฉ์ธ 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

์ด์ œ ๋ณธ๊ฒฉ์ ์ธ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์— ์•ž์„œ ๋ชจ๋“  ์•Œ๋ฆผ ์ข…๋ฅ˜๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ์— ๊ณตํ†ต์ ์œผ๋กœ ์„ค์ •์ด ๋˜๋Š” ๋ถ€๋ถ„์ด ์žˆ๋Š”๋ฐ, ๋ฐ”๋กœ ๊ถŒํ•œ์„ ์š”์ฒญ ๋ฐ›๋Š” ๋ฌธ์ œ์ด๋‹ค.

Permission์€ ์•ฑ์—์„œ ํ•„์ˆ˜์ ์œผ๋กœ ์ดํ•ดํ•˜๊ณ  ๊ฐœ๋ฐœํ•ด์•ผ ํ•˜๋Š” ๋ถ€๋ถ„์œผ๋กœ, ๊ถŒํ•œ์ด ์—†๋Š” ์‚ฌ์šฉ์ž๋Š” ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๊ธฐ์—, ์ ์ ˆํ•œ ์‹œ์ ์— ๊ถŒํ•œ ์š”์ฒญ์€ ํ•„์ˆ˜์ ์ด๋‹ค.

์—ฌ๊ธฐ์„œ๋Š” Notifications์™€ ๊ด€๋ จ๋œ ๊ถŒํ•œ์„ ์š”์ฒญ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ๋จผ์ € ์„ค์ •์„ ์ง„ํ–‰ํ•ด ๋ณด์ž.

Permission ์š”์ฒญ์„ ํ•˜๊ณ  ์‹ถ์„ ๋•Œ๋Š”, ๋ณดํ†ต ๊ฐ ํŒจํ‚ค์ง€์—์„œ๋„ ๊ถŒํ•œ์„ ์š”์ฒญ ๋ฐ›์„ ์ˆ˜ ์žˆ์ง€๋งŒ, ์•ฑ์—์„œ ์•Œ๋ฆผ์— ๋Œ€ํ•œ ๊ถŒํ•œ๋งŒ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๊ธฐ์—, ํ•œ ๋ฒˆ์— Permission ๊ด€๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋Š” ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค.

์ฐธ๊ณ ๋กœ Permission ๊ด€๋ จ๋œ ๋‚ด์šฉ์€ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์™€ ์‹ค์ œ ๋””๋ฐ”์ด์Šค, ์„ค์ •๋œ Flutter ํŒŒ์ผ ๋ฐ ๋””๋ฐ”์ด์Šค ๋ฒ„์ „์— ๋”ฐ๋ผ ์ผ๋ถ€ ๋‚ด์šฉ์ด ์กฐ๊ธˆ์”ฉ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์œผ๋‹ˆ, ๊ตฌ๊ธ€๋ง์ด ํ•„์š”ํ•  ์ˆ˜๋„ ์žˆ๊ณ , ํ•ด๊ฒฐ์ด ์•ˆ๋˜์‹œ๋Š” ๋ถ€๋ถ„์€ ๋Œ“๊ธ€ ๋‚จ๊ฒจ ๋†“์œผ์‹œ๋ฉด ์ตœ๋Œ€ํ•œ ๋„์›€ ๋“œ๋ฆฌ๋„๋ก ํ•˜๊ฒ ๋‹ค.

dependencies

permission_handler ํŒจํ‚ค์ง€๋ฅผ ์ถ”๊ฐ€ํ•ด ์ฃผ๋„๋ก ํ•˜์ž.

dependencies:
	permission_handler: ^11.1.0

์•Œ๋ฆผ ํ—ˆ์šฉ์„ ์œ„ํ•ด์„œ ์•ฑ์ด ๊ฐ€์žฅ ์ตœ์ƒ๋‹จ ์œ„์ ฏ์—์„œ ์•Œ๋ฆผ์— ๋Œ€ํ•œ ๊ถŒํ•œ์„ ์š”์ฒญํ•˜๋„๋ก ํ•ด๋ณด์ž.

๊ถŒํ•œ์„ ์š”์ฒญํ–ˆ๋Š”๋ฐ, ์•„๋ฌด ๋ฐ˜์‘์ด ์—†๊ฑฐ๋‚˜ ์•ฑ์— ํฌ๋ž˜์‹œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๊ฒƒ์ด๋‹ค. ํ”Œ๋žซํผ์— ๋งž๊ฒŒ ๊ถŒํ•œ์„ ์š”์ฒญํ•˜๊ธฐ ์œ„ํ•ด ์ถ”๊ฐ€์ ์œผ๋กœ ์„ค์ •์„ ํ•ด์ฃผ์ง€ ์•Š์•„์„œ ๊ทธ๋Ÿฐ ๊ฒƒ์ด๋‹ค.


void initState() {
	super.initState();
    _permissionWithNotification();
}
  
void _permissionWithNotification() async {
	await [Permission.notification].request();
}

IOS

๋จผ์ € 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

์•„๋ž˜ ์ด๋ฏธ์ง€ ์ฐธ๊ณ ํ•˜์‹œ๋ฉด ๋œ๋‹ค.

์ด์ œ ๋‹ค์‹œ ์‹คํ–‰ํ•ด์„œ ๊ถŒํ•œ์„ ์š”์ฒญ ๋ฐ›๋Š”์ง€ ํ™•์ธํ•ด ๋ณด์ž. ์ •์ƒ์ ์œผ๋กœ ์š”์ฒญ์„ ํ•œ๋‹ค.

Podfile

Android

์ด๋ฒˆ์—” Android ์„ค์ •๋„ ์ถ”๊ฐ€ํ•ด์ฃผ์ž.

Android API 33๋ ˆ๋ฒจ์„ ๊ธฐ์ค€์œผ๋กœ ์•Œ๋ฆผ ๊ถŒํ•œ์— ๋Œ€ํ•œ ๋‚ด์šฉ์˜ ๋ณ€ํ™”๊ฐ€ ์žˆ๋‹ค.

33๋ ˆ๋ฒจ ์ด์ „์ธ ๊ฒฝ์šฐ ์•Œ๋ฆผ์„ ๋”ฐ๋กœ ์š”์ฒญ๋ฐ›์„ ํ•„์š” ์—†์ด, ์•Œ๋ฆผ ๊ถŒํ•œ์ด ๋ชจ๋‘ ํ—ˆ์šฉ ์ƒํƒœ์˜€๋Š”๋ฐ, 33๋ ˆ๋ฒจ ์ด์ƒ๋ถ€ํ„ฐ ๊ถŒํ•œ์„ ๋”ฐ๋กœ ์š”์ฒญํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝ์ด ๋˜์—ˆ๋‹ค.

Remote Notifications ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์ถ”๊ฐ€์ ์ธ ์„ค์ •์ด ํ•„์š” ์—†์ง€๋งŒ, Local Notifications๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์—ฌ๋Ÿฌ ๊ถŒํ•œ๋“ค์„ ๋“ฑ๋กํ•ด์•ผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•ด๋‹น ๊ถŒํ•œ์€ ์•„๋ž˜์—์„œ ๋‹ค๋ฃจ๊ธฐ๋กœ ํ•˜๊ฒ ๋‹ค.

Android๋Š” ๋”ฐ๋กœ ์„ค์ • ์—†์ด๋„, ์š”์ฒญ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

Status

์ด์ œ ๊ถŒํ•œ์„ ์š”์ฒญํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ดค๋Š”๋ฐ, 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();
    }
  }

Local Notifications

Permission ๊ถŒํ•œ๋„ ์ •์ƒ์ ์œผ๋กœ ์ง„ํ–‰ ํ–ˆ์œผ๋‹ˆ, ๋ณธ๊ฒฉ์ ์œผ๋กœ flutter_local_notifications ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•œ ๋กœ์ปฌ ํ‘ธ์‹œ์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ณด๋„๋ก ํ•˜์ž.

dependencies

dependencies:
	flutter_local_notifications: ^16.3.0

initialization

๋จผ์ € ํŒจํ‚ค์ง€ ์‚ฌ์šฉ์‹œ ์ดˆ๊ธฐํ™”๋ฅผ ๋จผ์ € ์ง„ํ–‰ํ•ด์•ผ ํ•œ๋‹ค.

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);

Heads Up Notifications

์•ฑ ์‹คํ–‰ ์ค‘ ํ™˜๊ฒฝ์ธ Foreground ํ™˜๊ฒฝ์—์„œ ํ‘ธ์‹œ ์•Œ๋ฆผ์„ ๋…ธ์ถœ ์‹œํ‚ค๋Š” ๊ธฐ๋Šฅ์„ heads up notifications๋ผ๊ณ  ํ•œ๋‹ค. ํ•ด๋‹น ๊ธฐ๋Šฅ์ด ์ •์ƒ์ ์œผ๋กœ ์ง„ํ–‰์ด ๋˜๋ ค๋ฉด ๊ฐ ํ”Œ๋žซํผ ๋ณ„๋กœ ์„ค์ •์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.

Android

android > app > src > main > AndroidManifest.xml

application ํƒœ๊ทธ ์ƒ๋‹จ์— Permission์„ ํ™œ์„ฑํ™”ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด ์ฃผ์ž.

  • android.permission.RECEIVE_BOOT_COMPLETED : ์žฌ๋ถ€ํŒ…์‹œ ์•ฑ ์‹คํ–‰
  • android.permission.VIBRATE : ์ง„๋™ ์‚ฌ์šฉ
  • android.permission.WAKE_LOCK : ํ™”๋ฉด ์ผœ์ง ์œ ์ง€
  • android.permission.USE_FULL_SCREEN_INTENT : ์ „์ฒด ํ™”๋ฉด ์ธํ…ํŠธ ์‚ฌ์šฉ
  • android.permission.SCHEDULE_EXACT_ALARM : ์ •ํ™•ํ•œ ์•Œ๋ฆผ
    <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>

AndroidManifest.xml

IOS

IOS ์„ค์ •์„ ์ถ”๊ฐ€ํ•ด ์ฃผ์ž.

IOS ์„ค์ •์„ ์œ„ํ•ด XCode๋ฅผ ์—ด์–ด์ฃผ๋„๋ก ํ•˜์ž. ios ํด๋”์˜ ์šฐ์ธก ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์—ฌ "Open in Xcode"๋ฅผ ๋ˆŒ๋Ÿฌ์ฃผ๋ฉด ํ”„๋กœ์ ํŠธ์˜ ios ์ˆ˜์ค€์˜ XCode๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Runner > AppDelegate.swift

์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด ์ฃผ๋„๋ก ํ•˜์ž.

  • Foreground ์ƒํƒœ์—์„œ ์•Œ๋ฆผ ๋…ธ์ถœ
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 ํŒจํ‚ค์ง€์—๋Š” ์–ด๋–ค ๊ธฐ๋Šฅ๋“ค์ด ์žˆ๋Š”์ง€ ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณด๋„๋ก ํ•˜์ž.

show

๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์ด ํ‘ธ์‹œ๋ฅผ ์ฆ‰์‹œ ์ „์†กํ•˜์—ฌ ๋…ธ์ถœ๋  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ์ด๋‹ค. ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉด 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

์ด๋ฒˆ์—” ๊ฐ„๋‹จํ•˜๊ฒŒ ํŠน์ • ์ฃผ๊ธฐ๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ๋กœ์ปฌ ํ‘ธ์‹œ๋ฅผ ๋ฐ˜๋ณต์ ์œผ๋กœ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

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์„ ์„ค์ • ํ•˜์—ฌ์•ผ ํ•œ๋‹ค.

Timezone์€ Flutter์— ๊ธฐ๋ณธ์ ์œผ๋กœ ๋‚ด์žฅ๋˜์–ด ์žˆ๋Š” ๋‚ด์žฅ ๊ฐ์ฒด๋กœ ์ถ”๊ฐ€์ ์œผ๋กœ ์ข…์†์„ฑ์€ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

initialization

import 'package:timezone/data/latest_all.dart' as tz;
import 'package:timezone/timezone.dart' as tz;

ํƒ€์ž„์กด์€ ์‚ฌ์šฉํ•˜๊ธฐ ์ „ ๋ฐ˜๋“œ์‹œ ์ดˆ๊ธฐํ™”๋ฅผ ์ง„ํ–‰ํ•˜์—ฌ์•ผ ํ•œ๋‹ค.

tz.initializeTimeZones();

setLocalLocation

ํƒ€์ž„์กด์˜ ๋กœ์ผ€์ด์…˜์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์„ค์ •์„ ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด UTC ์‹œ๊ฐ„๋Œ€๊ฐ€ ์„ค์ •์ด ๋œ๋‹ค.

ํƒ€์ž„์กด ์„ค์ •์€ ๊ฐ ๋กœ์ผ€์ด์…˜์„ ๋„ฃ์–ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๋Œ€ํ•œ๋ฏผ๊ตญ์€ Asis/Seoul ์ด๋‹ค.

tz.setLocalLocation(tz.getLocation('Asia/Seoul'));

TZDateTime

์ด์ œ ํƒ€์ž„์กด์„ ์‚ฌ์šฉํ•˜์—ฌ ์‹œ๊ฐ„์„ ๋งŒ๋“ค์–ด ์ฃผ๋„๋ก ํ•ด๋ณด์ž. 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

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๋ถ„ ๋’ค ์ „์†ก

ํ˜„์žฌ ์‹œ๊ฐ„์—์„œ 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์ผ ๋งˆ๋‹ค.. ์ด๋Ÿฐ ๊ธฐ๋Šฅ์€ ์•„๋‹ˆ๋‹ค. ๋ฌด์กฐ๊ฑด ํ•˜๋ฃจ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ•œ๋‹ค.

๐Ÿ’ฅ Local Timezone

์‹œ๊ฐ„๋Œ€์— ๋Œ€ํ•ด์„œ ์ž์„ธํžˆ ์‚ดํŽด๋ณผ ๋ถ€๋ถ„์ด ์žˆ๋‹ค.

ํƒ€์ž„์กด์„ ์ดˆ๊ธฐํ™”ํ•  ๋•Œ์— 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์˜ ์ „์†ก ์ฃผ๊ธฐ์™€ ์‹œ๊ฐ„๋Œ€๋ฅผ ๋‹ค๋ฃจ๋Š”๋ฐ์— ์žˆ์–ด์„œ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์ด ๋งŽ์œผ๋‹ˆ, ๊ผญ ์—ฌ๋Ÿฌ๋ฒˆ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ์‚ฌ์šฉํ•ด ๋ณด์‹œ๊ธธ ๋ฐ”๋ž€๋‹ค.

pendingNotificationRequests

๋กœ์ปฌ ํ‘ธ์‹œ์˜ ์Šค์ผ€์ฅด์„ ๋“ฑ๋กํ•˜๋Š” ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ดค๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ๋“ฑ๋ก๋œ ์Šค์ผ€์ฅด ๋ชฉ๋ก์„ ์–ป์œผ๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ?

flutter_local_notifications ํŒจํ‚ค์ง€์—์„œ๋Š” ๊ฐ„๋‹จํ•˜๊ฒŒ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋ฐฐ์—ด์— ๋‹ด์•„์„œ ์Šค์ผ€์ฅด ๋ชฉ๋ก์„ ๋ฐ˜ํ™˜ํ•ด์ค€๋‹ค.

pendingNotificationRequests() ํ•จ์ˆ˜๋งŒ ํ˜ธ์ถœํ•ด์ฃผ๋ฉด ๋˜๊ณ , ๋น„๋™๊ธฐ๋กœ ๋ฐ˜ํ™˜๋˜๋Š” ํ•จ์ˆ˜์ด๋ฏ€๋กœ ๋น„๋™๊ธฐ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

List<PendingNotificationRequest> notifications = await local.pendingNotificationRequests();

PendingNotificationRequest ๊ฐ์ฒด๋Š” ์–ด๋–ค ์ •๋ณด๋“ค์„ ๋ฐ˜ํ™˜ํ•ด ์ฃผ๋Š”์ง€ ์ฝ˜์†”์— ์ถœ๋ ฅํ•ด๋ณด๋ฉด, title, body, payload, id ์ด๋ ‡๊ฒŒ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ด์ค€๋‹ค.

cancel

์ด๋ฒˆ์—๋Š” ๋“ฑ๋ก์ด ์•„๋‹Œ ๋“ฑ๋ก๋œ ์Šค์ผ€์ฅด์˜ ์ „์†ก์„ ์ทจ์†Œํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

์ทจ์†Œ๋Š” ๊ฐ„๋‹จํ•˜๋‹ค. ๋‹จ์ผ ํ‘ธ์‹œ ์Šค์ผ€์ฅด์„ ์ทจ์†Œํ•  ์ˆ˜๋„ ์žˆ๊ณ  ํ•œ ๋ฒˆ์— ์ „์ฒด ์Šค์ผ€์ฅด์„ ์ทจ์†Œํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

cancel

๋‹จ์ผ ํ‘ธ์‹œ ์Šค์ผ€์ฅด์„ ์ทจ์†Œํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ํ•œ ๊ฐœ์˜ ์Šค์ผ€์ฅด์„ ์ทจ์†Œํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋ฐ˜๋“œ์‹œ ์•Œ๊ณ  ์žˆ์–ด์•ผ ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋Š” ๋ฐ”๋กœ id์ด๋‹ค.

id๋Š” ์•ฑ ๋‚ด์—์„œ ์ •ํ•ด์ง„ ๊ทœ์น™์œผ๋กœ ์ƒ์„ฑํ•˜์—ฌ ๊ด€๋ฆฌํ•ด๋„ ๋˜๊ณ , ์œ„์—์„œ ์‚ดํŽด๋ณธ PendingNotificationRequest ๊ฐ์ฒด๋ฅผ ์–ป์–ด์„œ id๋ฅผ ๊ด€๋ฆฌํ•ด๋„ ๋œ๋‹ค.

final FlutterLocalNotificationsPlugin local = FlutterLocalNotificationsPlugin();
local.cancel(id);

cancelAll

ํ•œ ๋ฒˆ์— ๋ชจ๋“  ์Šค์ผ€์ฅด์„ ์ „๋ถ€ ์ทจ์†Œํ•˜๋ ค๋ฉด ๊ฐ„๋‹จํžˆ cancelAll() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

final FlutterLocalNotificationsPlugin local = FlutterLocalNotificationsPlugin();
local.cancelAll();

Linstener

์ง€๊ธˆ๊นŒ์ง€ 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 ์ƒํƒœ๋Š” ๋™์ผํ•œ ๋ฆฌ์Šค๋„ˆ๋กœ ์ˆ˜์‹ ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

์ถ”๊ฐ€์ ์œผ๋กœ Foreground, Background๋ฅผ ๊ตฌ๋ถ„ํ•ด์•ผ ํ•œ๋‹ค๋ฉด AppLifeCycle์„ ์‚ฌ์šฉํ•œ ์ƒํƒœ ๊ด€๋ฆฌ๊ฐ€ ์ถ”๊ฐ€์ ์œผ๋กœ ํ•„์š”ํ•˜๋‹ค.

Flutter App Lifecycle

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);
        }
      },
    );

Terminate

์•ฑ ์ข…๋ฃŒ ์ƒํƒœ์—์„œ์˜ ๋กœ์ปฌ ํ‘ธ์‹œ ๋ฆฌ์Šค๋„ˆ๋ฅผ ๊ตฌ๋…ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ณด์ž. ์•ฑ ์ข…๋ฃŒ ์ƒํƒœ๋Š” ์•ฑ์ด ์™„์ „ํžˆ ์ข…๋ฃŒ๋œ ์ƒํƒœ์—์„œ ์ˆ˜์‹ ๋˜๋Š” ๊ธฐ๋Šฅ์ด์–ด์„œ ์•ฑ์˜ ์ตœ์ƒ๋‹จ์—์„œ ์ตœ์ดˆ ์ดˆ๊ธฐํ™” ๋  ๋•Œ์— ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰ํ•˜๋ฉด ๋œ๋‹ค.

ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰๋˜๋Š” ํŠธ๋ฆฌ๊ฐ€ ์•„๋‹Œ ์—ฌ๋Ÿฌ๋ฒˆ ์žฌ ์‹คํ–‰ ๋˜๋Š” ๋ถ€๋ถ„์— ํ•ด๋‹น ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰์‹œํ‚ค๋ฉด ๊ณ„์† ์ˆ˜์‹ ๋˜๋‹ˆ, ์•ฑ ์‹คํ–‰ ์ตœ์ดˆ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.

getNotificationAppLaunchDetails() ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ ์‹œํ‚ค๋ฉด ๋กœ์ปฌ ํ‘ธ์‹œ๋ฅผ ํด๋ฆญ ํ–ˆ์„ ๋•Œ์— ๋ฐ์ดํ„ฐ๋ฅผ ํ•ด๋‹น ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜ํ™˜ํ•ด ์ค€๋‹ค.

 NotificationAppLaunchDetails? details = await local.getNotificationAppLaunchDetails();
    if (details != null) {
      if (details.notificationResponse != null) {
        if (details.notificationResponse!.payload != null) {
          //
        }
      }
    }

flutter_local_notifications(Listener) - 2022.10

๋ฆฌ์Šค๋„ˆ ๋ถ€๋ถ„์ด ์ž˜ ์ž‘๋™๋˜์ง€ ์•Š๊ฑฐ๋‚˜, ์ถ”๊ฐ€์ ์œผ๋กœ Remote Push, Links ์‚ฌ์šฉ ๋“ฑ์œผ๋กœ ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ•˜๋Š”์ง€ ์ž˜ ๋ชจ๋ฅด์‹œ๋Š” ๋ถ„๋“ค์€ ๋Œ“๊ธ€ ๋‚จ๊ฒจ์ฃผ์‹œ๋ฉด ์ตœ๋Œ€ํ•œ ๋„์™€ ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.


์ง€๊ธˆ๊นŒ์ง€ flutter_local_notifications๋ฅผ ์‚ฌ์šฉํ•œ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ดค๋‹ค.

๊ธฐ๋ณธ์ ์ธ ๋‚ด์šฉ์ด๋ผ๊ณ  ํ•ด๋„ ์‚ฌ์‹ค ์œ„์—์„œ ์‚ดํŽด๋ณธ ๋‚ด์šฉ๋งŒ ์žˆ์–ด๋„ ๋กœ์ปฌ ํ‘ธ์‹œ ๊ตฌ์„ฑ์€ ๋๋‚ฌ๋‹ค๊ณ  ๋ด๋„ ๋œ๋‹ค. ์ด์ œ๋ถ€ํ„ฐ ์„ค๋ช…ํ•  ๋‚ด์šฉ์€ ์˜ต์…”๋„ํ•œ ๋ถ€๋ถ„์ด๋‹ˆ, ๋กœ์ปฌ ํ‘ธ์‹œ ๊ตฌ์„ฑ์„ ๋‹ค์–‘ํ•˜๊ฒŒ ํ•ด๋ณด์‹ค ๋ถ„์ด๋ผ๋ฉด ๊ผญ ๋ด์•ผํ•  ๋‚ด์šฉ์€ ์•„๋‹ ์ˆ˜๋„ ์žˆ๋‹ค.

์›๋ž˜๋Š” ์ด์ œ๋ถ€ํ„ฐ ์†Œ๊ฐœํ•  ๋‚ด์šฉ์„ ์‚ฌ์šฉํ•ด๋ณด๊ณ  ์ •๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ๊ธ€์„ ์ž‘์„ฑํ•˜๋ ค๊ณ  ํ•œ๊ฒƒ์ธ๋ฐ, ๋ฐฐ๋ณด๋‹ค ๋ฐฐ๊ผฝ์ด ๋” ์ปค์ ธ๋ฒ„๋ ธ๋‹ค... ๋กœ์ปฌ ํ‘ธ์‹œ๋Š” ํ•œ ๋ฒˆ ์„ธํŒ…ํ•˜๊ณ  ํ…Œ์ŠคํŠธ ํ•˜๊ธฐ๊นŒ์ง€ ์˜ค๋žœ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ๊ณ , ๋‚ด์šฉ๋„ ๋งŽ๋‹ค...

์ด๋ฒˆ์— ์ž‘์„ฑํ•˜๋ฉด์„œ ๋กœ์ปฌ ํ‘ธ์‹œ์˜ ๊ธฐ๋Šฅ๋“ค์„ ๋‹ด์€ ์•ฑ์„ ํ•˜๋‚˜ ๊ฐœ๋ฐœ ํ•˜์˜€๋Š”๋ฐ, ํ•„์š”ํ•˜์‹  ๋ถ„์€ ๊ธ€ ๋งˆ์ง€๋ง‰์— ์ž‘์„ฑํ•œ ๋‚ด์šฉ์„ ์ฐธ๊ณ ํ•˜์…”์„œ APK๋กœ ์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ ์ง์ ‘ ์•ฑ์„ ์‹คํ–‰ํ•ด ๋ณด์‹ค ์ˆ˜ ์žˆ๋‹ค.

์ž ์ด์ œ ๋กœ์ปฌ ํ‘ธ์‹œ๋ฅผ ์ข€ ๋” ๋ฉ‹์ง€๊ฒŒ ๋งŒ๋“ค์–ด ๋ณด์ž.

* ์•„๋ž˜ ๋‚ด์šฉ์€ ์ •ํ™•ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์ „๋ถ€ ์ง„ํ–‰ํ•˜์ง€๋Š” ๋ชปํ•ด์„œ ์ผ๋ถ€ ๋‚ด์šฉ์ด ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์œผ๋‹ˆ, ๋” ์ž์„ธํ•˜๊ฒŒ ์•Œ๊ณ  ์‹ถ์œผ์‹  ๋‚ด์šฉ์ด ์žˆ๋‹ค๋ฉด ํŽธํ•˜๊ฒŒ ๋Œ“๊ธ€ ๋‚จ๊ฒจ์ฃผ์„ธ์š”! ์ถ”๊ฐ€์ ์œผ๋กœ ํ…Œ์ŠคํŠธ ํ›„ ๋‚ด์šฉ ์ˆ˜์ •ํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค !

Image

์•ฑ์„ ์‚ฌ์šฉํ•˜๋ฉด์„œ ํ‘ธ์‹œ ์•Œ๋ฆผ์„ ๋ฐ›์•„ ๋ณด๋ฉด ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋Š” ํ‘ธ์‹œ๋“ค์ด ์žˆ์—ˆ์„ ๊ฒƒ์ด๋‹ค. ์ด๋ฏธ์ง€๋Š” ์–ด๋–ป๊ฒŒ ๋ณด๋‚ด์•ผ ํ• ๊นŒ ?

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;
}

NotificationDetails

๋กœ์ปฌ ํ‘ธ์‹œ ์ „์†ก์‹œ 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์—์„œ๋งŒ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

Category

์ด๋ฒˆ์—๋Š” ์•Œ๋ฆผ ์นดํ…Œ๊ณ ๋ฆฌ ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๋„๋ก ํ•˜์ž.

์นดํ…Œ๊ณ ๋ฆฌ ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด์„œ ๋ชจ๋ฅด์‹œ๋Š” ๋ถ„๋“ค์ด ์žˆ์„ ์ˆ˜ ์žˆ์–ด ์–ด๋–ค ๊ธฐ๋Šฅ์ธ์ง€์— ๋Œ€ํ•ด ๊ฐ„๋‹จํ•˜๊ฒŒ๋งŒ ์‚ดํŽด๋ณด๋ฉด, ์•„๋ž˜ ์ด๋ฏธ์ง€์™€ ๊ฐ™์ด ์•ฑ์„ ์‹คํ–‰์‹œํ‚ค์ง€ ์•Š๊ณ ๋„ ํ‘ธ์‹œ ์•Œ๋ฆผ์— ๋Œ€ํ•œ ์‘๋‹ต์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.

์‚ฌ์‹ค ์ด ๊ธฐ๋Šฅ์€ ์š”์ƒˆ๋Š” ์ž˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ธฐ๋Šฅ์œผ๋กœ ์•Œ๊ณ ์žˆ๋‹ค. ๋ช‡ ๋…„์ „๊นŒ์ง€๋งŒ ํ•ด๋„ ํ‘ธ์‹œ ์•Œ๋ฆผ์‹œ ์‚ฌ์šฉ์ž๋“ค์—๊ฒŒ ์›ํ•˜๋Š” ์•ก์…˜์„ ์–ป๊ธฐ ์œ„ํ•ด ์ „์†กํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์—ˆ์ง€๋งŒ, ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ž‘์—…์„ ์ง„ํ–‰ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๋ถ€๋ถ„๊ณผ ์•ก์…˜์„ ์ฒ˜๋ฆฌํ•˜๋Š”๋ฐ์— ์–ด๋ ค์›€์ด ์žˆ์–ด ํ˜„์žฌ๋Š” ์•Œ๋ฆผ์— ๋Œ€ํ•œ ์ž‘์—…์ฒ˜๋ฆฌ ๊ธฐ๋Šฅ์˜ ํ‘ธ์‹œ ํƒ€์ž…์€ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.

์–ด๋–ค ๊ธฐ๋Šฅ์ธ์ง€์— ๋Œ€ํ•ด์„œ๋Š” ์•Œ๊ณ  ์žˆ์œผ๋ฉด ๋‚˜์  ๊ฒƒ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์•Œ์•„๋ณด์ž.

์ด ๊ธฐ๋Šฅ ์—ญ์‹œ IOS, Android ํ”Œ๋žซํผ ๋ณ„๋กœ ์„ค์ •๊ณผ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์ด ๋‹ค๋ฅด๋‹ค.

๋จผ์ € Android๋Š” ์ถ”๊ฐ€์ ์ธ ์„ค์ •์€ ํ•ด์ฃผ์ง€ ์•Š์•„๋„ ๋˜๊ณ  IOS๋งŒ ์ถ”๊ฐ€์ ์ธ ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™” ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

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

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",
              )
            ],
          ),
        ],
      ),

Listener

์นดํ…Œ๊ณ ๋ฆฌ ์•ก์…˜ ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ๋ฐ›๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ตœ์ƒ์œ„ ๋˜๋Š” ์ •์ ๋ฉ”์†Œ๋“œ๋กœ ๊ตฌ์„ฑํ•ด์•ผ ํ•œ๋‹ค๊ณ  ํ•œ๋‹ค. ์•ฑ์ด ์‹คํ–‰๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ํ•ด๋‹น ์ด๋ฒคํŠธ๋ฅผ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๊ธฐ ๋˜๊ธฐ์— ์ด ๋ถ€๋ถ„์„ ์ƒ๊ฐํ•ด์„œ ๋‚ด๋ถ€ ์ด๋ฒคํŠธ๋ฅผ ๊ตฌ์„ฑํ•˜์‹œ๋ฉด ๋œ๋‹ค.

('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,
    );

์นดํ…Œ๊ณ ๋ฆฌ ์ž‘์—…์€ ๊ธฐ๋ณธ์ ์ธ ๊ตฌ์„ฑ ๋ฐฉ๋ฒ•๊ณผ ์‚ฌ์šฉ๋ฒ•์— ๋Œ€ํ•ด์„œ๋งŒ ๊ฐ€๋ณ๊ฒŒ ๋‹ค๋ค˜์œผ๋‹ˆ, ์œ„์˜ ๋‚ด์šฉ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์›ํ•˜์‹œ๋Š” ๊ธฐ๋Šฅ์„ ๋งŒ๋“œ์‹œ๋ฉด ๋œ๋‹ค.

SubTitle

IOS, Android ๋‘˜ ๋‹ค ํ‘ธ์‹œ์— SubTitle์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๊ณ , details ์˜ต์…˜์— ์ถ”๊ฐ€ํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

IOS

DarwinNotificationDetails(
	...
	subtitle: "SubTitle",
),

Android

AndroidNotificationDetails(
	...
	subText: "SubTitle",
),

Progress Only Android

์ด๋ฒˆ์—๋Š” 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,
      ),
    );
        );
      });
    }

Vibration

์ด๋ฒˆ์—๋„ ์•ˆ๋“œ๋กœ์ด๋“œ ์ „์šฉ์ธ ์ง„๋™ ํŒจํ„ด์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

์•ˆ๋“œ๋กœ์ด๋“œ์˜ ํ‘ธ์‹œ ์ „์†ก์‹œ ์ง„๋™ ํŒจํ„ด์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, IOS๋Š” ์ง„๋™ ํŒจํ„ด์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋‹ค.

์ง„๋™ ํŒจํ„ด์€ AndroidNotificationDetails ์˜ต์…˜์˜ vibrationPattern์„ ์‚ฌ์šฉํ•ด์„œ Int64List๋กœ ํŒจํ„ด์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

AndroidNotificationDetails(
	...
	vibrationPattern: Int64List.fromList([0, 1500, 500, 2000, 500, 1500]),
),
  • 1์ดˆ ๊ฐ„๊ฒฉ์œผ๋กœ 3์ดˆ์”ฉ 2๋ฒˆ ์ง„๋™
Int64List.fromList([0, 3000, 1000, 3000]);
  • 1์ดˆ ์ง„๋™ ํ›„ 2์ดˆ ํ›„ 3์ดˆ ์ง„๋™ ๊ทธ๋ฆฌ๊ณ  0.5์ดˆ ํ›„ 1์ดˆ ์ง„๋™
Int64List.fromList([0, 1000, 2000, 3000, 500, 1000]);

์ง„๋™ ํŒจํ„ด์€ ์ตœ์ดˆ ์ƒ์„ฑ๋œ channelId ๊ฐ’์„ ๊ธฐ์ค€์œผ๋กœ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์•ฑ์„ ์žฌ์„ค์น˜ ํ•˜๋ฉด์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜์‹œ๋ฉด ๋œ๋‹ค. ๋งŒ์ผ ๊ธฐ์กด channelId๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค๋ฉด, ์ฑ„๋„์ด ์ƒ์„ฑ๋  ๋•Œ์˜ ์ง„๋™ ํŒจํ„ด์„ ๋”ฐ๋ผ๊ฐ€๊ธฐ ๋•Œ๋ฌธ์— ๋ฌด์กฐ๊ฑด ์ดˆ๊ธฐ ์‹คํ–‰์‹œ ํŒจํ„ด์„ ๋งŒ๋“ค์–ด์•ผ ํ•˜๋ฉฐ, ๊ฐ™์€ channelId๋กœ๋Š” ํŒจํ„ด์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋‹ค.

Sound

์ง„๋™ ํŒจํ„ด์— ์ด์–ด์„œ ์‚ฌ์šด๋“œ๋ฅผ ๋ณ€๊ฒฝํ•ด๋ณด์ž.

์‚ฌ์šด๋“œ ๋ณ€๊ฒฝ์€ ์ง„๋™ ํŒจํ„ด๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ IOS์—์„œ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ธฐ๋Šฅ์ด๋‹ค.

์‚ฌ์šด๋“œ ๋ณ€๊ฒฝ์„ ์œ„ํ•ด์„œ ์˜ค๋””์˜ค ํŒŒ์ผ์ด ํ•„์š”ํ•œ๋ฐ, ์‚ฌ์šฉํ•  ์˜ค๋””์˜ค ํŒŒ์ผ์€ flutter_local_notifications ํŒจํ‚ค์ง€ ์˜ˆ์ œ ์•ฑ์— ์žˆ๋Š” ํŒŒ์ผ๋กœ ์‚ฌ์šฉ์„ ํ•ด๋ณด๋„๋ก ํ•˜์ž.

MP3ํŒŒ์ผ์€ ์•„๋ž˜ ๋งํฌ์˜ Git ์ €์žฅ์†Œ์—์„œ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

slow_spring_board.mp3

Android

์•ˆ๋“œ๋กœ์ด๋“œ๋Š” ๋จผ์ € resource๋ฅผ ์ƒ์„ฑํ•ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค.

android > app > src > main > res

ํ•ด๋‹น ๊ฒฝ๋กœ๋กœ ์ด๋™ํ•˜์—ฌ raw ํด๋”๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๊ณ , rawํด๋”์— ๋‹ค์šด๋ฐ›์€ mp3 ํŒŒ์ผ์„ ๋„ฃ์–ด์ฃผ์ž.

์‚ฌ์šฉ ๋ฐฉ๋ฒ•์€ ๊ฐ„๋‹จํ•˜๋‹ค. ์ฃผ์˜ํ•  ์ ์€ ์œ„์—์„œ ์ง„๋™ ํŒจํ„ด์„ ์ƒ์„ฑํ•œ ๊ฒƒ๊ณผ ๊ฐ™์ด channelId๋กœ ์ด๋ฏธ ์ƒ์„ฑ๋œ ๋กœ์ปฌ ํ‘ธ์‹œ๊ฐ€ ์žˆ๋‹ค๋ฉด ์ƒˆ๋กœ์šด ์˜ค๋””์˜ค ํŒŒ์ผ๋กœ ์‚ฌ์šด๋“œ ์žฌ์ƒ์ด ์•ˆ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์ƒˆ๋กœ์šด channelId๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ์•„๋‹ˆ๋ฉด ์•ฑ์„ ์ง€์› ๋‹ค๊ฐ€ ๋‹ค์‹œ ๋นŒ๋“œํ•˜๋ฉด ๋œ๋‹ค.

AndroidNotificationDetails ์˜ต์…˜ ์ค‘ sound์— ๋ฆฌ์†Œ์Šค ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ๋„๋ก ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์ž.

๋งŒ์ผ ์žฌ์ƒ์ด ๋˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, playSound ๊ฐ’์ด true๋กœ ๋˜์–ด ์žˆ๋Š”์ง€๋„ ํ™•์ธํ•ด๋ณด์ž.

AndroidNotificationDetails(
	...
	sound: const RawResourceAndroidNotificationSound("slow_spring_board"),
      ),

IOS

์ด์–ด์„œ IOS์—์„œ๋„ ์‚ฌ์šด๋“œ๋ฅผ ๋ณ€๊ฒฝํ•ด๋ณด์ž.

IOS๋Š” mp3ํŒŒ์ผ์ด ์•„๋‹Œ aiff ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— IOS์— ์‚ฌ์šฉํ•  ์˜ค๋””์˜ค ํŒŒ์ผ๋„ ์•„๋ž˜ ๋งํฌ์—์„œ ๋‹ค์šด๋กœ๋“œ ํ•ด์ฃผ์ž.

slow_spring_board.aiff

๋จผ์ € XCode๋ฅผ ์—ด์–ด์ฃผ์ž.

Runner ํ•˜์œ„์— ๋‹ค์šด๋ฐ›์€ ์˜ค๋””์˜ค ํŒŒ์ผ์„ ๋„ฃ์–ด์ฃผ๋ฉด ๋œ๋‹ค.

DarwinNotificationDetails ์˜ต์…˜์˜ sound์— ํŒŒ์ผ ์ด๋ฆ„์„ ๋„ฃ์–ด์ฃผ๋ฉด ๋œ๋‹ค. ๋์ด๋‹ค.

DarwinNotificationDetails(
	presentAlert: true,
	presentBadge: true,
	presentSound: true,
	sound: "slow_spring_board.aiff",
),

๋งˆ๋ฌด๋ฆฌ

์ง€๊ธˆ๊นŒ์ง€ flutter_local_notifications ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•œ ๋กœ์ปฌ ํ‘ธ์‹œ ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด์„œ ์„ค์ •๋ถ€ํ„ฐ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•๊นŒ์ง€ ์•Œ์•„๋ณด์•˜๋‹ค.

flutter_local_notifications ํŒจํ‚ค์ง€์—๋Š” ์ด ์™ธ์—๋„ ๋งŽ์€ ๊ธฐ๋Šฅ๋“ค์ด ์ œ๊ณต๋˜๊ณ  ์žˆ์œผ๋‹ˆ, ํ•œ ๋ฒˆ์”ฉ ์‚ฌ์šฉํ•ด ๋ณด์‹œ๋ฉด์„œ ์ž์„ธํžˆ ์‚ดํŽด๋ณด์‹œ๊ธธ ๋ฐ”๋ž€๋‹ค.

์ด๋ฒˆ ๊ธ€์˜ ๋‚ด์šฉ์ด ๋„ˆ๋ฌด ๊ธธ์–ด์ ธ, ์ฃผ์š”ํ•œ ๊ธฐ๋Šฅ๋งŒ ์ž‘์„ฑํ•˜๊ฒŒ ๋˜์—ˆ์œผ๋‹ˆ ์ถ”๊ฐ€์ ์œผ๋กœ ๊ถ๊ธˆํ•˜์‹  ๋ถ€๋ถ„์€ ์š”์ฒญ ์ฃผ์‹œ๋ฉด ์ด ๊ธ€์— ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ƒˆ๋กœ์šด ๊ธ€๋กœ ์ž‘์„ฑํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค

์ฐธ๊ณ ๋กœ ์•Œ๋ฆผ ๊ธฐ๋Šฅ์€ PlugIn ํŒจํ‚ค์ง€์ด๋‹ค ๋ณด๋‹ˆ, ์„ค์ • ๋ฐ ๋ฒ„์ „์— ๋”ฐ๋ผ ์ž‘๋™์ด ์•ˆ๋˜์‹ค ์ˆ˜๋„ ์žˆ์œผ๋‹ˆ, ์•Œ๋ฆผ์„ ์ƒ์„ฑํ•˜๊ณ  ์‹ค์ œ ์•Œ๋ฆผ์ด ์˜ค๋Š”์ง€ ๊ผญ ํ…Œ์ŠคํŠธ ํ•ด๋ณด์‹œ๋ฉด์„œ ๊ฐœ๋ฐœ ํ•˜์‹œ๊ธธ ๋ฐ”๋ž€๋‹ค.

์ง€๊ธˆ๊นŒ์ง€ ๋‚ด์šฉ ์ค‘ ๊ถ๊ธˆํ•˜์‹  ๋‚ด์šฉ์€ ๋Œ“๊ธ€ ๋‚จ๊ฒจ์ฃผ์‹œ๋ฉด ์ตœ๋Œ€ํ•œ ๋น ๋ฅธ ๋‹ต๋ณ€ ๋“œ๋ฆฌ๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค !!


Sample App

Git Repository

์ด๋ฒˆ์— flutter_local_notifications ํŒจํ‚ค์ง€์— ๋Œ€ํ•ด์„œ ๋ณด๋‹ค ์ž์„ธํžˆ ๊ธ€์„ ์ž‘์„ฑํ•˜๊ฒŒ ๋˜๋ฉด์„œ, ๋‚˜์ค‘์—๋„ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ์ด ์žˆ์œผ๋ฉด ๊ทธ๋•Œ๋งˆ๋‹ค ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ์—” ์„ค์ •ํ•ด์•ผ ํ•˜๋Š” ๋ถ€๋ถ„์ด ๋งŽ์•„์„œ ํ…Œ์ŠคํŠธ์šฉ ์•ฑ์„ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด ๋ดค๋‹ค.

๊ฐ„๋‹จํ•˜๊ฒŒ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ด๋ณด๊ณ  ์‹ถ์œผ์‹  ๋ถ„๋“ค์€ Git Repository ๋“ค์–ด๊ฐ€์…”์„œ ํ”„๋กœ์ ํŠธ ์‹คํ–‰ํ•ด ๋ณด์‹œ๋ฉด ๋„์›€์ด ๋ ๊ฒ๋‹ˆ๋‹ค.



profile
Flutter Developer

10๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2024๋…„ 3์›” 6์ผ

์•ˆ๋…•ํ•˜์„ธ์š”. ๊ธ€ ์ž˜ ๋ณด์•˜์Šต๋‹ˆ๋‹ค. ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.
์ €๋Š” ํ˜„์žฌ flutter ์—์„œ ๊ฒŒ์‹œ๊ธ€ ๋Œ“๊ธ€์˜ ์•Œ๋ฆผ์„ ์œ„ํ•ด local_notification์„ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.
์ž๊ธฐ์ž์‹ ํ•œํ…Œ ํŠน์ •ํ•œ ์ด๋ฒคํŠธ(์˜ˆ๋กœ, ๋ฒ„ํŠผํด๋ฆญ)๊ฐ€ ๋ฐœ์ƒํ•˜์˜€์„๋•Œ๋Š” ๊ตฌํ˜„์ด ์ž˜ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ํŠน์ • ์‚ฌ์šฉ์žํ•œํ…Œ ๋ณด๋‚ด๋Š”๊ฒƒ์ด ์ดํ•ด๊ฐ€ ๋˜์ง€ ์•Š์•„ ๊ธ€์„ ๋‚จ๊ฒผ์Šต๋‹ˆ๋‹ค.

์‚ฌ์šฉ์žA๊ฐ€ ์‚ฌ์šฉ์žB์˜ ๊ฒŒ์‹œ๊ธ€์— ๋Œ“๊ธ€์„ ๋‹ฌ์•˜๋‹ค๋ฉด, ์‚ฌ์šฉ์žBํ•œํ…Œ ์•Œ๋ฆผ์„ ๋ณด๋‚ผ์ˆ˜๋Š” ์—†๋Š”๊ฑด๊ฐ€์š”?

1๊ฐœ์˜ ๋‹ต๊ธ€
comment-user-thumbnail
2024๋…„ 3์›” 14์ผ

์ƒ์„ธํ•œ ์„ค๋ช… ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

1๊ฐœ์˜ ๋‹ต๊ธ€
comment-user-thumbnail
2024๋…„ 3์›” 24์ผ

์•ˆ๋…•ํ•˜์„ธ์š”. ๋ธ”๋กœ๊ทธ ์ฐธ๊ณ ํ•˜๋ฉฐ flutter์™€ fcm ๋ฐ local notification์„ ํ™œ์šฉํ•˜์—ฌ push ์•Œ๋ฆผ์„ ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋””๋ฐ”์ด์Šค๊ฐ€ ์ž ๊ธˆ ์ƒํƒœ์ผ ๋•Œ, ์ฆ‰ ํ™”๋ฉด์ด ๊บผ์ ธ ์žˆ๋Š” ์ƒํƒœ์—์„œ ์•Œ๋ฆผ์ด ๋„์ฐฉํ•˜๋Š” ๊ฒฝ์šฐ์— ์†Œ๋ฆฌ๋งŒ ๋“ค๋ ค์„œ ํ™”๋ฉด์„ ๊ฐ™์ด ๊นจ์šฐ๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ณ  ์‹ถ์€๋ฐ, ์ด์™€ ๊ด€๋ จํ•˜์—ฌ ๋„์›€์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์„๊นŒ์š”? terminate ์ƒํƒœ์ผ ๋•Œ ์•Œ๋ฆผ์ด ์™”๋‹ค๋Š” ๊ฒƒ์„ ๋””๋ฐ”์ด์Šค ํ™”๋ฉด์„ ๊นจ์›Œ๋†“๊ณ  ๋ณด๊ณ  ์žˆ์ง€ ์•Š๋Š” ์ด์ƒ ํ™•์ธํ•˜๊ธฐ ์–ด๋ ค์šด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด๋ผ ์ƒ๊ฐ๋˜์–ด์„œ์š”.

1๊ฐœ์˜ ๋‹ต๊ธ€