Android: CountDownTimer skips last onTick()!
Asked Answered
P

12

52

Code:

public class SMH extends Activity {  

    public void onCreate(Bundle b) {  
        super.onCreate(b);  
        setContentView(R.layout.main);  

        TextView tv = (TextView) findViewById(R.id.tv);  

        new CountDownTimer(10000, 2000) {  
            public void onTick(long m) {  
               long sec = m/1000+1;  
               tv.append(sec+" seconds remain\n");  
            }  
            public void onFinish() {  
               tv.append("Done!");  
            }  
        }.start();  
   }

Output:
10 seconds remain
8 seconds remain
6 seconds remain
4 seconds remain
Done!

Problem:

How do I get it to show "2 seconds remain"? The time elapsed is indeed 10 seconds, but the last onTick() never happens. If I change the second parameter from 2000 to 1000, then this is the output:

10 seconds remain
9 seconds remain
8 seconds remain
7 seconds remain
6 seconds remain
5 seconds remain
4 seconds remain
3 seconds remain
2 seconds remain
Done!

So you see, it seems to be skipping that last onTick() call. And btw, the XML file is basically the default main.xml with the TextView assigned the id tv and the text set to "".

Pathan answered 13/1, 2012 at 21:39 Comment(2)
This bug is fixed in OreoHawk
In case anyone is interested in backwards compatibility, while the problem may have been resolved in Android 8 it is present in Android 7 and I haven't seen it work the way OP thinks it should in Android 5 or 6.Medford
G
23

I don't know why the last tick is not working but you can create your own timer with Runable , for example.

class MyCountDownTimer {
    private long millisInFuture;
    private long countDownInterval;
    public MyCountDownTimer(long pMillisInFuture, long pCountDownInterval) {
            this.millisInFuture = pMillisInFuture;
            this.countDownInterval = pCountDownInterval;
        }
    public void Start() 
    {
        final Handler handler = new Handler();
        Log.v("status", "starting");
        final Runnable counter = new Runnable(){

            public void run(){
                if(millisInFuture <= 0) {
                    Log.v("status", "done");
                } else {
                    long sec = millisInFuture/1000;
                    Log.v("status", Long.toString(sec) + " seconds remain");
                    millisInFuture -= countDownInterval;
                    handler.postDelayed(this, countDownInterval);
                }
            }
        };

        handler.postDelayed(counter, countDownInterval);
    }
}

and to start it,

new MyCountDownTimer(10000, 2000).Start();

EDIT FOR GOOFY'S QUESTION

you should have a variable to hold counter status (boolean) . then you can write a Stop() method like Start().

EDIT-2 FOR GOOFY'S QUESTION

actually there is no bug on stopping counter but there is a bug on start again after stop(resume).

I'm writing a new updated full code that I had just tried and it's working. It's a basic counter that show time on screen with start and stop button.

class for counter

public class MyCountDownTimer {
    private long millisInFuture;
    private long countDownInterval;
    private boolean status;
    public MyCountDownTimer(long pMillisInFuture, long pCountDownInterval) {
            this.millisInFuture = pMillisInFuture;
            this.countDownInterval = pCountDownInterval;
            status = false;
            Initialize();
    }

    public void Stop() {
        status = false;
    }

    public long getCurrentTime() {
        return millisInFuture;
    }

    public void Start() {
        status = true;
    }
    public void Initialize() 
    {
        final Handler handler = new Handler();
        Log.v("status", "starting");
        final Runnable counter = new Runnable(){

            public void run(){
                long sec = millisInFuture/1000;
                if(status) {
                    if(millisInFuture <= 0) {
                        Log.v("status", "done");
                    } else {
                        Log.v("status", Long.toString(sec) + " seconds remain");
                        millisInFuture -= countDownInterval;
                        handler.postDelayed(this, countDownInterval);
                    }
                } else {
                    Log.v("status", Long.toString(sec) + " seconds remain and timer has stopped!");
                    handler.postDelayed(this, countDownInterval);
                }
            }
        };

        handler.postDelayed(counter, countDownInterval);
    }
}

activity class

public class CounterActivity extends Activity {
    /** Called when the activity is first created. */
    TextView timeText;
    Button startBut;
    Button stopBut;
    MyCountDownTimer mycounter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        timeText = (TextView) findViewById(R.id.time);
        startBut = (Button) findViewById(R.id.start);
        stopBut = (Button) findViewById(R.id.stop);
        mycounter = new MyCountDownTimer(20000, 1000);
        RefreshTimer();
    }

    public void StartTimer(View v) {
        Log.v("startbutton", "saymaya basladi");
        mycounter.Start();
    }

    public void StopTimer(View v) {
        Log.v("stopbutton", "durdu");
        mycounter.Stop();
    }

    public void RefreshTimer() 
    {
        final Handler handler = new Handler();
        final Runnable counter = new Runnable(){

            public void run(){
                timeText.setText(Long.toString(mycounter.getCurrentTime()));
                handler.postDelayed(this, 100);
            }
        };

        handler.postDelayed(counter, 100);
    }
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:weightSum="1">
    <TextView android:textAppearance="?android:attr/textAppearanceLarge" 
              android:text="TextView" android:layout_height="wrap_content" 
              android:layout_width="wrap_content" 
              android:id="@+id/time">
    </TextView>
    <Button android:text="Start" 
            android:id="@+id/start" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:onClick="StartTimer">
    </Button>
    <Button android:text="Stop" 
            android:id="@+id/stop" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:onClick="StopTimer">
    </Button>
</LinearLayout>
Gallnut answered 13/1, 2012 at 23:24 Comment(13)
I figured I should just use a Handler to take care of it, but minus the flaw I'm talking about in my question, the CountDownTimer is so simple and perfect! I mean just from your code example and mine, you can see how simple and compact CountDownTimer is compared to a Handler! If only it actually worked right though lol.Pathan
absolutely you are right, I just want to show that you can do it with alternative methods.Gallnut
@ocanal thanks i am using your code but if i want to stop the timer how to do...please replyEulogist
@Eulogist look my edited answer again, I appended the code you want.Gallnut
@ocanal i found that the timer is not stopping it only shows the message can you please help me on thatEulogist
@ocanal i mean when i click on stop button it has to stop the counter which is running eg: when counter is 0:52 i click on stop the counter should stop at 0:52,but the counter is still running.Any idea on that???Eulogist
@Eulogist actually it's not running when you stop it, but there is a problem on resuming it. I appended a new full updated code to my answer, maybe it helps you.Gallnut
thanks man now i am able to pause and also resume it back again ...thanks a lot once again ... In the future can i contact you for any other queries???Eulogist
@ocanal how can i contact youEulogist
@Eulogist if you ask your question in stackoverflow, that will be better, because I can't answer every question you asked. but if you want to contact me, send me an email, it's in my profile page.Gallnut
How Could I Find that Count down has completed??????? and the start countdown again?Iguana
@ocanal you are awesome!! ..this is much better than using a CountdownTimer since there are many issues associated with it, being last onTick() miss and improper UI display of numerals.Surinam
What happens when I close the app in between ticks? Does it handle the cases where it returned back after the interval and then realized activity is already dead?Vitelline
S
57

I checked the source code of CountDownTimer. The "missing tick" comes from a special feature of CountDownTimer that I have not yet seen being documented elsewhere:

At the start of every tick, before onTick() is called, the remaining time until the end of the countdown is calculated. If this time is smaller than the countdown time interval, onTick is not called anymore. Instead only the next tick (where the onFinish() method will be called) is scheduled.

Given the fact that hardware clocks are not always super precise, that there may be other processes in the background that delay the thread running CountDownTimer plus that Android itself will probably create a small delay when calling the message handler of CountDownTimer it is more than likely that the call for the last tick before the end of the count down will be at least one millisecond late and therefore onTick() will not be called.

For my application I solved this problem simply by making the tick intervals "slightly" smaller (500 ms)

    myCountDownTimer = new CountDownTimer(countDownTime, intervalTime - 500) {
                                   ...
    }

and I could leave my code just as it is. For applications where the length of the interval time is critical, the other solutions posted here are probably the best.

Sauncho answered 5/9, 2012 at 14:9 Comment(4)
Yes, alternatively you could say "countDownTime+900" or something like that. With a countdowntime of 10000 and an interval of 1000 you will only see 10 ticks if each tick manages to fire precisely on time. Adding 900ms in this example wouldn't give it enough time for 1 more tick but will give it enough time for the "last" tick with some margin for the clock not being precisely on schedule. (e.g., when the last tick hits at "10004" ms from start, you would miss it but if you added a little padding, it would be fine)Allie
Changing the tick from 1000ms to 500ms fixed this one for me as well.Louth
Please add @Allie 's suggestion to your answer. That way even if your application requires a specific time between tics it'll work. I noticed on my device there is a 6ms delay. So countDownTime+200 should solve it in most cases and make it almost unrecognizable to the userMafia
Since the "feature" is still present in Android 7 I recorded time remaining when onTick fired with 1000 ms ticks. Over 5 s countdown, the last tick was at 1959. It was always around 40 ms short, adding 50 ms gave the (illusion of) final tick. Over 20 s, shortfall around 70 ms. Over 60 s, shortfall 180 ms. Adding fixed time is a kludge that doesn't work for variable countdown length. Adding 900 ms on a 1000 ms tick on a short countdown is practically a whole extra tick which means on a short countdown the last tick is like 2 ticks which was a big part of the problem in the first place.Medford
G
23

I don't know why the last tick is not working but you can create your own timer with Runable , for example.

class MyCountDownTimer {
    private long millisInFuture;
    private long countDownInterval;
    public MyCountDownTimer(long pMillisInFuture, long pCountDownInterval) {
            this.millisInFuture = pMillisInFuture;
            this.countDownInterval = pCountDownInterval;
        }
    public void Start() 
    {
        final Handler handler = new Handler();
        Log.v("status", "starting");
        final Runnable counter = new Runnable(){

            public void run(){
                if(millisInFuture <= 0) {
                    Log.v("status", "done");
                } else {
                    long sec = millisInFuture/1000;
                    Log.v("status", Long.toString(sec) + " seconds remain");
                    millisInFuture -= countDownInterval;
                    handler.postDelayed(this, countDownInterval);
                }
            }
        };

        handler.postDelayed(counter, countDownInterval);
    }
}

and to start it,

new MyCountDownTimer(10000, 2000).Start();

EDIT FOR GOOFY'S QUESTION

you should have a variable to hold counter status (boolean) . then you can write a Stop() method like Start().

EDIT-2 FOR GOOFY'S QUESTION

actually there is no bug on stopping counter but there is a bug on start again after stop(resume).

I'm writing a new updated full code that I had just tried and it's working. It's a basic counter that show time on screen with start and stop button.

class for counter

public class MyCountDownTimer {
    private long millisInFuture;
    private long countDownInterval;
    private boolean status;
    public MyCountDownTimer(long pMillisInFuture, long pCountDownInterval) {
            this.millisInFuture = pMillisInFuture;
            this.countDownInterval = pCountDownInterval;
            status = false;
            Initialize();
    }

    public void Stop() {
        status = false;
    }

    public long getCurrentTime() {
        return millisInFuture;
    }

    public void Start() {
        status = true;
    }
    public void Initialize() 
    {
        final Handler handler = new Handler();
        Log.v("status", "starting");
        final Runnable counter = new Runnable(){

            public void run(){
                long sec = millisInFuture/1000;
                if(status) {
                    if(millisInFuture <= 0) {
                        Log.v("status", "done");
                    } else {
                        Log.v("status", Long.toString(sec) + " seconds remain");
                        millisInFuture -= countDownInterval;
                        handler.postDelayed(this, countDownInterval);
                    }
                } else {
                    Log.v("status", Long.toString(sec) + " seconds remain and timer has stopped!");
                    handler.postDelayed(this, countDownInterval);
                }
            }
        };

        handler.postDelayed(counter, countDownInterval);
    }
}

activity class

public class CounterActivity extends Activity {
    /** Called when the activity is first created. */
    TextView timeText;
    Button startBut;
    Button stopBut;
    MyCountDownTimer mycounter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        timeText = (TextView) findViewById(R.id.time);
        startBut = (Button) findViewById(R.id.start);
        stopBut = (Button) findViewById(R.id.stop);
        mycounter = new MyCountDownTimer(20000, 1000);
        RefreshTimer();
    }

    public void StartTimer(View v) {
        Log.v("startbutton", "saymaya basladi");
        mycounter.Start();
    }

    public void StopTimer(View v) {
        Log.v("stopbutton", "durdu");
        mycounter.Stop();
    }

    public void RefreshTimer() 
    {
        final Handler handler = new Handler();
        final Runnable counter = new Runnable(){

            public void run(){
                timeText.setText(Long.toString(mycounter.getCurrentTime()));
                handler.postDelayed(this, 100);
            }
        };

        handler.postDelayed(counter, 100);
    }
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:weightSum="1">
    <TextView android:textAppearance="?android:attr/textAppearanceLarge" 
              android:text="TextView" android:layout_height="wrap_content" 
              android:layout_width="wrap_content" 
              android:id="@+id/time">
    </TextView>
    <Button android:text="Start" 
            android:id="@+id/start" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:onClick="StartTimer">
    </Button>
    <Button android:text="Stop" 
            android:id="@+id/stop" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:onClick="StopTimer">
    </Button>
</LinearLayout>
Gallnut answered 13/1, 2012 at 23:24 Comment(13)
I figured I should just use a Handler to take care of it, but minus the flaw I'm talking about in my question, the CountDownTimer is so simple and perfect! I mean just from your code example and mine, you can see how simple and compact CountDownTimer is compared to a Handler! If only it actually worked right though lol.Pathan
absolutely you are right, I just want to show that you can do it with alternative methods.Gallnut
@ocanal thanks i am using your code but if i want to stop the timer how to do...please replyEulogist
@Eulogist look my edited answer again, I appended the code you want.Gallnut
@ocanal i found that the timer is not stopping it only shows the message can you please help me on thatEulogist
@ocanal i mean when i click on stop button it has to stop the counter which is running eg: when counter is 0:52 i click on stop the counter should stop at 0:52,but the counter is still running.Any idea on that???Eulogist
@Eulogist actually it's not running when you stop it, but there is a problem on resuming it. I appended a new full updated code to my answer, maybe it helps you.Gallnut
thanks man now i am able to pause and also resume it back again ...thanks a lot once again ... In the future can i contact you for any other queries???Eulogist
@ocanal how can i contact youEulogist
@Eulogist if you ask your question in stackoverflow, that will be better, because I can't answer every question you asked. but if you want to contact me, send me an email, it's in my profile page.Gallnut
How Could I Find that Count down has completed??????? and the start countdown again?Iguana
@ocanal you are awesome!! ..this is much better than using a CountdownTimer since there are many issues associated with it, being last onTick() miss and improper UI display of numerals.Surinam
What happens when I close the app in between ticks? Does it handle the cases where it returned back after the interval and then realized activity is already dead?Vitelline
P
4

I've spent hours trying to figure out this problem, and I'm happy to show you a nice work around. Don't bother waiting for the onFinish() call, just add 1 (or whatever your interval is) to your units, then add an if statement in the onTick() calls. Just do your onFinish() task(s) on the last onTick(). Here's what I've got:

    new CountDownTimer( (countDownTimerValue + 1) * 1000, 1000) { //Added 1 to the countdownvalue before turning it into miliseconds by multiplying it by 1000.
        public void onTick(long millisUntilFinished) {

          //We know that the last onTick() happens at 2000ms remaining (skipping the last 1000ms tick for some reason, so just throw in this if statement.
            if (millisUntilFinished < 2005){ 
                //Stuff to do when finished.
            }else{
                mTextField.setText("Time remaining: " + (((millisUntilFinished) / 1000) - 1));  //My textfield is obviously showing the remaining time. Note how I've had to subtrack 1 in order to display the actual time remaining.
            }
        }

        public void onFinish() {
        //This is when the timer actually finishes (which would be about 1000ms later right? Either way, now you can just ignore this entirely.


        }
    }.start();
Potts answered 30/1, 2012 at 3:53 Comment(1)
I like you solution but I find that, at least for Android >=6, the problem is the final onTick() not getting called but the onFinish() is still getting called at the right time. Since I'm only updating the countdown display in my onTick, I just use your solution to show that final update but leave the onFinish() animations at the same placeVolleyball
G
4

The most simple solution I came up with is as follows. Note that it only works if you need a simple screen to display with a seconds countdown.

mTimer = new CountDownTimer(5000, 100){
            public void onTick(long millisUntilFinished) {
                mTimerView.setText(Long.toString(millisUntilFinished/1000));                
             }

             public void onFinish() {
                 mTimerView.setText("Expired");
             }
        };

        mTimer.start();

In the code above the onTick() is called every 100 milliseconds but visually only seconds are displayed.

Grandeur answered 16/6, 2012 at 16:18 Comment(0)
R
3

While the solution above is valid, it can be further improved. It unnecessarily has a runnable inside another class (which can already be treated on it's own). So just create a class that extends a thread (or runnable).

    class MyTimer extends Thread {
      private long millisInFuture;
      private long countDownInterval;
      final Handler mHandler = new Handler();

      public MyTimer(long pMillisInFuture, long pCountDownInterval) {
        this.millisInFuture = pMillisInFuture;
        this.countDownInterval = pCountDownInterval;
      }

      public void run() {
        if(millisInFuture <= 0) {
          Log.v("status", "done");
        } else {
          millisInFuture -= countDownInterval;
          mHandler.postDelayed(this, countDownInterval);
        }
      }
    }
Raynell answered 7/3, 2012 at 3:49 Comment(0)
M
2

I found easy solution. I need CountDown to update ProgressBar, so I did this:

new CountDownTimer(1000, 100) {

    private int counter = 0;

    @Override
    public void onTick(long millisUntilFinished) {
        Log.d(LOG_TAG, "Tick: " + millisUntilFinished);
        if (++counter == 10) {
            timeBar.setProgress(--lenght); // timeBar and lenght defined in calling code
            counter = 0;
        }
    }


    @Override
    public void onFinish() {
        Log.d(LOG_TAG, "Finish.");

        timeBar.setProgress(0);
    }

};

Small tick do the trick :)

Meanly answered 30/4, 2014 at 10:19 Comment(0)
E
2

So I think I went a little over board because my timer runs in its own thread instead of using postDelay handlers, though it always posts back to the thread it was created in. I also knew that I only cared about seconds so its simplified around that idea. It also lets you cancel it and restart it. I do not have pausing built in because that's not in my needs.

/**
* Created by MinceMan on 8/2/2014.
*/
public abstract class SecondCountDownTimer {

private final int seconds;
private TimerThread timer;
private final Handler handler;

/**
 * @param secondsToCountDown Total time in seconds you wish this timer to count down.
 */
public SecondCountDownTimer(int secondsToCountDown) {
    seconds = secondsToCountDown;
    handler = new Handler();
    timer = new TimerThread(secondsToCountDown);
}

/** This will cancel your current timer and start a new one.
 *  This call will override your timer duration only one time. **/
public SecondCountDownTimer start(int secondsToCountDown) {
    if (timer.getState() != State.NEW) {
        timer.interrupt();
        timer = new TimerThread(secondsToCountDown);
    }
    timer.start();
    return this;
}

/** This will cancel your current timer and start a new one. **/
public SecondCountDownTimer start() {
    return start(seconds);
}

public void cancel() {
    if (timer.isAlive()) timer.interrupt();
    timer = new TimerThread(seconds);
}

public abstract void onTick(int secondsUntilFinished);
private Runnable getOnTickRunnable(final int second) {
    return new Runnable() {
        @Override
        public void run() {
            onTick(second);
        }
    };
}

public abstract void onFinish();
private Runnable getFinishedRunnable() {
    return new Runnable() {
        @Override
        public void run() {
            onFinish();
        }
    };
}

private class TimerThread extends Thread {

    private int count;

    private TimerThread(int count) {
        this.count = count;
    }

    @Override
    public void run() {
        try {
            while (count != 0) {
                handler.post(getOnTickRunnable(count--));
                sleep(1000);
            }
        } catch (InterruptedException e) { }
        if (!isInterrupted()) {
            handler.post(getFinishedRunnable());
        }
    }
}

}

Elviraelvis answered 2/8, 2014 at 15:13 Comment(0)
E
1

To expand on Nantoka's answer. Here's my code to ensure the view is updated correctly:

countDownTimer = new CountDownTimer(countDownMsec, 500) 
{
    public void onTick(long millisUntilFinished)
    {
        if(millisUntilFinished!=countDownMsec)
        {
            completedTick+=1;
            if(completedTick%2==0)      // 1 second has passed
            {
                // UPDATE VIEW HERE based on "seconds = completedTick/2"
            }
            countDownMsec = millisUntilFinished;  // store in case of pause
        }
    }

    public void onFinish()
    {
        countDownMsec = 0;
        completedTick+=2;       // the final 2 ticks arrive together
        countDownTimer = null;

        // FINAL UPDATE TO VIEW HERE based on seconds = completedTick/2 == countDownMsec/1000
    }
}
Eldwun answered 23/11, 2016 at 10:26 Comment(0)
M
1

I also faced the same issue with CountDownTimer and I tried different approaches. So one of the easiest ways is in solution provided by @Nantoca - he suggests to double the frequency from 1000ms to 500ms. But I don't like this solution because it makes more work which will consume some extra battery resource.

So I decided to use @ocanal's soultion and to write my own simple CustomCountDownTimer.

But I found couple of flaws in his code:

  1. It's a bit inefficient (creating second handler to publish results)

  2. It starts to publish first result with a delay. (You need to do a post() method rather than postDelayed() during first initialization)

  3. odd looking. Methods with capital letter, status instead of classic isCanceled boolean and some other.

So I cleaned it a bit and here is the more common version of his approach:

private class CustomCountDownTimer {

    private Handler mHandler;
    private long millisUntilFinished;
    private long countDownInterval;
    private boolean isCanceled = false;

    public CustomCountDownTimer(long millisUntilFinished, long countDownInterval) {
        this.millisUntilFinished = millisUntilFinished;
        this.countDownInterval = countDownInterval;
        mHandler = new Handler();
    }

    public synchronized void cancel() {
        isCanceled = true;
        mHandler.removeCallbacksAndMessages(null);
    }

    public long getRemainingTime() {
        return millisUntilFinished;
    }

    public void start() {

        final Runnable counter = new Runnable() {

            public void run() {

                if (isCanceled) {
                    publishUpdate(0);
                } else {

                    //time is out
                    if(millisUntilFinished <= 0){
                        publishUpdate(0);
                        return;
                    }

                    //update UI:
                    publishUpdate(millisUntilFinished);

                    millisUntilFinished -= countDownInterval;
                    mHandler.postDelayed(this, countDownInterval);
                }
            }
        };

        mHandler.post(counter);
    }
}
Minta answered 11/6, 2017 at 18:0 Comment(0)
M
1

if Your time Interval is more than 4 sec then every onTick() call would not be proper. So if you want precise result then keep interval less than 5 sec. The Reseaon is at the start of every tick, before onTick() is called, the remaining time until the end of the countdown is calculated and If this time is smaller than the countdown time interval, onTick() would not not called anymore. Instead only the next tick (where the onFinish() method will be called) is scheduled.

Mesa answered 10/11, 2017 at 14:16 Comment(0)
N
1

Add a few milliseconds to your timer to allow it time to process the code. I added +100 to your timer-length, and also Math.ceil() to round up the result, rather than adding 1.

Also... the first tick is AFTER 2000 millis, so you won't get a "10 seconds left" entry unless you add it.

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    final TextView tv = (TextView) findViewById(R.id.tv);
    tv.setText("10 Seconds remain\n"); //displayed before the first tick.
    new CountDownTimer(10000+25, 1000) { //25 to account for processing time
        public void onTick(long m) {
            long sec = (long) Math.ceil(m / 2000 ); //round up, don't add 1
            tv.append(sec + " seconds remain\n");
        }
        public void onFinish() {
            tv.append("Done!");
        }
    }.start();
}
Nicolis answered 23/5, 2018 at 17:37 Comment(0)
C
-1

You are calculating time remaining incorrectly. The callback gets the number of milliseconds until completion of the task.

public void onTick(long m) {  
    long sec = m/1000+1;  
    tv.append(sec+" seconds remain\n");  
}  

should be

public void onTick(long m) {  
    long sec = m/1000;  
    tv.append(sec+" seconds remain\n");  
}

I've never used this class myself but it looks like you will not get a callback the instant it starts, which is why it appears like you're missing an entry. e.g. 10000 ms, 1000 ms per tick you'd get a total of 9 update callbacks, not 10 - 9000, 8000, 7000, 6000, 5000, 4000, 3000, 2000, 1000, finish.

Cherisecherish answered 14/1, 2012 at 4:56 Comment(1)
I did that +1 to make the output make sense. The output I have in my question is reflective of the actual time left until the countdown is over e.g. when it says "3 seconds... 2 seconds... Done!" it means there were really 2 seconds left before that "Done!" message showed up. So basically if I take out the "+1" from "long sec = m/1000" then the text will say "2 seconds... 1 seconds... Done!" Even though when it says "1 seconds", there are really 2 seconds to go. That sounds a little confusing but if you try out my short code, you will see what I am talking about.Pathan

© 2022 - 2024 — McMap. All rights reserved.