5

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?

Odaym
  • 1,777
  • 1
  • 18
  • 31

3 Answers3

3

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.

David Wasser
  • 85,616
  • 15
  • 182
  • 239
  • You are right about the setTimeInMillis(System.currentTimeInMillis) line but when I went to check it it seems that I have long since removed it while debugging, and yes it's definitely wrong to do that as it will overwrite any previously set time, the bounty expires in 40 minutes, I will check now and get back to this – Odaym Feb 02 '17 at 08:59
  • you are also right that specifying an arbitrary DAY_OF_WEEK will cause the calendar (before and after I checked the date of this calendar) to go back in time in fact. Now I am not going to resort to the method of adding 7 but I will avoid the Calendar altogether and use JodaTime's LocalDateTime so I won't run into this discrepancy, what do you think of that? – Odaym Feb 02 '17 at 17:50
  • What is the benefit of using JodaTime? How will you set the day of the week? There is nothing wrong with your code, you just need to account for the possibility that the Calendar will be set back instead of forwards. How will using JodaTime solve this problem? – David Wasser Feb 02 '17 at 17:55
  • I see, well I thought that setting the day with JodaTime won't result in such a mishap even happening first, because I don't know what's causing it to begin with, the arbitrary way of setting? How would I do it so as it won't be an arbitrary day of week and avoid it going back? – Odaym Feb 02 '17 at 18:15
  • You will have exactly the same problem using JodaTime, if you want to set a specific day of the week. Why don't you just use my suggestion? Otherwise you could get the current time, get the current day of the week, determine how many days are between the current day of the week and day of the week you want to set the alarm, then add that many days to the Calendar. This also works, but is more code. My original suggestion is more efficient and uses less code. – David Wasser Feb 02 '17 at 18:54
2

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

A.A.
  • 776
  • 1
  • 7
  • 22
  • I actually do the adding of the time back inside the adapter which serves the alarm rows, when each of the rows is clicked it prompts for a time picker, and from that picker I set the time and minute etc.. to the medication.setAlarmTime(), and back from the Activity i grab those objects and use them. – Odaym Feb 02 '17 at 08:58
0

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.

Djuka
  • 470
  • 5
  • 18