From c81ce8b6e254b9b2e569d5ff00981af3a1421ec2 Mon Sep 17 00:00:00 2001 From: Jeroen van de Ven Date: Fri, 17 Dec 2021 15:03:28 +0100 Subject: [PATCH 1/6] Use setAlarmClock --- .../FlutterLocalNotificationsPlugin.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java index d417da74d..0ff55cb80 100644 --- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java +++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java @@ -450,10 +450,10 @@ private static void scheduleNotification( AlarmManager alarmManager = getAlarmManager(context); if (BooleanUtils.getValue(notificationDetails.allowWhileIdle)) { - AlarmManagerCompat.setExactAndAllowWhileIdle( + AlarmManagerCompat.setAlarmClock( alarmManager, - AlarmManager.RTC_WAKEUP, notificationDetails.millisecondsSinceEpoch, + pendingIntent, pendingIntent); } else { AlarmManagerCompat.setExact( @@ -492,8 +492,8 @@ private static void zonedScheduleNotification( .toInstant() .toEpochMilli(); if (BooleanUtils.getValue(notificationDetails.allowWhileIdle)) { - AlarmManagerCompat.setExactAndAllowWhileIdle( - alarmManager, AlarmManager.RTC_WAKEUP, epochMilli, pendingIntent); + AlarmManagerCompat.setAlarmClock( + alarmManager, epochMilli, pendingIntent, pendingIntent); } else { AlarmManagerCompat.setExact(alarmManager, AlarmManager.RTC_WAKEUP, epochMilli, pendingIntent); } @@ -515,8 +515,8 @@ static void scheduleNextRepeatingNotification( PendingIntent pendingIntent = getBroadcastPendingIntent(context, notificationDetails.id, notificationIntent); AlarmManager alarmManager = getAlarmManager(context); - AlarmManagerCompat.setExactAndAllowWhileIdle( - alarmManager, AlarmManager.RTC_WAKEUP, notificationTriggerTime, pendingIntent); + AlarmManagerCompat.setAlarmClock( + alarmManager, notificationTriggerTime, pendingIntent, pendingIntent); saveScheduledNotification(context, notificationDetails); } @@ -568,8 +568,8 @@ private static void repeatNotification( AlarmManager alarmManager = getAlarmManager(context); if (BooleanUtils.getValue(notificationDetails.allowWhileIdle)) { - AlarmManagerCompat.setExactAndAllowWhileIdle( - alarmManager, AlarmManager.RTC_WAKEUP, notificationTriggerTime, pendingIntent); + AlarmManagerCompat.setAlarmClock( + alarmManager, notificationTriggerTime, pendingIntent, pendingIntent); } else { alarmManager.setInexactRepeating( AlarmManager.RTC_WAKEUP, notificationTriggerTime, repeatInterval, pendingIntent); From c9c07a4724f8335f8c7dadcc440b4be9a6fcc1f5 Mon Sep 17 00:00:00 2001 From: github-actions <> Date: Fri, 17 Dec 2021 14:13:37 +0000 Subject: [PATCH 2/6] Google Java Format --- .../FlutterLocalNotificationsPlugin.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java index 0ff55cb80..389bfbfc2 100644 --- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java +++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java @@ -451,10 +451,7 @@ private static void scheduleNotification( AlarmManager alarmManager = getAlarmManager(context); if (BooleanUtils.getValue(notificationDetails.allowWhileIdle)) { AlarmManagerCompat.setAlarmClock( - alarmManager, - notificationDetails.millisecondsSinceEpoch, - pendingIntent, - pendingIntent); + alarmManager, notificationDetails.millisecondsSinceEpoch, pendingIntent, pendingIntent); } else { AlarmManagerCompat.setExact( alarmManager, @@ -492,8 +489,7 @@ private static void zonedScheduleNotification( .toInstant() .toEpochMilli(); if (BooleanUtils.getValue(notificationDetails.allowWhileIdle)) { - AlarmManagerCompat.setAlarmClock( - alarmManager, epochMilli, pendingIntent, pendingIntent); + AlarmManagerCompat.setAlarmClock(alarmManager, epochMilli, pendingIntent, pendingIntent); } else { AlarmManagerCompat.setExact(alarmManager, AlarmManager.RTC_WAKEUP, epochMilli, pendingIntent); } From 2b16361b6051f8f8eec9085a2d57499cb6b5fc14 Mon Sep 17 00:00:00 2001 From: Jeroen van de Ven Date: Mon, 10 Jan 2022 17:28:11 +0100 Subject: [PATCH 3/6] [WIP] Use StringSet to avoid very big json strings, test performance --- .../FlutterLocalNotificationsPlugin.java | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java index 389bfbfc2..29fe21762 100644 --- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java +++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java @@ -87,6 +87,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; /** FlutterLocalNotificationsPlugin */ @Keep @@ -100,6 +101,8 @@ public class FlutterLocalNotificationsPlugin private static final String DEFAULT_ICON = "defaultIcon"; private static final String SELECT_NOTIFICATION = "SELECT_NOTIFICATION"; private static final String SCHEDULED_NOTIFICATIONS = "scheduled_notifications"; + // NOTE: We don't convert on update, so we can never release this until we do + private static final String SCHEDULED_NOTIFICATIONS_SET = "scheduled_notifications_set"; private static final String INITIALIZE_METHOD = "initialize"; private static final String GET_CALLBACK_HANDLE_METHOD = "getCallbackHandle"; private static final String ARE_NOTIFICATIONS_ENABLED_METHOD = "areNotificationsEnabled"; @@ -392,12 +395,14 @@ static Gson buildGson() { private static ArrayList loadScheduledNotifications(Context context) { ArrayList scheduledNotifications = new ArrayList<>(); SharedPreferences sharedPreferences = - context.getSharedPreferences(SCHEDULED_NOTIFICATIONS, Context.MODE_PRIVATE); - String json = sharedPreferences.getString(SCHEDULED_NOTIFICATIONS, null); - if (json != null) { + context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_SET, Context.MODE_PRIVATE); + Set jsons = sharedPreferences.getStringSet(SCHEDULED_NOTIFICATIONS_SET, null); + if (jsons != null) { Gson gson = buildGson(); - Type type = new TypeToken>() {}.getType(); - scheduledNotifications = gson.fromJson(json, type); + Type type = new TypeToken() {}.getType(); + for (String json : jsons) { + scheduledNotifications.add(gson.fromJson(json, type)); + } } return scheduledNotifications; } @@ -405,11 +410,15 @@ private static ArrayList loadScheduledNotifications(Context private static void saveScheduledNotifications( Context context, ArrayList scheduledNotifications) { Gson gson = buildGson(); - String json = gson.toJson(scheduledNotifications); + int amount = scheduledNotifications.size(); + String[] jsons = new String[amount]; + for (int i = 0; i < amount; i++) { + jsons[i] = gson.toJson(scheduledNotifications.get(i)); + } SharedPreferences sharedPreferences = - context.getSharedPreferences(SCHEDULED_NOTIFICATIONS, Context.MODE_PRIVATE); + context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_SET, Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(SCHEDULED_NOTIFICATIONS, json); + editor.putStringSet(SCHEDULED_NOTIFICATIONS_SET, Set.of(jsons)); editor.apply(); } @@ -606,6 +615,7 @@ private static long calculateRepeatIntervalMilliseconds(NotificationDetails noti return repeatInterval; } + // TODO don't json parse the whole list, but this will allow duplicate notifications with the same id private static void saveScheduledNotification( Context context, NotificationDetails notificationDetails) { ArrayList scheduledNotifications = loadScheduledNotifications(context); @@ -1615,7 +1625,11 @@ private void cancelAllNotifications(Result result) { alarmManager.cancel(pendingIntent); } - saveScheduledNotifications(applicationContext, new ArrayList<>()); + SharedPreferences sharedPreferences = + applicationContext.getSharedPreferences(SCHEDULED_NOTIFICATIONS_SET, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.remove(SCHEDULED_NOTIFICATIONS_SET); + editor.apply(); result.success(null); } From 4302ce1eadcd5f5678751e920e520beecd69655c Mon Sep 17 00:00:00 2001 From: Jeroen van de Ven Date: Mon, 10 Jan 2022 18:04:43 +0100 Subject: [PATCH 4/6] [WIP] Don't parse all jsons on add, improve performance 85% --- .../FlutterLocalNotificationsPlugin.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java index 29fe21762..52917e5b3 100644 --- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java +++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java @@ -84,6 +84,7 @@ import java.util.Arrays; import java.util.Calendar; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -615,19 +616,18 @@ private static long calculateRepeatIntervalMilliseconds(NotificationDetails noti return repeatInterval; } - // TODO don't json parse the whole list, but this will allow duplicate notifications with the same id + // Don't json parse the whole list, but this will allow duplicate notifications with the same id private static void saveScheduledNotification( Context context, NotificationDetails notificationDetails) { - ArrayList scheduledNotifications = loadScheduledNotifications(context); - ArrayList scheduledNotificationsToSave = new ArrayList<>(); - for (NotificationDetails scheduledNotification : scheduledNotifications) { - if (scheduledNotification.id.equals(notificationDetails.id)) { - continue; - } - scheduledNotificationsToSave.add(scheduledNotification); - } - scheduledNotificationsToSave.add(notificationDetails); - saveScheduledNotifications(context, scheduledNotificationsToSave); + SharedPreferences sharedPreferences = + context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_SET, Context.MODE_PRIVATE); + Set jsons = sharedPreferences.getStringSet(SCHEDULED_NOTIFICATIONS_SET, new HashSet()); + Set newSet = new HashSet(jsons); + Gson gson = buildGson(); + newSet.add(gson.toJson(notificationDetails)); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putStringSet(SCHEDULED_NOTIFICATIONS_SET, newSet); + editor.apply(); } private static int getDrawableResourceId(Context context, String name) { From e358b3b31c538358aa5524bd8af15209ddfab4d5 Mon Sep 17 00:00:00 2001 From: Jeroen van de Ven Date: Tue, 11 Jan 2022 10:37:07 +0100 Subject: [PATCH 5/6] Add data migration from big String to StringSet --- .../FlutterLocalNotificationsPlugin.java | 95 +++++++++++++++---- 1 file changed, 78 insertions(+), 17 deletions(-) diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java index 52917e5b3..f13b65428 100644 --- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java +++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java @@ -26,6 +26,7 @@ import android.text.Spanned; import android.text.TextUtils; import android.text.style.ForegroundColorSpan; +import android.util.Log; import androidx.annotation.Keep; import androidx.annotation.NonNull; import androidx.core.app.AlarmManagerCompat; @@ -101,8 +102,8 @@ public class FlutterLocalNotificationsPlugin private static final String DRAWABLE = "drawable"; private static final String DEFAULT_ICON = "defaultIcon"; private static final String SELECT_NOTIFICATION = "SELECT_NOTIFICATION"; - private static final String SCHEDULED_NOTIFICATIONS = "scheduled_notifications"; - // NOTE: We don't convert on update, so we can never release this until we do + private static final String SCHEDULED_NOTIFICATIONS_FILE = "scheduled_notifications"; + private static final String SCHEDULED_NOTIFICATIONS_STRING = "scheduled_notifications"; private static final String SCHEDULED_NOTIFICATIONS_SET = "scheduled_notifications_set"; private static final String INITIALIZE_METHOD = "initialize"; private static final String GET_CALLBACK_HANDLE_METHOD = "getCallbackHandle"; @@ -396,30 +397,84 @@ static Gson buildGson() { private static ArrayList loadScheduledNotifications(Context context) { ArrayList scheduledNotifications = new ArrayList<>(); SharedPreferences sharedPreferences = - context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_SET, Context.MODE_PRIVATE); - Set jsons = sharedPreferences.getStringSet(SCHEDULED_NOTIFICATIONS_SET, null); - if (jsons != null) { + context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_FILE, Context.MODE_PRIVATE); + Set jsonNotifications = getScheduledNotificationsJsonSet(sharedPreferences); + if (jsonNotifications != null) { Gson gson = buildGson(); Type type = new TypeToken() {}.getType(); - for (String json : jsons) { + for (String json : jsonNotifications) { scheduledNotifications.add(gson.fromJson(json, type)); } } return scheduledNotifications; } + /** + * Get scheduled notifications from shared preferences, converting from old format if present. + * + * Returns null if neither are present + * The returned Set may not be mutated! + */ + private static Set getScheduledNotificationsJsonSet(SharedPreferences sharedPreferences) { + Set jsonNotifications = sharedPreferences.getStringSet(SCHEDULED_NOTIFICATIONS_SET, null); + if (jsonNotifications != null) { + return jsonNotifications; + } + String notificationsJson = sharedPreferences.getString(SCHEDULED_NOTIFICATIONS_STRING, null); + if (notificationsJson == null) { + return null; + } + + // Convert (once) from the old format to the new format and delete the old + Gson gson = buildGson(); + Type type = new TypeToken>() {}.getType(); + ArrayList notificationsList = gson.fromJson(notificationsJson, type); + int amount = notificationsList.size(); + String[] jsonNotificationsToSave = new String[amount]; + for (int i = 0; i < amount; i++) { + jsonNotificationsToSave[i] = gson.toJson(notificationsList.get(i)); + if (i == 0) { + logLongJson(jsonNotificationsToSave[i]); + } + } + Set scheduledNotifications = Set.of(jsonNotificationsToSave); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.remove(SCHEDULED_NOTIFICATIONS_STRING); + editor.putStringSet(SCHEDULED_NOTIFICATIONS_SET, scheduledNotifications); + editor.apply(); + return scheduledNotifications; + } + + private static void logLongJson(String json) { + String TAG = "json_example"; + if (json.length() > 4000) { + Log.v(TAG, "sb.length = " + json.length()); + int chunkCount = json.length() / 4000; // integer division + for (int i = 0; i <= chunkCount; i++) { + int max = 4000 * (i + 1); + if (max >= json.length()) { + Log.v(TAG, json.substring(4000 * i)); + } else { + Log.v(TAG, json.substring(4000 * i, max)); + } + } + } else { + Log.v(TAG, json); + } + } + private static void saveScheduledNotifications( Context context, ArrayList scheduledNotifications) { Gson gson = buildGson(); int amount = scheduledNotifications.size(); - String[] jsons = new String[amount]; + String[] jsonNotificationsToSave = new String[amount]; for (int i = 0; i < amount; i++) { - jsons[i] = gson.toJson(scheduledNotifications.get(i)); + jsonNotificationsToSave[i] = gson.toJson(scheduledNotifications.get(i)); } SharedPreferences sharedPreferences = - context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_SET, Context.MODE_PRIVATE); + context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_FILE, Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putStringSet(SCHEDULED_NOTIFICATIONS_SET, Set.of(jsons)); + editor.putStringSet(SCHEDULED_NOTIFICATIONS_SET, Set.of(jsonNotificationsToSave)); editor.apply(); } @@ -616,17 +671,23 @@ private static long calculateRepeatIntervalMilliseconds(NotificationDetails noti return repeatInterval; } - // Don't json parse the whole list, but this will allow duplicate notifications with the same id + // Note: This will now allow duplicate notifications on ID which would be overwritten before. private static void saveScheduledNotification( Context context, NotificationDetails notificationDetails) { SharedPreferences sharedPreferences = - context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_SET, Context.MODE_PRIVATE); - Set jsons = sharedPreferences.getStringSet(SCHEDULED_NOTIFICATIONS_SET, new HashSet()); - Set newSet = new HashSet(jsons); + context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_FILE, Context.MODE_PRIVATE); + Set jsonNotifications = getScheduledNotificationsJsonSet(sharedPreferences); + if (jsonNotifications == null) { + jsonNotifications = new HashSet(); + } + + // Mutations on the Set from getStringSet() are not allowed! + Set jsonNotificationsToSave = new HashSet(jsonNotifications); + Gson gson = buildGson(); - newSet.add(gson.toJson(notificationDetails)); + jsonNotificationsToSave.add(gson.toJson(notificationDetails)); SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putStringSet(SCHEDULED_NOTIFICATIONS_SET, newSet); + editor.putStringSet(SCHEDULED_NOTIFICATIONS_SET, jsonNotificationsToSave); editor.apply(); } @@ -1626,7 +1687,7 @@ private void cancelAllNotifications(Result result) { } SharedPreferences sharedPreferences = - applicationContext.getSharedPreferences(SCHEDULED_NOTIFICATIONS_SET, Context.MODE_PRIVATE); + applicationContext.getSharedPreferences(SCHEDULED_NOTIFICATIONS_FILE, Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.remove(SCHEDULED_NOTIFICATIONS_SET); editor.apply(); From c5c5e76d4b1666648d85fb8160b922b05c7e74ce Mon Sep 17 00:00:00 2001 From: Jeroen van de Ven Date: Tue, 11 Jan 2022 11:09:33 +0100 Subject: [PATCH 6/6] Remove log of example json --- .../FlutterLocalNotificationsPlugin.java | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java index f13b65428..dcb2a66c8 100644 --- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java +++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java @@ -26,7 +26,6 @@ import android.text.Spanned; import android.text.TextUtils; import android.text.style.ForegroundColorSpan; -import android.util.Log; import androidx.annotation.Keep; import androidx.annotation.NonNull; import androidx.core.app.AlarmManagerCompat; @@ -433,9 +432,6 @@ private static Set getScheduledNotificationsJsonSet(SharedPreferences sh String[] jsonNotificationsToSave = new String[amount]; for (int i = 0; i < amount; i++) { jsonNotificationsToSave[i] = gson.toJson(notificationsList.get(i)); - if (i == 0) { - logLongJson(jsonNotificationsToSave[i]); - } } Set scheduledNotifications = Set.of(jsonNotificationsToSave); SharedPreferences.Editor editor = sharedPreferences.edit(); @@ -445,24 +441,6 @@ private static Set getScheduledNotificationsJsonSet(SharedPreferences sh return scheduledNotifications; } - private static void logLongJson(String json) { - String TAG = "json_example"; - if (json.length() > 4000) { - Log.v(TAG, "sb.length = " + json.length()); - int chunkCount = json.length() / 4000; // integer division - for (int i = 0; i <= chunkCount; i++) { - int max = 4000 * (i + 1); - if (max >= json.length()) { - Log.v(TAG, json.substring(4000 * i)); - } else { - Log.v(TAG, json.substring(4000 * i, max)); - } - } - } else { - Log.v(TAG, json); - } - } - private static void saveScheduledNotifications( Context context, ArrayList scheduledNotifications) { Gson gson = buildGson();