Adding multiple reminders causes them to trigger at the same time - android

I include here the full problem description because I'm not exactly sure if the logic behind the solution is even correct, but I am pretty sure that it has to do with the way I'm setting the alarms themselves that's causing this inaccuracy, or just sometimes pure fault (alarms don't trigger at all).
A user can add a new medication from a list of medications.
Screen 1
When a certain medication is found, clicking on it will show this screen http://imgur.com/nLC9gTG
That screen contains the Medication's name and under the "Posology" title (the green bar) is where the reminders for that Medication can be added.
Forget the "Units" field.
The "Frequency" field accepts a number and the label to the right of the "Frequency" field is clickable, it causes a dropdown menu to appear, from which the user can select "times for day" or "times per week".
The "Days of week" label (the label is empty in the screenshot) is also clickable, it presents the user with a dropdown menu from which the user can select multiple days from the days of a week.
The "Treatment Duration" field accepts a number and the label to the right of the "Treatment Duration" field will reflect the user's choice of "Frequency" (if it's "times per week" then that label will say "weeks", if it's "times per month" then that label will say "months").
Screen 2
In this second screenshot http://imgur.com/AcUmlHH -- There is a Switch which allows the user to enable reminders for this Medication (item, instance, etc.) that he's attempting to add.
If the "Frequency" field above has a number greater than 0 (2, for example), then the reminders Switch will create a list of Reminder fields which it will show just underneath the "Get Notifications" green bar.
When the user finally presses on "Add Medication", a new Medication object will be created in the database, along with the "Frequency" (number of reminders) that the user has chosen to add for this Medication object.
Create a Medication table:
id
name
description
dosage
frequency
frequencyType
treatmentDuration
ForeignCollection<MedicationReminder>
ArrayList<DayChoice> (DayChoice is a class with "Day Name" and "Selected")
when
whenString
units
unitForm
remarks
remindersEnabled
Create a MedicationReminder table:
Medication (foreign key for the Medication table)
Calendar
int[] days_of_week
totalTimesToTrigger
Upon creating this new Medication object:
Medication medication = new Medication();
medication.setFrequency()
medication.setName().setDosage().setRemindersEnabled()....
assignForeignCollectionToParentObject(medication);
assignForeignCollectionToParentObject(Medication)
private void assignForeignCollectionToParentObject(Medication medicationObject) {
medicationDAO.assignEmptyForeignCollection(medicationObject, "medicationReminders");
MedicationRemindersRecyclerAdapter adapter =
(MedicationRemindersRecyclerAdapter) remindersRecyclerView.getAdapter();
//Clear previous reminders
medicationObject.getMedicationReminders().clear();
for (int i = 0; i < adapter.getItemCount(); i++) {
int realDaysSelected = 0;
MedicationReminder medReminder = adapter.getItem(i);
medReminder.setMedication(medicationObject);
medReminder.setDays_of_week(daysOfWeekArray);
//These days are populated when the user selected them from the "Days of Week" clickable label
for (int aDaysOfWeekArray : daysOfWeekArray) {
if (aDaysOfWeekArray != 0) realDaysSelected++;
}
medReminder.setTotalTimesToTrigger(
Integer.parseInt(treatmentDurationET.getText().toString()) * realDaysSelected);
medicationObject.getMedicationReminders().add(medReminder);
}
setupMedicationReminders(medicationObject.getMedicationReminders().iterator());
}
setupMedicationReminders()
public void setupMedicationReminders(Iterator<MedicationReminder> medicationRemindersIterator) {
PendingIntent pendingIntent;
AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
while (medicationRemindersIterator.hasNext()) {
MedicationReminder medReminder = medicationRemindersIterator.next();
for (int i = 0; i < medReminder.getDays_of_week().length; i++) {
int dayChosen = medReminder.getDays_of_week()[i];
if (dayChosen != 0) {
medReminder.getAlarmTime().setTimeInMillis(System.currentTimeMillis());
medReminder.getAlarmTime().set(Calendar.DAY_OF_WEEK, dayChosen);
Intent intent = new Intent(AddExistingMedicationActivity.this, AlarmReceiver.class);
intent.putExtra(Constants.EXTRAS_ALARM_TYPE, "medications");
intent.putExtra(Constants.EXTRAS_MEDICATION_REMINDER_ITEM, (Parcelable) medReminder);
pendingIntent = PendingIntent.getBroadcast(this, medReminder.getId(), intent,
PendingIntent.FLAG_UPDATE_CURRENT);
int ALARM_TYPE = AlarmManager.ELAPSED_REALTIME_WAKEUP;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
am.setExactAndAllowWhileIdle(ALARM_TYPE, medReminder.getAlarmTime().getTimeInMillis(),
pendingIntent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
am.setExact(ALARM_TYPE, medReminder.getAlarmTime().getTimeInMillis(), pendingIntent);
} else {
am.set(ALARM_TYPE, medReminder.getAlarmTime().getTimeInMillis(), pendingIntent);
}
}
}
}
}
The problem is when the medication reminders are added, they are always triggered shortly after being added, and all at the same time.
Say I select Frequency 2 for Saturday and Friday with a treatment duration of 1 week. This means that there will be a total of 4 reminders being added, 2 on Friday and 2 on Saturday.
When I do this, and it happens to be a Saturday, the alarms trigger at the same time together, for Saturday.
What's wrong?

When you do this:
medReminder.getAlarmTime().setTimeInMillis(System.currentTimeMillis());
medReminder.getAlarmTime().set(Calendar.DAY_OF_WEEK, dayChosen);
The results are unpredictable. If the current day is a Monday and you call set(Calendar.DAY_OF_WEEK) with Calendar.THURSDAY, should the date be changed to the previous Thursday? or the next Thursday? You don't know.
If your alarms are all going off immediately, this would indicate that changing the DAY_OF_WEEK is causing the calendar to go backwards instead of forwards. To verify that, after you set the DAY_OF_WEEK, call getTimeInMillis() and compare it against the current time. If it is smaller, then your calendar has gone back in time. To fix that, just add 7 days to the calendar.
Also, you are using this type of alarm: AlarmManager.ELAPSED_REALTIME_WAKEUP. This type takes a value that represents the amount of time that has passed since device boot.
However, you are using the RTC for the time value (ie: Calendar.getTimeInMillis()). These 2 values are incompatible. If you want to use the RTC, you need to use AlarmManager.RTC_WAKEUP.

The problem is when the medication reminders are added, they are always triggered shortly after being added, and all at the same time.
That's because that's what you asked for. You are always reading the current time and setting it to the reminder time:
medReminder.getAlarmTime().setTimeInMillis(System.currentTimeMillis());
You never read the time provided by the user in this screen, i.e. the Calendar field of your reminder is never set. Here is your code:
MedicationReminder medReminder = adapter.getItem(i);
medReminder.setMedication(medicationObject);
medReminder.setDays_of_week(daysOfWeekArray);
for (int aDaysOfWeekArray : daysOfWeekArray) {
if (aDaysOfWeekArray != 0) realDaysSelected++;
}
medReminder.setTotalTimesToTrigger(...);
You missed the line for actually setting the reminder time

This might not be answer to your question, but IMHO you should consider this.
I would argue that this is bad UX. User of this application could be confused about the times of reminders based only on time (without dates). So maybe it would be better to add full date next to each reminder that the user would be able to edit. When you generate them for the first time, you set them in right order.
Reminder 1: 2/2/2017 13:04
Reminder 2: 9/2/2017 13:04
Reminder 3: 16/2/2017 13:04
Reminder 4: 25/2/2017 13:04
This would also reflect real-world better. Lets consider this use case: user has his phone with him, but he forgot medication. He can't take it at right time, so he takes it some point later (maybe even tommorow). This will mess up the plan, but in this case he can edit the date to a date when he actually took his medication. Then you adjust the date and time on all reminders after the one he edited to follow the intervals based on times_per_week, times_per_month. He should not be able to change them to past date.
Reminder 1: 2/2/2017 13:04 // He took medication on time
Reminder 2: 9/2/2017 13:04 // He missed this one and changed to date bellow
10/2/2017 12:22 // This is when he actually took the medication
Reminder 3: 16/2/2017 13:04 // This is no longer valid
17/2/2017 12:22 // You adjust reminder to this date and time
Reminder 4: 25/2/2017 13:04 // This is no longer valid
26/2/2017 12:22 // You adjust reminder to this date and time
User must still be limited to a certain range of dates for each reminder. You can't let him pick just any date. This is based on his plan settings and the current date, but I don't want to get into that. It's a lot of work.
If you write these changes to your database there should be no data loss. You can later add a feature to your application: a report on how many times user was late vs how many times the user took the medication on time. It's a nice little feature for mobile application.

Related

Best way to reset a database value every week on specific day (Android - Room)

I'm working on an Android app that has a functionality that is weekly basis, that is, every day of the week the user has to mark as done the day. This value is a boolean on my database, that is initialized with false, and is set to true when the user clicks on the checkbox. Everything is working fine.
But my problem is that I need to "reset" this boolean value to false on all the seven days of the week every time a new week begins. I don't need to have records of the past weeks. All that matters is the actual week (Sunday to Saturday).
It's a very simple task, I only need to do this:
for(WeekDay day: dao.getWeekDays()){
day.setDone(false);
dao.updateWeekDay(day); //update the value in database
}
So, I did some research (I'm new to android) and find out that Android has different schedule services like JobScheduler or AlarmManager. My app is designed to Android 10+ (API 29+).
What do you think is the best solution for my problem?
It's a very simple task (it won't take too much battery, internet,...) and I need to do this in a specific day (Sunday) every week. Also, this task needs to be done as soon as it possible, even if the phone is turned off on Sunday. It doesn't need to be a background service, but I need to guarantee that when the user opens the app and it's a new week, that method needs to be call before, but only if it had not been call in the actual week before.
Anyone has ideas?
Ok, I think I found a simple solution for my problem, based on other similar answers I read. I just need to run these function every time the app starts. I didn't need to use any background service, like WorkManager.
I only need to store in SharedPreferences the last date when the system did a reset in the values. Then, every time I open the app, it checks if today is in a different week from the last reset day. If it's true, then I run that "for cycle" in the question and update the last reset day to today in the SharedPreferences. If it's false, I do nothing.
The method inSameCalendarWeek checks if the day is in the same week from the same year of today (Locale.US guarantees that a week starts on Sunday. But I could change that to Locale.getDefault() to be more flexible). Also, for example, if December 31 is in the same week of January 1, even if they are in different years, the method will return true.
private void checkAndResetDoneDays() {
long lastResetDay = settings.getLong(LAST_DAY_RESET, 0);
LocalDate date = Instant.ofEpochMilli(lastResetDay).atZone(ZoneId.systemDefault()).toLocalDate();
if (!inSameCalendarWeek(date)) {
resetDoneDays();
settings.edit()
.putLong(LAST_DAY_RESET, LocalDate.now(ZoneId.systemDefault()).atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli())
.commit();
}
}
public boolean inSameCalendarWeek(LocalDate firstDate) {
LocalDate secondDate = LocalDate.now(ZoneId.systemDefault());
// get a reference to the system of calendar weeks in US Locale (to guarantee that week starts on Sunday)
WeekFields weekFields = WeekFields.of(Locale.US);
// find out the calendar week for each of the dates
int firstDatesCalendarWeek = firstDate.get(weekFields.weekOfWeekBasedYear());
int secondDatesCalendarWeek = secondDate.get(weekFields.weekOfWeekBasedYear());
/*
* find out the week based year, too,
* two dates might be both in a calendar week number 1 for example,
* but in different years
*/
int firstWeekBasedYear = firstDate.get(weekFields.weekBasedYear());
int secondWeekBasedYear = secondDate.get(weekFields.weekBasedYear());
// return if they are equal or not
return firstDatesCalendarWeek == secondDatesCalendarWeek
&& firstWeekBasedYear == secondWeekBasedYear;
}

How to delete SharedPreferences automatically after one month?

I want to delete SharedPreferences, if they exist, after one month automatically. I could not find any solutions. Is this possible to make?
Thanks a lot.
It depends.
The easiest way is to delete it when the user starts the app.
When the apps is created, you check the SharedPreferences for the last updated time.
If it's null (the first time), you save the current time in milliseconds as a long.
If it's not null, you read it and compare it against the current time. If it less than a month, you do nothing. If it's more than a month, you clear the shared preferences and, after clearing it, insert the new time.
Something like:
long lastUpdate = sharedPreferences.getLong(LAST_UPDATE, -1);
if(lastUpdate == -1) {
//First time
sharedPreferences.edit().putLong(LAST_UPDATE, System.currentTimeMillis()).apply();
} else {
boolean isMoreThanAMonth = //Here you should do the math. it depends, you want to consider a month like 30 days, or you want to know if it was in another month... somehthing like that
if(isMoreThanAMonth) {
sharedPreferences.edit().clear().apply()
}
}
Of course, if you want to clear the SharedPreferences even if the user does not open the app you should use a Service. It's more complex and expensive for the OS, so you should try to go for the first one if it fits your requirement.
long installed = context
.getPackageManager()
.getPackageInfo(context.getPackag‌​eName(), 0)
.firstInstallTime
;
public long firstInstallTime
The time at which the app was first installed. Units are as per System.currentTimeMillis().
Now you can compare two date and get months diffrent by using GregorianCalendar
after you get one month different do as you want..clear sharedPrefrence.edit().clear().commit()
One possible way,
1. get the calendar instance.
2. Get maximum day of month.
3. Store in a var1 string in format of dd/mm/yyyy.
4. Get the current date from some calendar object and store in same way from point 3 but in var2.
5. Compare two strings.
6. If match then it will be last day of month and call delete() on your files.
Done.

How to display different text in a textview according to date in android app?

I am very new at coding and want to develop an android app that display new content according the day you are opening the app.
For example : 20 days to eat better
Day 1 (user open the app), then the message is "XXXXXX"
Day 2 (user open the app), the the message is "YYYYY"
Day 1 is a the day the user download the app.
I understood that I need to use "timestamp" as a string, but how can I do it ?
Thanks for your help !
I'm not quite sure about what you are trying to achieve. But based on what I understood. You just want to get the current day
Calendar calendar = Calendar.getInstance();// sets the current date
calendar.get(Calendar.DATE);// gets the current day returns an integer value
calendar.getTime();// gets the current date and returns a Date Object
You can start from here. The calendar class offers you different values for the current calendar date. I'm pretty sure all you need to know is available if you just tried to search the web. But to give you something to work on, experiment with the Calendar class for date manipulations.
EDIT:
If you want to display a certain string based on the date/day. What you can do is to create an array and user the date/day value to get the string.
String[] stringArray = {"Text1", "Text2", "Text3", "Text4", "Text5", "Text6", "Text7"};
String theStringYouWant = stringArray[calendar.get(Calendar.DATE)];
You can use this approach if you have short strings and limited record. But if you have tons of data, you might want to utilize a database to store your data, and use this approach.

Android calendar create kind of recursion

I have a problem with adding event to Google Calendar programmaticly.
Here is code:
private void addToGoogleCalendar(long beginTime) {
ContentValues event = new ContentValues();
Observable.from(dataKeeper.getCalendarIds().keySet()).filter(key -> dataKeeper.getCalendarIds().get(key).equals(defaultCalendarName.getText().toString())).forEach(key ->
event.put(CalendarContract.Reminders.CALENDAR_ID, Integer.parseInt(key))
);
event.put(CalendarContract.Reminders.TITLE, reminderName.getText().toString());
if (!TextUtils.isEmpty(commentEdittext.getText().toString()))
event.put(CalendarContract.Reminders.DESCRIPTION, commentEdittext.getText().toString());
event.put(CalendarContract.Reminders.EVENT_TIMEZONE, TimeZone.getDefault().getID());
event.put(CalendarContract.Reminders.DTSTART, beginTime);
event.put(CalendarContract.Reminders.DTEND, dataKeeper.getEnd());
event.put(CalendarContract.Reminders.EVENT_COLOR, getResources().getColor(R.color.primary));
// http://tools.ietf.org/html/rfc2445
if (dataKeeper.getPeriod() > 0 && dataKeeper.getPeriod() != 7)
event.put(CalendarContract.Reminders.RRULE, String.format("FREQ=DAILY;INTERVAL=%d;UNTIL=%s", dataKeeper.getPeriod() - 1,
new SimpleDateFormat("yyyyMMdd", Locale.getDefault()).format(dataKeeper.getEnd())));
else if (dataKeeper.getPeriod() == 7)
event.put(CalendarContract.Reminders.RRULE, String.format("FREQ=WEEKLY;INTERVAL=1;UNTIL=%s",
new SimpleDateFormat("yyyyMMdd", Locale.getDefault()).format(dataKeeper.getEnd())));
event.put(CalendarContract.Reminders.ALL_DAY, 1); // 0 for false, 1 for true
event.put(CalendarContract.Reminders.STATUS, CalendarContract.Events.STATUS_CONFIRMED);
String eventUriString = "content://com.android.calendar/events";
getContentResolver().insert(Uri.parse(eventUriString), event);
}
Inrerval = 1
So, when I add event, it adds as need, but in few seconds I see that event adds recursive. First day one time, next day two times and so on.
Method called only ones, so I have no idea WTF happens.
You've set the end of the master instance to the end of the recurrence rule.
The first instance has a duration of dataKeeper.getEnd() - beginTime. According to the RRULE this instance is recurs every day until dataKeeper.getEnd()
Now if dataKeeper.getEnd() is several days after beginTime it happens that although the fist instance is still ongoing on the second day (because it has a duration of multiple days), another instance starts on that day.
On the third day, the first two instances are still ongoing and the rule says another one should start.
So there is one instance added until the UNTIL date of the RRULE, after that the number of instances decreases by one each day.
To fix it make sure that the first instance is just a day long. Replace
event.put(CalendarContract.Reminders.DTEND, dataKeeper.getEnd());
by
event.put(CalendarContract.Events.DURATION, "P1D");
This sets the duration of each instance to 1 day. You should always use DURATION for recurring events anyway, see Events.
Btw, you should also use CalendarContract.Events instead of CalendarContract.Reminders. Although Reminders also implements EventsColumns it's meant for alarms. I guess it contains the event columns because it effectively reads from a join over both tables.

How to check if timepicker1 time is oldest than timepicker2 time?

i have to select a interval of time.
i have two timepickers on my app, i need to check if timepicker1 selected time is less than timepicker2 selected time. If not, i have to show a toast to told the user the error.
I also need to do this with two datepickers,not with times in that case, but with dates.
please can someone give me some code examples for do this?
You just need to compare the individual components of the date or time in the correct order. For time:
if (time1.getCurrentHour() < time2.getCurrentHour() || (time1.getCurrentHour() == time2.getCurrentHour() && time1.getCurrentMinute() < time1.getCurrentMinute())) {
//time 1 is earlier.
}
You might need to add in a bit of complexity depending on if you are showing 24 hour time or not.
For dates, its the same, just compare first the year then the month then the day.

Categories

Resources