androidnotificationsnullpointerexceptionalarmmanagerandroid-pendingintent

App crashes with NullPointerException when deleting an edited medication with PendingIntent


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.


Solution

  • I've got two suggestions you can try to see if it works.

    1. 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.

    2. 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.