-
Notifications
You must be signed in to change notification settings - Fork 0
[Android] Use StringSet in SharedPreferences #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 10.0.0-medapp
Are you sure you want to change the base?
Changes from all commits
c81ce8b
c9c07a4
2b16361
4302ce1
e358b3b
c5c5e76
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -84,9 +84,11 @@ | |
| 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; | ||
| import java.util.Set; | ||
|
|
||
| /** FlutterLocalNotificationsPlugin */ | ||
| @Keep | ||
|
|
@@ -99,7 +101,9 @@ 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"; | ||
| 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"; | ||
| private static final String ARE_NOTIFICATIONS_ENABLED_METHOD = "areNotificationsEnabled"; | ||
|
|
@@ -392,24 +396,63 @@ static Gson buildGson() { | |
| private static ArrayList<NotificationDetails> loadScheduledNotifications(Context context) { | ||
| ArrayList<NotificationDetails> 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_FILE, Context.MODE_PRIVATE); | ||
| Set<String> jsonNotifications = getScheduledNotificationsJsonSet(sharedPreferences); | ||
| if (jsonNotifications != null) { | ||
| Gson gson = buildGson(); | ||
| Type type = new TypeToken<ArrayList<NotificationDetails>>() {}.getType(); | ||
| scheduledNotifications = gson.fromJson(json, type); | ||
| Type type = new TypeToken<NotificationDetails>() {}.getType(); | ||
| 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<String> getScheduledNotificationsJsonSet(SharedPreferences sharedPreferences) { | ||
| Set<String> 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<ArrayList<NotificationDetails>>() {}.getType(); | ||
| ArrayList<NotificationDetails> 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)); | ||
| } | ||
| Set<String> 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 saveScheduledNotifications( | ||
| Context context, ArrayList<NotificationDetails> scheduledNotifications) { | ||
| Gson gson = buildGson(); | ||
| String json = gson.toJson(scheduledNotifications); | ||
| int amount = scheduledNotifications.size(); | ||
| String[] jsonNotificationsToSave = new String[amount]; | ||
| for (int i = 0; i < amount; i++) { | ||
| jsonNotificationsToSave[i] = gson.toJson(scheduledNotifications.get(i)); | ||
| } | ||
| SharedPreferences sharedPreferences = | ||
| context.getSharedPreferences(SCHEDULED_NOTIFICATIONS, Context.MODE_PRIVATE); | ||
| context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_FILE, Context.MODE_PRIVATE); | ||
| SharedPreferences.Editor editor = sharedPreferences.edit(); | ||
| editor.putString(SCHEDULED_NOTIFICATIONS, json); | ||
| editor.putStringSet(SCHEDULED_NOTIFICATIONS_SET, Set.of(jsonNotificationsToSave)); | ||
| editor.apply(); | ||
| } | ||
|
|
||
|
|
@@ -450,11 +493,8 @@ private static void scheduleNotification( | |
|
|
||
| AlarmManager alarmManager = getAlarmManager(context); | ||
| if (BooleanUtils.getValue(notificationDetails.allowWhileIdle)) { | ||
| AlarmManagerCompat.setExactAndAllowWhileIdle( | ||
| alarmManager, | ||
| AlarmManager.RTC_WAKEUP, | ||
| notificationDetails.millisecondsSinceEpoch, | ||
| pendingIntent); | ||
| AlarmManagerCompat.setAlarmClock( | ||
| alarmManager, notificationDetails.millisecondsSinceEpoch, pendingIntent, pendingIntent); | ||
| } else { | ||
| AlarmManagerCompat.setExact( | ||
| alarmManager, | ||
|
|
@@ -492,8 +532,7 @@ 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 +554,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 +607,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); | ||
|
|
@@ -610,18 +649,24 @@ private static long calculateRepeatIntervalMilliseconds(NotificationDetails noti | |
| return repeatInterval; | ||
| } | ||
|
|
||
| // Note: This will now allow duplicate notifications on ID which would be overwritten before. | ||
| private static void saveScheduledNotification( | ||
| Context context, NotificationDetails notificationDetails) { | ||
| ArrayList<NotificationDetails> scheduledNotifications = loadScheduledNotifications(context); | ||
| ArrayList<NotificationDetails> scheduledNotificationsToSave = new ArrayList<>(); | ||
| for (NotificationDetails scheduledNotification : scheduledNotifications) { | ||
| if (scheduledNotification.id.equals(notificationDetails.id)) { | ||
| continue; | ||
| } | ||
| scheduledNotificationsToSave.add(scheduledNotification); | ||
| SharedPreferences sharedPreferences = | ||
| context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_FILE, Context.MODE_PRIVATE); | ||
| Set<String> jsonNotifications = getScheduledNotificationsJsonSet(sharedPreferences); | ||
| if (jsonNotifications == null) { | ||
| jsonNotifications = new HashSet<String>(); | ||
| } | ||
| scheduledNotificationsToSave.add(notificationDetails); | ||
| saveScheduledNotifications(context, scheduledNotificationsToSave); | ||
|
|
||
| // Mutations on the Set from getStringSet() are not allowed! | ||
| Set<String> jsonNotificationsToSave = new HashSet<String>(jsonNotifications); | ||
|
|
||
| Gson gson = buildGson(); | ||
| jsonNotificationsToSave.add(gson.toJson(notificationDetails)); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. notifications should have unique ids not unique json strings
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I ignored this explicitly, although we can solve this by using a HashMap instead of ArrayList in |
||
| SharedPreferences.Editor editor = sharedPreferences.edit(); | ||
| editor.putStringSet(SCHEDULED_NOTIFICATIONS_SET, jsonNotificationsToSave); | ||
| editor.apply(); | ||
| } | ||
|
|
||
| private static int getDrawableResourceId(Context context, String name) { | ||
|
|
@@ -1619,7 +1664,11 @@ private void cancelAllNotifications(Result result) { | |
| alarmManager.cancel(pendingIntent); | ||
| } | ||
|
|
||
| saveScheduledNotifications(applicationContext, new ArrayList<>()); | ||
| SharedPreferences sharedPreferences = | ||
| applicationContext.getSharedPreferences(SCHEDULED_NOTIFICATIONS_FILE, Context.MODE_PRIVATE); | ||
| SharedPreferences.Editor editor = sharedPreferences.edit(); | ||
| editor.remove(SCHEDULED_NOTIFICATIONS_SET); | ||
| editor.apply(); | ||
| result.success(null); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check implies that we cannot switch back to the old implementation and again to this one, the migration will always happen only once
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is true, in an attempt to reduce the number of extra queries to Shared Preferences made during normal use.
When switching back to the old implementation, the notifications will be lost. When re-switching to the new implementation, the lost notifications will be restored and any new ones will be lost.
Preventing the initial loss is only possible by additionally saving the alarms the old way, which would defeat the purpose of the change.
Doing a second migration is only possible by always checking for presence of an old data object, which would incur a performance penalty each time.
My suggestion to solve the second migration problem is to make it the responsibility of the app and not the plugin. App developers can ensure notifications are explicitly re-scheduled by the app if such a rare situation occurs.