I want to set notification for every day, at user-selected hours and minutes, but the issue is that some times it gets notification on time and some time it delays with 10, 20, 30, 40 seconds delay, and also check that today time is passed or not; if it is, I don’t want to show notification
Function to set Notification
private fun scheduleNotification(context: Context, hour: Int, minute: Int) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, AlarmReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val calendar = Calendar.getInstance().apply {
timeInMillis = System.currentTimeMillis()
set(Calendar.HOUR_OF_DAY, hour)
set(Calendar.MINUTE, minute)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
if (before(Calendar.getInstance())) {
add(Calendar.DATE, 1)
}
}
alarmManager.cancel(pendingIntent)
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent
)
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
AlarmManager.INTERVAL_DAY,
pendingIntent
)
}
private fun createNotificationChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = "Workout Reminder Channel"
val descriptionText = "Channel for workout reminders"
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
description = descriptionText
}
val notificationManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
companion object {
const val CHANNEL_ID = "workout_reminder_channel"
}
BroadcastReceiver
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val today = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
LocalDate.now()
} else {
TODO("VERSION.SDK_INT < O")
}
val mainIntent = Intent(context, MainActivity::class.java)
mainIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
val pendingIntent = PendingIntent.getActivity(
context,
0,
mainIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notification = NotificationCompat.Builder(context, ProfileViewModel.CHANNEL_ID)
.setSmallIcon(R.drawable.img_fitness1)
.setContentTitle("Workout Reminder")
.setContentText("It's time for your workout! Time : ${formatDate(today)}")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build()
notificationManager.notify(NOTIFICATION_ID, notification)
}
companion object {
const val NOTIFICATION_ID = 1
}
}
Please have a look at the official documentation about scheduling alarms.
In Android, you have two types of alarms:
- exact alarms: These are aiming to be delivered at the specified time
- inexact alarms: These will be delivered approximately at the specified time (~ within one hour).
If we inspect the documentation of setRepeated
, we find the following piece of information:
Note: as of API 19, all repeating alarms are inexact. If your application needs precise delivery times then it must use one-time exact alarms, rescheduling each time as described above. Legacy applications whose targetSdkVersion is earlier than API 19 will continue to have all of their alarms, including repeating alarms, treated as exact.
So depending on which device APIs you want to support, you will need to use a two-layered approach:
- On devices with API < 19, you can use
setRepeated
, and it will be exact. - On devices with API >= 19, you will have to use
setExact()
(respects battery saving mode),setExactAndAllowWhileIdle()
(ignores battery saving mode), orsetAlarmClock()
(highest priority).
In your case, if the notification should appear at a user-sepcified time, you might want to use setAlarmClock
. You then however need to schedule each next alarm in your code.
See also this stackoverflow question for reference.
Use AlarmManager.setExactAndAllowWhileIdle
to ensure the alarm is delivered at the exact time, even if the device is in doze mode.
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent
)
Ensure you pass the hour and minute extras with the intent so from that you can check time is passed or not
val intent = Intent(context, AlarmReceiver::class.java).apply {
// you can also parse time in long format
putExtra("hour", hour)
putExtra("minute", minute)
}