Search code examples
androidkotlin

Delay in Notification Android Kotlin


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
    }
}

Solution

  • 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), or setAlarmClock() (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.