I’m working on an Android app that handles medication reminders with notifications and alarms. The functionality is mostly working as expected:
I can add a medication.
The notifications are triggered and alarms work fine.
I can delete a medication and the notifications are canceled successfully.
However, I’m facing an issue when editing a medication. Here’s the behavior:
I add a medication and the notifications and alarms work as expected.
I edit the medication (update its details such as name, quantity, interval, etc.).
After editing, the notifications and alarms still work as they should.
When I attempt to delete the edited medication, the app crashes with a NullPointerException error stating that the PendingIntent is null.
The stack trace points to the following:
java.lang.NullPointerException: cancel() called with a null PendingIntent
at android.app.AlarmManager.cancel(AlarmManager.java:1366)
at com.example.medicamentoreminder.MedicationUtils.cancelAlarm(MedicationUtils.kt:75)
...
I’ve made sure that the unique ID for the medication remains unchanged during the edit process. The issue only occurs when I delete the edited medication, not when I delete the original one. Here’s the basic flow of the code:
Adding a medication:
A PendingIntent is created for scheduling the alarm.
The medication details are saved and alarms are set.
Editing a medication:
The original medication’s data is loaded into the edit fields.
When saving the edited medication, the PendingIntent is updated with the new data and the alarm is re-scheduled.
Deleting a medication:
I attempt to cancel the alarm and notification for the medication being deleted.
The cancelAlarm method is called, but it crashes with a NullPointerException.
Question: Why is the PendingIntent null when trying to delete the edited medication? Is there something I'm missing when updating or canceling the PendingIntent after editing?
Here is the code related to canceling the alarm:
fun cancelAlarm(context: Context, uniqueID: Int) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context.applicationContext, AlarmReceiver::class.java).apply {
action = "com.example.ALARM_ACTION" // Custom action to identify the intent
}
val pendingIntent = PendingIntent.getBroadcast(
context.applicationContext,
uniqueID,
intent,
PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE
)
alarmManager.cancel(pendingIntent)
}
And here’s the code that schedules the alarm after editing the medication:
fun scheduleAlarm(
context: Context,
medication: Medication,
alarmID: Int
) {
// Log para depurar
Log.d("MedicationUtils", "Configurando alarma con alarmID: $alarmID para ${medication.name}")
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context.applicationContext, AlarmReceiver::class.java).apply {
action = "com.example.ALARM_ACTION" // Custom action to identify the intent
putExtra("medName", medication.name)
putExtra("quantity", medication.quantity)
}
val pendingIntent = PendingIntent.getBroadcast(
context.applicationContext,
alarmID,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val triggerTime = System.currentTimeMillis() + (medication.intervalInMinutes * 60 * 1000)
val intervalMillis = medication.intervalInMinutes * 60 * 1000L
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP,
triggerTime,
intervalMillis,
pendingIntent
)
}
and this one is for edditing
private fun saveUpdatedMedication() {
if (medicationIndex != -1) {
// Primero, obtenemos el medicamento antiguo
val oldMedication = medications[medicationIndex]
MedicationUtils.cancelAlarm(this, oldMedication.uniqueID)
MedicationUtils.cancelNotification(this, oldMedication.uniqueID)
MedicationUtils.deleteMedication(this, medicationIndex, medications)
// Ahora, actualizamos el medicamento con los nuevos valores
val updatedMedication = Medication(
name = nameEditText.text.toString(),
quantity = quantityEditText.text.toString(),
interval = intervalEditText.text.toString(),
intervalInMinutes = MedicationUtils.parseIntervalToMinutes(intervalEditText.text.toString()),
medicationIndex = medicationIndex,
uniqueID = oldMedication.uniqueID
)
// Actualizamos el medicamento en la lista
medications[medicationIndex] = updatedMedication
// Guardamos los cambios en SharedPreferences
MedicationUtils.saveMedications(sharedPreferences, medications)
// Establecemos la nueva alarma para el medicamento editado
MedicationUtils.scheduleAlarm(this, updatedMedication, updatedMedication.uniqueID)
// Devolvemos los datos actualizados a MedicationDetailsActivity
val resultIntent = Intent(applicationContext, AlarmReceiver::class.java).apply {
action = "com.example.ALARM_ACTION"
putExtra("medName", updatedMedication.name)
putExtra("quantity", updatedMedication.quantity)
putExtra("interval", updatedMedication.interval)
putExtra("medicationIndex", medicationIndex)
putExtra("uniqueID", updatedMedication.uniqueID)
}
setResult(RESULT_OK, resultIntent)
finish() // Terminar la actividad después de guardar
}
}
Any suggestions or insights on why the PendingIntent becomes null after editing the medication would be greatly appreciated.
I've got two suggestions you can try to see if it works.
Make sure the intent is exactly the same when you try to cancelAlarm() as it was when you scheduledAlarm (). Add the same intent extras with empty values in cancelAlarm() to match the original intent.
When you cancelAlarm() instead of using intent FLAG_NO_CREATE use FLAG_CANCEL_CURRENT instead to cancel the existing alarm with the given intent. FLAG_NO_CREATE will see if there exists a PendingIntent with given intent(action and extras) and returns null if the match is not found.
Try using this code:
fun cancelAlarm(context: Context, uniqueID: Int) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context.applicationContext, AlarmReceiver::class.java).apply {
action = "com.example.ALARM_ACTION"
putExtra("medName", "")
putExtra("quantity", "")
}
// Using FLAG_CANCEL_CURRENT to ensure any previous intent instance is canceled
val pendingIntent = PendingIntent.getBroadcast(
context.applicationContext,
uniqueID,
intent,
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
if (pendingIntent != null) {
alarmManager.cancel(pendingIntent)
} else {
Log.w("MedicationUtils", "No PendingIntent found for uniqueID:
$uniqueID")
}
}
My Analysis why it might look like its working when you don't edit the medication:
When you call cancelAlarm() during editing to remove the original one, it should cancel the original PendingIntent if everything matches correctly. However, if the Intent used to retrieve the PendingIntent in cancelAlarm doesn’t perfectly match the one initially set, the cancellation can fail without an error, leaving an orphaned PendingIntent. This discrepancy would only surface when trying to delete the medication after editing, which explains why the NullPointerException arises at that point.