Daily notifications at a specific user-defined time

Asked

Viewed 1,549 times

7

I created a method to send a notification to the user with the name sendNotification() using NotificationCompat.Builder and NotificationManager.

I need this notification launched every day at 07:30AM, this time can be adjusted by the user, persisting this time using the class SharedPreference.

It seems to me that with the public class AlarmManager it is possible to perform this procedure, but I’m not sure if I have to create a service or if it itself would be the service itself.

According to that answer, the exact time, such as 07:30AM. So in my application I inserted this way:

calendar.set(Calendar.HOUR_OF_DAY, 7); 
calendar.set(Calendar.MINUTE, 30);
calendar.set(Calendar.SECOND, 0);

But I’ve run several tests, and you’re not notifying correctly at the specified time.

So as not to pollute too much here of code, I put what I did in Githubgist.

How this notification could be made daily at a specific time?

  • Explain what you mean by "it’s not working properly"

  • @ramaral is not launching the notification to the user.

  • The way it is, with calendar.add(Calendar.DAY_OF_MONTH, 1);, is only released the next day. Read the comments there on the other question.

  • @ramaral in any chance is it possible to launch type shortly? How would it be?

  • Take that line.

  • @Ramaral I took but it didn’t work.

  • The code was tested before answering that question. Don’t forget the permissions. If targetSdkVersion is 24 there are also changes with respect to ACTION_BOOT_COMPLETED. Testing with targetSdkVersion 21

  • @ramaral Does this permission enter the concept of permission in real time? I am in the market, soon arriving at home I take the test. Did you ever see my code? You saw something wrong?

  • No, you just have to take that line out. Pay attention to the notes and comments in the other reply.

  • @ramaral "No" to which question? I arrived home researched, really the RECEIVE_BOOT_COMPLETED does not enter the concept of Permission Runtime. I’ll do a little more digging to see what that might be. I read the comments there, saw that it worked for the boy, so it should work here for me too, considering that I didn’t change anything in the code beyond the time and the withdrawal of the line, as you can see on githubgist. But anyway, thank you for your attention and forgive the ignorance.

Show 5 more comments

1 answer

4


Using the code of BroadcastReceiver of response that refers and implementing what is referred to in the notes, will be like this:

public class StartUpBootReceiver extends BroadcastReceiver {

    private static String HOUR = "hour";
    private static String MINUTE = "minute";


    public static void setAlarm(Context context, int hour, int minute){
        SharedPreferences preferences =  PreferenceManager.getDefaultSharedPreferences(context);
        preferences.edit()
                .putInt(HOUR, hour)
                .putInt(MINUTE, minute)
                .apply();
        setAlarm(context);
    }


    @Override
    public void onReceive(Context context, Intent intent) {

        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
            setAlarm(context);
            Toast.makeText(context, "Alarm set", Toast.LENGTH_LONG).show();
        }

    }

    private static void setAlarm(Context context) {

        int hour = getHour(context);
        int minute = getMinute(context);

        if(hour == -1 || minute == -1){
            //nenhum horário definido
            return;
        }

        // Cria um Calendar para o horário estipulado
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, hour);
        calendar.set(Calendar.MINUTE, minute);

        //Se já passou
        if(isDateBeforeNow(calendar)){
            //adiciona um dia
            calendar.add(Calendar.DAY_OF_MONTH, 1);
        }


        //PendingIntent para lançar o serviço
        Intent serviceIntent = new Intent(context, BootService.class);
        PendingIntent pendingIntent = PendingIntent.getService(context, 0, serviceIntent, 0);

        AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
        //Cancela um possível alarme existente
        alarmManager.cancel(pendingIntent);

        //Alarme que se repete todos os dias a uma determinada hora
        alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP,
                calendar.getTimeInMillis(),
                AlarmManager.INTERVAL_DAY,
                pendingIntent);
    }

    private static int getHour(Context context){
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        return preferences.getInt(HOUR, -1);
    }
    private static int getMinute(Context context){
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        return preferences.getInt(MINUTE, -1);
    }

    private static boolean isDateBeforeNow(Calendar calendar){
        return calendar.getTimeInMillis() <= System.currentTimeMillis();
    }
}

Use the method

StartUpBootReceiver.setAlarm(context, hour, minute);

to set/change the alarm time.

If the device is switched off, the alarm will be re-recorded.

Declare the BroadcastReceiver in the Androidmanifest.xml

<receiver android:name="aSuaPackage.StartUpBootReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>  

Add the permission

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

Implement the service to launch the notification:

public class BootService extends IntentService {

    private PowerManager.WakeLock wakeLock;

    public BootService() {
        super("name");
    }

    @Override
    public void onCreate() {
        super.onCreate();

        PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
        wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK |
                PowerManager.ACQUIRE_CAUSES_WAKEUP |
                PowerManager.ON_AFTER_RELEASE, "BootService");
        wakeLock.acquire();
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {

        //Lance a notificação aqui.
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    if(wakeLock.isHeld()){
        //Verificou-se que o iluminar do ecrã
        //não acontecia devido ao WakeLock ser
        //rapidamente libertado(apesar de PowerManager.ON_AFTER_RELEASE !?).
        try {
            //Atrasa a libertação do WakeLock
            //de forma a permitir a iluminação do ecrâ.
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            wakeLock.release();
        }
    }
}

Declare it in the AndroidManifest.xml

<service android:name=".BootService"/>

and add the permission to get the Wake Lock

<uses-permission android:name="android.permission.WAKE_LOCK"/>

Note: The first pitch of a inexact Repeating Alarm, as pointed out in documentation, will never be before the indicated time, but may not occur during almost the entire interval after that time. If the repeat interval is long and the alarm is set to a time after a short time, the first release may only occur after the interval has elapsed.

An alternative is to use the method setRepeating() instead of setInexactRepeating(). However, from API 19 all "Repeating Alarm" are considered "inaccurate".

The solution is to set an alarm using the method set() and then add to the calendar the repeat interval and set another alarm using setInexactRepeating().

A possible implementation for INTERVAL_DAY it will be so:

public class StartUpBootReceiver extends BroadcastReceiver {

    private static int REPEATING_ID = 1001;
    private static int ON_TIME_ID = 1002;
    private static String HOUR = "hour";
    private static String MINUTE = "minute";


    public static void setAlarm(Context context, int hour, int minute){
        SharedPreferences preferences =  PreferenceManager.getDefaultSharedPreferences(context);
        preferences.edit()
                .putInt(HOUR, hour)
                .putInt(MINUTE, minute)
                .apply();
        setAlarm(context);
    }


    @Override
    public void onReceive(Context context, Intent intent) {

        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
            setAlarm(context);
            Toast.makeText(context, "Alarm set", Toast.LENGTH_LONG).show();
        }

    }

    private static void setAlarm(Context context) {

        int hour = getHour(context);
        int minute = getMinute(context);

        if(hour == -1 || minute == -1){
            //nenhum horário definido
            return;
        }
        //Cancela possiveis alarmes existentes
        cancelAlarm(context);

        Calendar calendar = getCalendar(hour, minute);

        //Se já passou
        if(isDateBeforeNow(calendar)){
            //adiciona um dia
            calendar.add(Calendar.DAY_OF_MONTH, 1);
        }else{
            //Alarme para o horário especificado
            setOneTimeAlarm(context, calendar);
            //adiciona um dia para repetir o alarme no dia seguinte
            calendar.add(Calendar.DAY_OF_MONTH, 1);
        }
        //Repete o alarme no dia seguinte
        setRepeatingAlarm(context, calendar);

    }

    private static void setRepeatingAlarm(Context context, Calendar calendar){

        PendingIntent pendingIntent = getPendingIntent(context, REPEATING_ID);

        AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);

        //Alarme que se repete todos os dias a uma determinada hora
        alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,
                calendar.getTimeInMillis(),
                AlarmManager.INTERVAL_DAY,
                pendingIntent);
    }

    private static void setOneTimeAlarm(Context context, Calendar calendar){

        PendingIntent pendingIntent= getPendingIntent(context, ON_TIME_ID);

        AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);

        //Alarme para o horário especificado
        alarmManager.set(AlarmManager.RTC_WAKEUP,
                         calendar.getTimeInMillis(),
                         pendingIntent);
    }

    private static void cancelAlarm(Context context){

        AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
        alarmManager.cancel(getPendingIntent(context, ON_TIME_ID));
        alarmManager.cancel(getPendingIntent(context, REPEATING_ID));

    }

    private static PendingIntent getPendingIntent(Context context, int id){
        //PendingIntent para lançar o serviço
        Intent serviceIntent = new Intent(context, BootService.class);
        return PendingIntent.getService(context, id, serviceIntent, 0);
    }

    private static Calendar getCalendar(int hour, int minute){

        // Cria um Calendar para o horário especificado
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(System.currentTimeMillis());
        calendar.set(Calendar.HOUR_OF_DAY, hour);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, 0);
        return calendar;
    }

    private static int getHour(Context context){
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        return preferences.getInt(HOUR, -1);
    }
    private static int getMinute(Context context){
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        return preferences.getInt(MINUTE, -1);
    }

    private static boolean isDateBeforeNow(Calendar calendar){
        return calendar.getTimeInMillis() <= System.currentTimeMillis();
    }
}
  • Hello Ramaral, thank you for the reply! Seeing here what you did, I realized that you are different with what you told me to do before, which was to use Intentservice by putting the notification on onHandleIntent. Enfin, I don’t know if it has anything to do with time zones or the minimum time frame. I’m putting for example some 3 minutes after compilation and I’m waiting and no notification happens, in this case yours, no Toast is fired.

  • In my head it works, but maybe I’m having trouble with the schedule. For example now it’s 23:13 here at PT-BR. so in 3 minutes I’ll do: calendar.set(Calendar.HOUR_OF_DAY, 23);&#xA; calendar.set(Calendar.MINUTE, 16); right?

  • The reason you’re not getting tested has to do with the fact that the repetition period is AlarmManager.INTERVAL_DAY. In the documentation says the following "Your Alarm’s first Trigger will not be before the requested time, but it Might not occur for Almost a full interval after that time." The first release the alarm will never be before the indicated time, but may not occur during almost the entire interval, after that time".

  • To test, replace AlarmManager.INTERVAL_DAY for 30*1000. I don’t know why but with Intentservice it doesn’t work.

  • 1

    The Intentservice works, the problem is that I was using a Toast to be notified, which evidently does not work in an Intentservice. I will edit the answer to use Intentservice and add a Wakelock to "wake up" the device.

  • Cool Ramaral, I left to see this today, but it seems that already works well. I used the setAlarm() and it already worked immediately, I don’t know yet 24. But I’ll validate now because you’ve already wasted enough time with me. I’ll let you know. Ah, there’s a detail, the PowerManager.FULL_WAKE_LOCK is deprecated, and I know it probably won’t give any error, but you know another method to replace it?

  • 1

    Yes I know, Powermanager.FULL_WAKE_LOCK is deprecated. The problem is that there is no other way (I know) to "wake up" the device in case the service serves to launch a notification.

Show 2 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.