μλ νμΈμ©:)
2020λ 4μμ μμ νλ [flutter] local notification Quick Start μμ μ΄νλ‘
μ΄λ² μ±μλ μλ¦Ό κΈ°λ₯μ μ μ© μμΌμΌν΄μ 2020λ 11μ κΈ°μ€μΌλ‘ λ€μ μμ±ν΄λ³΄λ €ν©λλΉ
π₯ μ΄λ²μ λ§₯λΆμ΄ μμΌλ! iOS settingλΆν° ν΄μ iOS κΈ°μ€μΌλ‘ μ λμκ°κ²λ ν΄λ³΄κ² μ΅λλ€π₯
https://pub.dev/packages/flutter_local_notifications/install
dependencies:
# notification (20.11.11 κΈ°μ€)
flutter_local_notifications: ^3.0.1+2
NotificationCompat API
λ₯Ό μ¬μ©νμ¬ κ΅¬ν Android μ₯μΉλ₯Ό μ€νν μ μμ΅λλ€.UILocalNotification API
λ₯Ό μ¬μ©ν©λλ€. UserNotification API
λ₯Ό (aka User Notifications Framework)λ iOS 10μ΄μμμ μ¬μ©λ©λλ€.{flutter App} > android > app > src > main > AndroidManifest.xml
...
<application
...
>
<activity
...
android:windowSoftInputMode="adjustResize"
android:showWhenLocked="true"
android:turnScreenOn="true">
<intent-filter>
...
</intent-filter>
</activity>
...
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
<receiver 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νμΌ
- κΈ°κΈ°κ° μ 겨μμ λλ μΆλ ₯
<activity ... android:showWhenLocked="true" android:turnScreenOn="true">
- μ¬λΆν μμλ notifications remain scheduled μ μ§νλ €λ©΄ μΆκ°
<receiver 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>
- scheduled notifications νλ©΄μ μΆλ ₯ν λ €λ©΄ μΆκ°
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
<!-- local notification -->
<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" />
- λΆν μ μλΉμ€(Service) μ€ννκΈ°
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
- μ§λ(VIBRATE) μ¬μ©
<uses-permission android:name="android.permission.VIBRATE"/>
- ν΄λν°μ΄ κΊΌμ Έμλ μνμμ μλ¦Όμ΄ λ°μνλ©΄ νλ©΄μ κΉ¨μ°λ κΈ°λ₯
(μλ¦Ό λ°μ μ νλ©΄ μΌμ§κ² νλ κΈ°λ₯)<uses-permission android:name="android.permission.WAKE_LOCK"/>
- Full-screen intent notifications
(cf. Full-Screen Intent Notification νλ©΄ μμ & μ€λͺ )<uses-permission android:name="android.permission.WAKE_LOCK"/>
{flutter App} > ios > Runner > AppDelegate.swift
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
...
) -> Bool {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
...
}
}
λΌμ΄λΈλ¬λ¦¬ 곡μ μ€λͺ
iOSλ κΈ°λ³Έμ μΌλ‘ μ΄νμ΄ μ΄λ €μμ λ(=foreground) μλμ΄ μΈλ¦¬μ§ μλλ‘ μ€μ λμ΄μμ΅λλ€.
- iOS 10 μ΄μμμ presentation optionsμ¬μ©ν΄ foregroundμ μλ λμ μλ¦Όμ΄ νΈλ¦¬κ±° λ λ λμ 컨νΈλ‘€
if #available(iOS 10.0, *) { UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate }
β μ μΈμ€ν΄μ€λ₯Ό λ§λ λ€μ κ° νλ«νΌμ μ¬μ©ν μ€μ μΌλ‘ μ΄κΈ°ν μμ
void main() async {
...
_initNotiSetting();
runApp(MyApp());
}
void _initNotiSetting() async {
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
final initSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher');
final initSettingsIOS = IOSInitializationSettings(
requestSoundPermission: false,
requestBadgePermission: false,
requestAlertPermission: false,
);
final initSettings = InitializationSettings(
android: initSettingsAndroid,
iOS: initSettingsIOS,
);
await flutterLocalNotificationsPlugin.initialize(
initSettings,
);
}
- μ΄κΈ°νλ ν λ²λ§ μνν΄μΌ νλ©° μ΄λ
main.dart
μμ κΈ°λ₯ μν
(λλ μ±μ νμλ 첫 λ²μ§Έ νμ΄μ§ λ΄μμμ΄ μμ μ μν ν μ μμ΅λλ€.)- μ± μμ ν λ°λ‘ κΆνμ 묻길 μνλ€λ©΄
requestSoundPermission
trueλ‘ λ³κ²½
- μ λ μ±μμ ν 묻λκ² μλ μλ¦Ό μκ°μ€μ ν μλ¦ΌκΆνμ λ¬Όμ΄λ΄€μΌλ©΄ ν΄μ μ΄κΈ°μ€μ μλ falseλ‘ μμ±νμ΅λλ€.
notificationμ μ€μ
μ½λ μ€λͺ
νκ² μ΅λλ€.UI
SubmitButton(
...
text: 'νμΈ',
onPressed: isDisabled ? null : _dailyAtTimeNotification,
)
onPressed
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
Future _dailyAtTimeNotification() async {
final notiTitle = 'title';
final notiDesc = 'description';
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
final result = await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(
alert: true,
badge: true,
sound: true,
);
var android = AndroidNotificationDetails('id', notiTitle, notiDesc,
importance: Importance.max, priority: Priority.max);
var ios = IOSNotificationDetails();
var detail = NotificationDetails(android: android, iOS: ios);
if (result) {
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.deleteNotificationChannelGroup('id');
await flutterLocalNotificationsPlugin.zonedSchedule(
0, // idλ uniqueν΄μΌν©λλ€. intκ°
notiTitle,
notiDesc,
_setNotiTime(),
detail,
androidAllowWhileIdle: true,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: DateTimeComponents.time,
);
}
}
tz.TZDateTime _setNotiTime() {
tz.initializeTimeZones();
tz.setLocalLocation(tz.getLocation('Asia/Seoul'));
final now = tz.TZDateTime.now(tz.local);
var scheduledDate = tz.TZDateTime(tz.local, now.year, now.month, now.day,
10, 0);
return scheduledDate;
}
}
- idλ uniqueν΄μΌν©λλ€.
- μ°μ κΈ°μ‘΄μ μ°λ
schedule()
μ΄ λ²μ μ μ΄ λλ©΄μ deprecated(μ¬μ©μ€λ¨) λμ΅λλ€.FlutterLocalNotificationsPlugin().zonedSchedule()
λ‘ μ¬μ©νλ©΄λλ€.
- κΈ°μ‘΄ : μ§μ λ λ μ§ λ° μκ°μ νμ ν μλ¦Όμ μμ½ν©λλ€.
- λ³κ²½ : νΉμ μκ°λλ₯Ό κΈ°μ€μΌλ‘ μ§μ λ λ μ§ λ° μκ°μ νμλλλ‘ μλ¦Όμ μμ½ν©λλ€.
= κ°λ¨ν λ§ν΄μ timezoneμ€μ μ΄ κ°μ₯ ν° μ°¨μ΄μ
- κ°μ₯ λμ¬κ²¨ λ³Ό κ³³ =
_setNotiTime()
- TZDateTimeμ default timezoneμ΄
UTC
λ‘ λμ΄μλ€.- κ·Έλμ
tz.initializeTimeZones();
ν
νμλ‘tz.setLocalLocation(tz.getLocation('Asia/Seoul'));
λ₯Ό ν΄μ€μΌνλ€.- setLocationν λ μμ½μ§λ§ timezone(ex. KST)μ λ£λκ² μλ
location name(ex. Asia/Seoul)λ₯Ό λ£μ΄μ€μΌνλ€.
- λ¬Όλ‘ λμμλ μμ€μ½λ κ·Έλλ‘ μ€μ νλ©΄ νκ΅μ΄ μλ λ€λ₯Έ timezoneμ κ³μ λΆλ€μ.. λ§κ·Έλλ‘ μ΄μ λ°μπ₯π₯π₯π₯
- https://pub.dev/packages/flutter_native_timezone
await FlutterNativeTimezone.getLocalTimezone();
μ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ΄μ©ν΄μ κ°μΈμ μΌλ‘ providerλ‘ κ΄λ¦¬ν΄μ λ°μμ€μ§λ§ λ°λ‘ μ μ§μμλλ°
ν΄λΉ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©νλ©΄ λ¬Έμ μμ΄ location nameμ€μ μ΄ κ°λ₯ν©λλ€.
- μλ λ€μ μ½λλ₯Ό μΆκ°λ₯Ό μνλ©΄ μ΄μ NotificationChannelλ€μ΄ μ΄μμμ΄μ λ€μ€μΌλ‘ μλ¦Ό μ§μ₯μ λ°κ²λλ€..π₯
- μ΄λ§μ μ¦ μ¬λ¬κ°μ μλμ νκ³ μΆμ κ²½μ°, λ€μ
deleteNotificationChannelGroup
λ₯Ό λΉΌμ£Όλ©΄ λλ€.await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>() ?.deleteNotificationChannelGroup('id');
uniqueν idκ° ν λλ‘ .cancle(id)
κ° νΈμΆμ μλ μμ λ©λλ€.
await FlutterLocalNotificationsPlugin().cancel(id)
μλλ‘μ΄λμμ μ΄κΈ° μ ν λ λ€μκ³Ό κ°μ΄ μλ¬λ λ μ°Έκ³
platformException (PlatformException(INVALID_ICON, The resource app_icon could not be found. Please make sure it has been added as a drawable resource to your Android head project., null, null))
solution
μ λ§ κ°μ¬ν©λλ€! μκ° μ€μ μ΄λ»κ² ν΄μ€μΌνλ.. κ³ λ―Όνκ³ μμλλ° γ γ
νμνκ±Έ κ²μνλ©΄, λ€λ³΄λ λΈλ‘κ·Έμ μ΄λ―Έ λ€ μ€λͺ μ΄ λμλ€μ! μ€λλ κ°μ¬ν©λλ€!
{flutter App} > android > app > src > main > AppDelegate.swift λΆλΆ μ€νκ° μλ κ² κ°μ΅λλ€!
AppDelegate.swift -> AndroidManifest.xml
κ°μΌλ 3.0.1+4 μ΄μ λ²μ μμ AndroidManifest.xml μμ μμ ν΄μΌ λ κ² λ§μ΄ μ€μλ€μ
https://pub.dev/packages/flutter_local_notifications#-android-setup