Search code examples
javaandroidnotificationsalarmrecurring

Android Java Recurring notifications best practice


The feature I want to achieve is basically that I want my app to send 1-3 notifcations each day, every day, at approximate times. For example, one notification between 09:00-11:00, one between 13:00-18:00 and one between 20:00 and 22:00.

I started off by trying to use setRepeating from alarmManager, I didn't get this to work at all, maybe I used it wrong. I then moved to a solution to schedule a new notification every time the app is started, or each time a notification is received.

It works somewhat, since the otification is schedule during onCreate the notifications are sent and received ok as long as I sometimes open the app. And some of them are re-scheduled properly even without opening the app. But it seems like they "disappear" after a while of not opening the app, as if the notifications stops getting sent. I have tried removed battery optimization, didn't resolve the issue.

Are there some best practices for handling recurring notifications? I know some apps have issues with notifications not being send, for example Telegram on my phone. But some are working flawlessly, for example Duolingo.

Some of the code:

In the onCreate(), I check permission (or ask permission) and then call the schedule function:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
    if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.POST_NOTIFICATIONS}, PERMISSION_REQUEST_CODE);
    } else {
        Notification.scheduleNotification(context);
    }
} else {
    Notification.scheduleNotification(context);
}


The function that schedules the notification:

private static final int[] notificationHours = {8,15,20,22};

public static int nextAlarmHour(int currentHour) {
    // Find the next alarm hour that is greater than the current hour
    for (int hour : notificationHours) {
        if (hour > currentHour) {
            return hour;
         }
    }

    // If no alarm hour is found greater than the current hour,
    // return the first alarm hour in the array (i.e., the next day)
    return notificationHours[0];
}


public static void scheduleNotification() {
    context = MainActivity.getContext();

    SharedPreferences preferences = context.getSharedPreferences(context.getString(R.string.app_name), Context.MODE_PRIVATE);
    int lastScheduledHour = preferences.getInt("lastScheduledHour", -1);

    Calendar calendar = Calendar.getInstance();

    int currentHour = LocalTime.now().getHour();
    int hour = nextAlarmHour(currentHour);

    // Check if the next alarm hour is greater than or equal to the current hour
    if (hour <= currentHour) {
        // Increment the calendar day to the next day
        calendar.add(Calendar.DAY_OF_MONTH, 1);
    }

    // Check if the hour is the same as the last scheduled hour
    if (hour == lastScheduledHour) {
        // No need to schedule a duplicate alarm
        return;
    }
    calendar.set(Calendar.HOUR_OF_DAY, hour);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);

    PendingIntent pendingIntent = createNotificationIntent(context);
    AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

    if (alarmManager != null) {
        alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
    }
}


Creating notification channel


private static void createNotificationChannel(Context context) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "channelName", NotificationManager.IMPORTANCE_DEFAULT);
        NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
        notificationManager.createNotificationChannel(channel);
    }
}


Receive notification, and re-schedule

public static class MyNotificationReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        createNotificationChannel(context);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
            .setSmallIcon(R.mipmap.xx)
            .setContentTitle(context.getString(R.string.app_name))
            .setContentText(<text>)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setAutoCancel(true)
            .setContentIntent(createStartIntent(context));

        NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
        try {
            notificationManager.notify(NOTIFICATION_ID, builder.build());
        } catch (SecurityException ignore) {
        }
        scheduleNotification(context);
    }
}

Solution

  • You can use WorkManager's periodic work requests to get around using AlarmManager. It works similarly, but instead of using exact time like AlarmManager, it schedules work by intervals. You can calculate the interval between a specific moment and time of first notification, then fire the worker to start at that interval and repeat at daily intervals.

    For example suppose you schedule a periodicworkrequest at 8 AM. You set initial delay to 1hr, with daily intervals. WorkManager isn't exact, so it will start the work a little after 1hr(after 9 AM). You don't need to reschedule them every time unless you want to change intervals, and you can have multiple periodic works running in unison.