Android: onFinish() for CountDownTimer called after new one is declared - java

I am writing an HIIT (High Intensity Interval Training) activity for which I am implementing an interval timer. The CountDownTimer is supposed to finish 5 minutes of Warm-Up, then proceed to time the HIIT workout.
public class WarmUpActivity extends ActionBarActivity{
TextView Mode;
TextView Time;
int minutes;
long time_remaining;
boolean warmup_finished;
private CountDownTimer HIIT_Timer;
private void StartTimer() {
HIIT_Timer = new CountDownTimer(time_remaining, 1000) {
#Override
public void onTick(long millisUntilFinished) {
time_remaining = millisUntilFinished; //in case activity is paused or stopped
Time.setText(" " + (int)floor(millisUntilFinished / 60000) + ":" + ((millisUntilFinished / 1000) % 60));
if (warmup_finished == true) { //if we are in HIIT mode
if ((int)millisUntilFinished % 60000 == 0) { //every minute
if (Mode.getText() == "Low Intensity")
Mode.setText("High Intensity");
else
Mode.setText("Low Intensity");
}
}
}
#Override
public void onFinish() {
if (warmup_finished==false){
Mode.setText("Low Intensity");
warmup_finished = true;
HIIT_Method();
return;
}
else {
Completed_Method();
return;
}
}
}.start();
}
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.hiit_layout);
Mode=(TextView) findViewById(R.id.mode);
Time=(TextView) findViewById(R.id.time);
warmup_finished=false;
Mode.setText("Warm-Up");
time_remaining=5*60000; //5 minutes when created
}
#Override
public void onStart(){
super.onStart();
StartTimer();
return;
}
private void HIIT_Method(){
minutes=getIntent().getIntExtra(SelectHIITDuration.MINUTES, 0);
time_remaining=minutes*60000;
StartTimer();
return;
}
private void Completed_Method(){
Mode.setText("Workout Completed");
}
}
When warmup is finished and onFinish() is called for the first time, HIIT_Method is called, in which the HIIT timer is supposed to start with user specified duration. The problem is, after the new timer is declared using Start_Timer(), somehow Completed_Method is called. It can only be called from onFinish(). Why is onFinish() being called after I declare a new timer?

We need to move your call to startTimer from onStart to onCreate.
The issue here is understanding the Android lifecycle for an activity. A very good explanation by someone with better understanding than I is explained here.
Now from what I understand, we typically don't touch onStart until we start worrying about service binding and database queries, but other developers may think differently.
The official android documentation on an activity's lifecycle can be found here

Related

Why doesn't countdown timer work on standalone class which is not an activity?

I am trying to use countdowntimer in Android, following is a small testcode to understand its behaviour
new CountDownTimer(30000, 1000) {
public void onTick(long millisUntilFinished) {
System.out.println("tick");
}
public void onFinish() {
System.out.println("finish");
}
}.start();
System.out.println("done");
If I place this snippet in MainActivity of my project it runs fine, but not in a standalone class. Why ?
Also, does countdown timer run on main thread itself, or it spawns another thread ?
Your code includes 2 parts:
CountDownTimer cdt = new CountDownTimer(30000, 1000) {
public void onTick(long millisUntilFinished) {
System.out.println("tick");
}
public void onFinish() {
System.out.println("finish");
}
};
The first part is instantiating a CountDownTimer object, you can place this code any where, on any thread, because only object was created, it does nothing.
The second part is:
cdt.start();
You should notice that CountDownTimer must be start from Main Thread (call it from onCreate or onResume... of an Activity). So if you place your code in another Class, it is not the problem, you must sure that start() function called on Main Thread.
//Update:
You know, the onTick and onFinish function of CountDownTimer always is called on Main Thread. CountDownTimer will not run any thread.
It's its code:
public synchronized final CountDownTimer start() {
mCancelled = false;
if (mMillisInFuture <= 0) {
onFinish();
return this;
}
mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
mHandler.sendMessage(mHandler.obtainMessage(MSG));
return this;
}
Very simply, CountDownTimer will send a message by a Handler.
And this is Handler:
// handles counting down
private Handler mHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
synchronized (CountDownTimer.this) {
if (mCancelled) {
return;
}
final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
if (millisLeft <= 0) {
onFinish();
} else if (millisLeft < mCountdownInterval) {
// no tick, just delay until done
sendMessageDelayed(obtainMessage(MSG), millisLeft);
} else {
long lastTickStart = SystemClock.elapsedRealtime();
onTick(millisLeft);
// take into account user's onTick taking time to execute
long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();
// special case: user's onTick took more than interval to
// complete, skip to next interval
while (delay < 0) delay += mCountdownInterval;
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
}
};
The Handler will send a delay message for calling next onTick() or onFinish(). So it must use Main Thread (or Main Looper). If you want it run in a custom thread, reimplement it in your way :D

onTick method of Countdown timer keeps getting called even after calling cancel

I am trying to load some data. Id data is loaded in 20 seconds than i start new activity else i will finish by giving some relevant message. I have started a countdownTimer to keep track of time. Once data is loaded, I want to stop the timer. I have Following class :
public class SplashActivity extends AppCompatActivity {
private Context mContext;
private Boolean mDataLoadedFromServer = false;
private String mJSONData;
private SplashTimerForLoadingMasterDataForAllChannels mTimer;
private void stopTimer(){
mTimer.cancel();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
setContentView(R.layout.activity_splash);
mTimer = new SplashTimerForLoadingMasterDataForAllChannels(20000,1000);
mTimer.start();
}
class SplashTimerForLoadingMasterDataForAllChannels extends CountDownTimer {
public SplashTimerForLoadingMasterDataForAllChannels(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
Log.d("testTimer", "SplashTimerForLoadingMasterDataForAllChannels");
//SomE AsyncTAsk
LoadData loaddata = new LoadData();
loaddata.execute();
//SomE AsyncTAsk
}
#Override
public void onTick(long millisUntilFinished) {
Log.d("testTimer", "onTick millisUntilFinished = " + millisUntilFinished + " mDataLoadedFromServer = " + mDataLoadedFromServer);
//mDataLoadedFromServer is modified once Data is loaded in AsyncTask
if(mDataLoadedFromServer) {
stopTimer();
}
}
#Override
public void onFinish() {
Log.d("testTimer", "onFinish");
if(mDataLoadedFromServer) {
mDataSavedAndNextActivityLaunched = true;
if (Utils.checkIfUserLoggedIn()) {
mContext.startActivity(new Intent(mContext, ABCACtivity.class));
} else {
mContext.startActivity(new Intent(mContext, XYZActivity.class));
}
finish();
}
}
}
}
I cancel it in a local method call but onTick still keeps getting called. Can someone please help?
Basically counter does not simply stop if I cancle it from onTick() or from onFinish() ie FROM INSIDE TIMER.
However it stops very easily if I do it from any point outside of timer.
So the point at which my data is fully loaded...I called timer.cancle() and it did the trick.
However I still dont understand why it does not work if we do same from inside timer methods.
I tried this code snippet, since most answers are saying you cannot cancel the timer inside its implementation, thus i tried using a handler inside onFinish. Old post but if anyone comes across this its helpful.
new Handler().post(new Runnable() {
#Override
public void run() {
timerTextView.setText("00:" + String.format("%02d", counter));
cancel();
}
});
you need to call stopTimer() outside of the CountDownTimer's onTick(),
something like this
#Override
public void onFinish() {
Log.d("testTimer", "onFinish");
if(mDataLoadedFromServer) {
mDataSavedAndNextActivityLaunched = true;
if (Utils.checkIfUserLoggedIn()) {
mContext.startActivity(new Intent(mContext, ABCACtivity.class));
} else {
mContext.startActivity(new Intent(mContext, XYZActivity.class));
}
finish();
stopTimer();
}
}

Receiving a string value in in MainActivity from SecondActivity, and then counting it down

In my program you set the string value for the countdown timer in the secondActivity. That value is then sent to the MainActivities textView which is then supposed to start counting down as soon as the value is fetched. Right now I have made it so you can set the value, and the value is fetched correctly, but what I don't know how to do is start Counting Down this value when It is received. I have already made a CounterClass.
Here is my MainActivity Code...
Bundle extras = getIntent().getExtras();
String intentString;
if(extras != null) {
intentString = extras.getString("TimeValue");
timer = new CounterClass(60000, 1000);
timer.start();
timeSent();
} else {
intentString = "Default String";
}
textTime= (TextView) findViewById(R.id.timeText);
textTime.setText(intentString);
You need to start your timer. Always read the docs.
timer = new CounterClass(millisInFuture, countdownInterval);
timer.start();
EDIT
millisInFuture --- The number of millis in the future from the call to start() until the countdown is done and onFinish() is called.
countDownInterval ---
The interval along the way to receive onTick(long) callbacks.
EDIT 18-12-2015
Convert your intentString to millisInFuture and send it to the CounterClass. And then format it back to HH:MM in onTick() method.
String time = "16:54";
String split[] = time.split(":");
long futureInMillis = Integer.parseInt(split[0]) * 60 * 60 * 1000 + Integer.parseInt(split[1]) * 60 * 1000;
Lets say that you really want to implement your own class.
I think the best and clean way to do it is by interface.
First create a interface class.
AddTimes.java
public interface AddTimes {
void writeTime(String time);
}
Now, implement your interface in your class.
public class MainActivity extends AppCompatActivity implements AddTimes{
Use your class TextView variable "textTime"
timeText = (TextView)findViewById(R.id.timeText); //remove declaration TextView and use timeText instead of textTime .
start your class
new CounterClass(5000L,500L, this).start();
At the CounterClass, add constructor.
AddTimes addTimes;
public CounterClass(long millisInFuture, long countDownInterval, AddTimes addTimes) {
super(millisInFuture, countDownInterval);
this.addTimes = addTimes;
}
Replace textTime.setText(hms); to addTimes.writeTime(hms);
Finally. At your main activity. Implement AddTimes method.
public void writeTime(String time) {
timeText.setText("Time - " + time);
}
Instead of this
TextView timeText = (TextView)findViewById(R.id.timeText);
timeText.setText(intentString);
you need this
textTime= (TextView)findViewById(R.id.timeText);
textTime.setText(intentString);
timer = new CounterClass(10000, 1000);
timer.start();
As you might see timeText has changed to textTime which is a class member variable and your CounterClass uses it.
Copy the code as it is to you MainActivity.java file. Hope this might work.
public class MainActivity extends AppCompatActivity {
TextView textTime;
CountDownTimer timer;
int intValue = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Bundle extras = getIntent().getExtras();
if(extras != null) {
String intentString = extras.getString("TimeValue"); //if this is a plain number like 20, 45, 209, 8
intValue = Integer.valueOf(intentString);
}
textTime= (TextView)findViewById(R.id.timeText);
timer = new CountDownTimer(intValue * 1000, 1000) {
public void onTick(long millisUntilFinished) {
intValue--;
textTime.setText("Count Down: " + intValue);
}
#Override
public void onFinish() {}
}
}
timer.start();
}
You can simply use the count down timer:
new CountDownTimer(30000, 1000) {
public void onTick(long millisUntilFinished) {
textField.setText("seconds remaining: " + millisUntilFinished / 1000);
//here you can have your logic to set text to edittext
}
public void onFinish() {
textField.setText("done!");
}
}.start();

android - Showing variable changes

Ok - I know there has got to be a simple solution to this but for the life of me I can't figure it out.
Programming a very basic android activity to simply iterate through 0-99. I have a textview that I want to display the count. What happens is that it simply stays blank until the end and then shows the ending count (99).
Not sure if a textview is the right way to display or what the answer is. Any help is greatly appreciated. Thanks in advance
Try using code like this in onCreate (where number is defined as a field):
textView.post(new Runnable() {
#Override
public void run() {
number++;
textView.setText("counting: " + number);
if (number < 100) {
textView.postDelayed(this, 50);
}
}
});
Edit: code was edited as View classes have post and postDelayed, which propagates call to Handler instance they have internally.
You need to read a bit about Handler class.
Warning: this code leaks Activity for the time of approximatelly 5 seconds and should not be used directly in production code. You need to remove Runnable from the message queue at the appropriate time (maybe in onDestroy, but it depends on your needs).
View.removeCallbacks for anti-memory-leak.
My guess is that your onCreate() has code like this:
for (int i=0;i<100;i++) {
tv.setText(String.valueOf(i));
Thread.sleep(100); // or something to delay for a bit
}
That will give you the output that you are describing.
As with many GUI frameworks, Android's UI is event-driven. Calling setText() does not update the screen. Rather, it puts a message on a queue, asking for the screen to be updated. That queue is processed by the main application thread... the same thread that is calling onCreate() in the first place. Hence, what you are doing is queuing up 100 setText() calls, none of which will be processed until your loop is complete. Applying the 100 of them takes very little time, giving the visual result of only seeing the last change.
User a timer scheduled at a fixed rate. Increment a counter every second. Set the text on the UI thread. cancel the timer when required.
public class MainActivity extends Activity {
TextView _tv;
Timer _t;
int _count=0;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
_tv = (TextView) findViewById( R.id.textView1 );
_t = new Timer();
_tv.setText(""+_count);
_t.scheduleAtFixedRate( new TimerTask() {
#Override
public void run() {
_count++;
runOnUiThread(new Runnable() //run on ui thread
{
public void run()
{
_tv.setText(""+_count);
if(_count==99)
{
_t.cancel();
}
}
});
}
}, 1000, 1000 );
}
#Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
_t.cancel();
}
}
Use a countdown timer, in below code, onTick() will get called every second, here you can display/update your number each second.
set interval according to your need. Its in mili seconds.
public class TimerActivity extends Activity {
private final long startTime = 100 * 1000;
private final long interval = 1 * 1000;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_timer);
countDownTimer = new MyCountDownTimer(startTime, interval);
countDownTimer.start();
public class MyCountDownTimer extends CountDownTimer {
public MyCountDownTimer(long startTime, long interval) {
super(startTime, interval);
}
#Override
public void onFinish() {
text.setText("Time's up!");
}
#Override
public void onTick(long millisUntilFinished) {
text.setText(100 - millisUntilFinished/1000);
}
}
}

Android: CountDownTimer skips last onTick()!

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 "".
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.
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>
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();
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.
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);
}
}
}
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 :)
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());
}
}
}
}
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
}
}
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:
It's a bit inefficient (creating second handler to publish results)
It starts to publish first result with a delay. (You need to do a post() method rather than postDelayed() during first initialization)
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);
}
}
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.
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();
}
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.

Categories