Kyle Banks

Creating a Countdown Label on Android

Written by @kylewbanks on Jul 16, 2016.

Given a future date, how can you update a TextView with a countdown timer to the date on Android? Pretty easily in fact, using a couple quantity strings, the TimeUnit class, and a Handler.

The basic idea is to calculate the time difference between now and the future date, format the difference in a readable form and display it on a TextView, and finally update it on a timed interval to keep it counting down.

First up, let’s define our strings. For this example, since we’ll be using the full names for each unit of time, we need to handle plural and singular units of each. For this, we use a quantity string to define the string to use in each case, in our usual strings.xml:

<plurals name="days">
    <item quantity="zero">%d Days</item>
    <item quantity="one">%d Day</item>
    <item quantity="other">%d Days</item>
</plurals>
<plurals name="hours">
    <item quantity="zero">%d Hours</item>
    <item quantity="one">%d Hour</item>
    <item quantity="other">%d Hours</item>
</plurals>
<plurals name="minutes">
    <item quantity="zero">%d Minutes</item>
    <item quantity="one">%d Minute</item>
    <item quantity="other">%d Minutes</item>
</plurals>
<plurals name="seconds">
    <item quantity="zero">%d Seconds</item>
    <item quantity="one">%d Second</item>
    <item quantity="other">%d Seconds</item>
</plurals>

We’ll see how these are used in a moment.

Next we’ll need a function that takes a future date and compares it to the current time. Then, it steps through and adds each unit of time that’s applicable. For instance, if the future date is over two days away, we’ll want a day, hour, minute, and second count. If the future date is 10 minutes away, we’ll only want a minute and second count.

Here’s how it works:

/**
 * Returns a formatted string containing the amount of time (days, hours,
 * minutes, seconds) between the current time and the specified future date.
 *
 * @param context
 * @param futureDate
 * @return
 */
public static CharSequence getCountdownText(Context context, Date futureDate) {
    StringBuilder countdownText = new StringBuilder();

    // Calculate the time between now and the future date.
    long timeRemaining = futureDate.getTime() - new Date().getTime();
    
    // If there is no time between (ie. the date is now or in the past), do nothing
    if (timeRemaining > 0) {
        Resources resources = context.getResources();

        // Calculate the days/hours/minutes/seconds within the time difference.
        //
        // It's important to subtract the value from the total time remaining after each is calculated. 
        // For example, if we didn't do this and the time was 25 hours from now, 
        // we would get `1 day, 25 hours`.
        int days = (int) TimeUnit.MILLISECONDS.toDays(timeRemaining);
        timeRemaining -= TimeUnit.DAYS.toMillis(days);
        int hours = (int) TimeUnit.MILLISECONDS.toHours(timeRemaining);
        timeRemaining -= TimeUnit.HOURS.toMillis(hours);
        int minutes = (int) TimeUnit.MILLISECONDS.toMinutes(timeRemaining);
        timeRemaining -= TimeUnit.MINUTES.toMillis(minutes);
        int seconds = (int) TimeUnit.MILLISECONDS.toSeconds(timeRemaining);

        // For each time unit, add the quantity string to the output, with a space.
        if (days > 0) {
            countdownText.append(resources.getQuantityString(R.plurals.days, days, days));
            countdownText.append(" ");
        }
        if (days > 0 || hours > 0) {
            countdownText.append(resources.getQuantityString(R.plurals.hours, hours, hours));
            countdownText.append(" ");
        }
        if (days > 0 || hours > 0 || minutes > 0) {
            countdownText.append(resources.getQuantityString(R.plurals.minutes, minutes, minutes));
            countdownText.append(" ");
        }
        if (days > 0 || hours > 0 || minutes > 0 || seconds > 0) {
            countdownText.append(resources.getQuantityString(R.plurals.seconds, seconds, seconds));
            countdownText.append(" ");
        }
    }

    return countdownText.toString();
}

Right, so now we can add a countdown timer to our TextView:

Date futureDate = ...;
TextView textView = ...;

textView.setText(getCountdownText(getContext(), futureDate);

And we’ll get something that looks like this:

Countdown Timer Example

But how do you make it update as the time difference changes? If a user is presented this view and it doesn’t actually count down, it’s not particularly useful, is it?

The solution is to use a Handler to update the TextView on a timed interval. In your Activity, add the following:

/**
 * The time (in ms) interval to update the countdown TextView.
 */
private static final int COUNTDOWN_UPDATE_INTERVAL = 500;

private Handler countdownHandler;

/**
 * Stops the  countdown timer.
 */
private void stopCountdown() {
    if (countdownHandler != null) {
        countdownHandler.removeCallbacks(updateCountdown);
        countdownHandler = null;
    }
}

/**
 * (Optionally stops) and starts the countdown timer.
 */
private void startCountdown() {
    stopCountdown();
    
    countdownHandler = new Handler();
    updateCountdown.run();
}

/**
 * Updates the countdown.
 */
private Runnable updateCountdown = new Runnable() {
    @Override
    public void run() {
        try {
            textView.setText(getCountdownText(getContext(), futureDate);
        } finally {
            countdownHandler.postDelayed(updateCountdown, COUNTDOWN_UPDATE_INTERVAL);
        }
    }
};

Here we have two methods, one to stop and one to start the countdown timer. We also define a Runnable which does the actual update. After each execution, it instructs our Handler to run again after a delay of COUNTDOWN_UPDATE_INTERVAL.

You may notice the value of COUNTDOWN_UPDATE_INTERVAL is 500 (milliseconds), and not one second. This may seem odd considering that the countdown display is only different after a full second has passed, so we technically waste one execution every second. However, I’ve found that if you set the update interval to one second, or even something closer to that, you’ll get skipped updates on slower devices. You see a countdown that looks like:

22 seconds
21 seconds 
19 seconds

Instead of:

22 seconds
21 seconds
20 seconds
19 seconds

I’ve found that 500 milliseconds gives the best balance of performance and UX for this reason.

Anyways, we have one last thing to do. Right now, if you use this code as-is, you’ll find that when leaving your Activity the timer is still running, which is less than ideal. The solution is simple though, and relies on the Activity Lifecycle:

@Override
public void onStart() {
    super.onStart();
    startCountdown();
}

@Override
public void onStop() {
    super.onStop();
    stopCountdown();
}

And that’s it, now when the Activity stops and starts, the countdown will stop and start with it.

Let me know if this post was helpful on Twitter @kylewbanks or down below!