Search code examples
androidandroid-intentbroadcastreceiverscheduled-tasksalarmmanager

Using an AlarmManager to run code every midnight in Android app


I saw this answer here https://stackoverflow.com/a/24198314/7767733 but could not get it working.

I have a function refreshStuff() which refresh the values of a Fragment. I call this function when the Fragment is being made, when the user updates the values, and during onResume(). I also want to force a refresh at midnight, which is relevant when the user is just looking at the screen and not doing anything.

But I am not sure how to set this up in my MyFragment class. I tried:

public class MyFragment extends Fragment {
    ...
    private AlarmManager alarmMgr;
    private PendingIntent alarmIntent;
    private Context context;
    ...

    //in onCreate method
    context = getActivity();
    alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(context, AlarmReceiver.class);
    alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

    //midnight I hope
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(System.currentTimeMillis()); //do I even need this?
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.set(Calendar.MINUTE, 0);

    alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
        AlarmManager.INTERVAL_DAY, alarmIntent);

And if I understand the answer, I have to add this method somewhere:

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

However I don't have AlarmReceiver anywhere and I don't know where to put onReceive() or what I have to define in the Manifest. I also don't know if I have to "turn it off" after I receive it? (do the "alarms" "stack" when they shouldn't?)

Otherwise is this the right way to do this so far?

Edit:

My attempt at implementing CommonsWare's answer:

public class MyFragment extends Fragment implements Runnable, SomeOtherListenersIUse, YetAnotherListenerEtc {

    private View viewForDisplayingTotals;

    //...rest of implementation omitted for brevity

    @Override
    public void run() {
        refreshValues();
    }

    @Override
    public void onStart() {
        super.onStart();
        refreshValues();
        viewForDisplayingTotals.postDelayed(this, HelperMethods.getMillisecondsTillNextMidnight());
    }

    @Override
    public void onStop() {
        super.onStop();
        viewForDisplayingTotals.removeCallbacks(this);
    }
}

My helper method:

public static long getMillisecondsTillNextMidnight() {
    Calendar c = Calendar.getInstance();
    c.add(Calendar.DAY_OF_MONTH, 1); //add a day first
    c.set(Calendar.HOUR_OF_DAY, 0); //then set the other fields to 0
    c.set(Calendar.MINUTE, 0);
    c.set(Calendar.SECOND, 0);
    c.set(Calendar.MILLISECOND, 0);
    return c.getTimeInMillis() - System.currentTimeMillis();
}

Solution

  • Get rid of all the AlarmManager stuff. That is for when you want to get control in the background at certain times, and in your case, this is only an issue when you are in the foreground.

    Step #1: Add implements Runnable to your MyFragment definition

    Step #2: That will require you to implement the run() method, and in there, call refreshStuff()

    Step #3: In onResume() (or, better, onStart()), when you are calling refreshStuff(), also call postDelayed(this, ...) on some handy View, where ... is the number of milliseconds between now and the next midnight

    Step #4: In onPause() (or, better, onStop()), call removeCallbacks(this) on that same View

    This will cause your run() method to be called around midnight, where you will call refreshStuff().

    //do I even need this?

    Yes. Note, though, that your calculation is for the past midnight and ignores the seconds. add() a day and consider using set() to zero out the seconds.