Android Daily Repeating Alarm/Reminder at Specific Time With AlarmManager

May 11, 2019

This example demonstrate daily alarm at specific time (e.g. 8.30am daily).

  • Read about Caveats of Repeating Alarm.
  • Use setExact / setWindow / set to start alarm at exact timing (or setExactAndAllowWhileIdle for extreme accuracy), then set alarm again at onReceive for the next day. Don’t use setInexactRepeating or setRepeating due to time drift and inability to guarantee execution at exact time.
class ReminderReceiver : BroadcastReceiver() {

    companion object {
        private const val REQUEST_TIMER1 = 1
        private const val PARAM_NAME = "name"

        fun startAlarm(context: Context, startMillis: Long? = null) {
            val pendingIntent = getIntent(context, REQUEST_TIMER1, "Alarm at 8.30am")
            val alarm = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager

            // trigger at 8:30am
            val alarmTime = LocalTime.of(8, 30)
            var now = LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES)
            val nowTime = now.toLocalTime()
            // if same time, schedule for next day as well
            // if today's time had passed, schedule for next day
            if (nowTime == alarmTime || nowTime.isAfter(alarmTime)) {
                now = now.plusDays(1)
            }
            now = now.withHour(alarmTime.hour).withMinute(alarmTime.minute) // .withSecond(alarmTime.second).withNano(alarmTime.nano)

            // alarm use UTC/GMT time
            val utc = now.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime()
            val startMillis = utc.atZone(ZoneOffset.UTC)!!.toInstant()!!.toEpochMilli()

            Timber.d("Alarm will trigger in ${(startMillis-System.currentTimeMillis())/1000}s")

            // AlarmManagerCompat.setExact(alarm, AlarmManager.RTC_WAKEUP, startMillis, pendingIntent!!)
            if (Build.VERSION.SDK_INT >= 19) {
                // alarm.setExact(AlarmManager.RTC_WAKEUP, startMillis, pendingIntent)

                // effort to save battery, allow deviation of 15 minutes
                val windowMillis = 15L * 60L * 1_000L
                alarm.setWindow(AlarmManager.RTC_WAKEUP, startMillis, windowMillis, pendingIntent)
            }
            else {
                alarm.set(AlarmManager.RTC_WAKEUP, startMillis, pendingIntent)
            }
        }

        fun cancelAlarm(context: Context) {
            val pendingIntent = getIntent(context, REQUEST_TIMER1)
            val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
            alarmManager.cancel(pendingIntent)
        }
    }


    override fun onReceive(context: Context, intent: Intent) {
        try {
            val name = intent.getStringExtra(PARAM_NAME)
            Timber.d("onReceive, name=$name")

            Toast.makeText(context, name, Toast.LENGTH_LONG).show()
        }
        catch (e: Exception) {
            Timber.e(e)
        }
        finally { // use try/catch to make sure next day alarm is set even if exception happens
            // start alarm for next day
            startAlarm(context)
        }
    }
}

Edit AndroidManifest.xml.

```xml
<manifest ..>
    <application ...>
        <receiver android:name=".ReminderReceiver" />
    </application>
</manifest>

Usage

ReminderReceiver.startAlarm(context)

NOTE: The AlarmManager won’t survive a device reboot. You have to implement a device bootup receiver and call ReminderReceiver.startAlarm(context).

NOTE: Refer Setup Android Notification.

NOTE: To support multiple alarm, use a different requestCode for each alarm. Refer Android AlarmManager: Multiple Alarm With Arguments/Parameters.

NOTE: AlarmManager is removed when the app is uninstalled, and it seems the Alarm is cancelled after APK update (not sure if this is true or always true).

You might want to explore Android Schedule Daily Repeating/Reminder Alarm at Specific Time With WorkManager.

References:

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.