Search code examples
androidalarmmanager

Triggering an alarm at a specific time every day by using setExact()


I am trying to trigger an alarm once at a specific time everyday. But like all others I am using setExact() instead of setRepeating(). Alarm is firing at the correct time. But once it fires it keeps on repeating itself after every 5sec. How can I trigger it only once in a day at a specific time? Here is my code:

MainActivity.java:

public class MainActivity extends AppCompatActivity implements Observer {

    private AlarmManager alarmManager = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        BroadcastObserver.getInstance().addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        Log.e("MainActivity", "Alarm set through observer");
        cancelAlarm();
        setAlarm();
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (!checkAlarm()) {
            setAlarm();
        }

    }

    public void setAlarm() {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, 14);
        calendar.set(Calendar.MINUTE, 13);
        calendar.set(Calendar.SECOND, 0);
        alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(this, MyReceiver.class);
        intent.setAction(MyReceiver.ACTION_RECEIVER);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1001, intent, PendingIntent.FLAG_CANCEL_CURRENT);
        alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
    }

    private boolean checkAlarm() {
        Intent intent = new Intent(this, MyReceiver.class);
        intent.setAction(MyReceiver.ACTION_RECEIVER);
        boolean isSet = PendingIntent.getBroadcast(this, 1001, intent, PendingIntent.FLAG_NO_CREATE) != null;
        Log.e("MainActivity", isSet + " :Alarm is set");
        return isSet;
    }

    @Override
    protected void onStop() {
        super.onStop();
        cancelAlarm();
    }

    private void cancelAlarm() {
        Intent intent = new Intent(this, MyReceiver.class);
        intent.setAction(MyReceiver.ACTION_RECEIVER);
        final PendingIntent pendingIntent =
                PendingIntent.getBroadcast(MainActivity.this, 1001, intent,
                        PendingIntent.FLAG_NO_CREATE);
        if (pendingIntent != null) {
            alarmManager.cancel(pendingIntent);
            pendingIntent.cancel();
        }
    }
}

BroadcastReceiver:

public class MyReceiver extends BroadcastReceiver {

    public static final String ACTION_RECEIVER = "Receiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.e("MainActivity", "triggered");
        BroadcastObserver.getInstance().updateValue(intent);
    }
}

BroadcastObserver:

public class BroadcastObserver extends Observable {
    private static BroadcastObserver instance = new BroadcastObserver();

    public static BroadcastObserver getInstance(){
        return instance;
    }

    private BroadcastObserver(){}

    public void updateValue(Object data) {
        synchronized (this) {
            setChanged();
            notifyObservers(data);
        }
    }
}

Solution

  • once it fires it keeps on repeating itself after every 5 sec

    You are setting an alarm for 14:13 for a given day. When the alarm fires the Observer is notified and it sets the exact same alarm (for 14:13).

    At this point we're past 14:13, so the alarm will fire immediately, notifying the Observer again, resulting in an infinite loop of the above steps.

    The simplest solution might be to check the time when setting the alarm and if it's in the past, then add a day to it:

    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.HOUR_OF_DAY, 14);
    calendar.set(Calendar.MINUTE, 13);
    calendar.set(Calendar.SECOND, 0);
    
    if (calendar.before(Calendar.getInstance())) {
        calendar.add(Calendar.DAY_OF_YEAR, 1);
    }
    

    To avoid memory leaks you need to remove the Observer when appropriate:

    @Override
    protected void onDestroy() {
        BroadcastObserver.getInstance().deleteObserver(this);
        super.onDestroy();
    }
    

    Also, cancelling the alarm in update() is redundant. This is not a repeating alarm, so it makes no sense to cancel it after it fired.