Resuming CountdownTimer after rotation - java

I have a CountdownTimer that counts down from 60 seconds. This CountdownTimer works by setting a textView to the remaining milliseconds, but whenever i rotate my device, the CountdownTimer gets reset.
I know this happens because the Activity gets restarted on rotation. So i tried saving the time remaining in a bundle and then restoring it, after the Activity was restarted.
long transferValue;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_playtimemode);
Log.d("Debug", "onCreate: " + transferValue);
long setTime = 60000;
long difference = setTime - transferValue;
new CountDownTimer(difference, 1000) {
public void onTick(long millisUntilFinished) {
millisUntilFinishedToSave = millisUntilFinished;
tvCountdown.setText("" + millisUntilFinished / 1000);
}
public void onFinish() {
tvCountdown.setText("done!");
}
}.start();
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putLong("millisKey", millisUntilFinishedToSave);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
transferValue = savedInstanceState.getLong("millisKey");
Log.d("Debug", "onRestoreInstanceState(): " + transferValue);
}
This however doesn't work. I am intializing transferValue at the top of this code (hence it returning 0), but how can i else save the data from the savedInstanceState to the CountdownTimer?
07-06 20:21:30.038: D/Debug(28995): onCreate: 0
07-06 20:21:30.043: D/Debug(28995): onRestoreInstanceState(): 55994

I would give your timer it's own thread. Your timer is being stopped because it's on the UI thread (as you stated) and when the UI redraws the Activity is re-initialized. All long running processes should have their own thread. Rule of thumb: get out of the UI thread as soon as possible.
Here is an example of using a Service. This service will start when called and stay in memory regardless of screen orientation changes or even activity focus changes.
Here is the service:
public class Timer extends Service {
#Override
public IBinder onBind(Intent i) {
return null;
}
#Override
public int onStartCommand(Intent i, int flags, int startId) {
// Put your timer code here
}
}
You will need to add this line to your manifest (somewhere between the application open/close tags):
<service android:name=".Timer" />
Then you can start the service at any time by using this (it's important to note that you can start it over and over again, the recursive calls do not make a new service.):
startService(new Intent(this, Timer.class));

Use System.currentTimeMillis to get the current system time, then add 60000 milliseconds to the time and store that as an end time. Then any time you have to change anything, just compare System.currentTimeMillis to the EndTime.
Endtime = System.currentTimeMillis + 60000;
then on every instance
TimeRemaining = Endtime - System.currentTimeMillis

The accepted answer makes the thing very complex. You can do it in a simpler way. The problem in your code is that Activity gets created on rotation (see Activity Lifecycle), so does the CountdownTimer instance.
I could write the whole example code but there is a nice answer from #Drew here: Android Innerclass TextView reference when activity resumes

Related

Question on CountDown Timer Java, Android

I'm studying Java+Android now and I have the following example of a code, that is working fine.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new CountDownTimer(10000,1000){
public void onTick(long msUntillDone){
Log.i("Seconds left",String.valueOf(msUntillDone/1000));
}
public void onFinish(){
Log.i("Finished","We are done!");
}
}.start();
}
}
The question that I have is following. In the onTick function there is msUntillDone variable that I pass to it, and it gets that its 10000. But where does it get that from the new CountDownTimer, I mean how does it know it's value if I don't explicitly assign this name to 10000 value in main method arguments?
I hope you understand my question.
CountDownTimer(long millisInFuture, long countDownInterval)
The CountDownTimer schedules a countdown until a time in the future, with regular notifications on intervals along the way.
It means that the onTick() method is periodically called, every countDownInterval milliseconds (more and less). It is a mandatory parameter or course
You can consider the CountDownTimer as a counter, decreasing from millisInFuture to 0 and which warning the caller about the progress every countDownInterval milliseconds.
If you're interested by how it works under the hood, you can check the implementation : https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/CountDownTimer.java;l=44?q=CountDownTimer&sq=

My service is behaving strangely when the phone is off

The purpose of the service is to keep track of time since a button was pressed. If the menuactivity is showing, it updates some values on the menuactivity every minute using a timer, otherwise, it just updates itself.
It seems to work fine when the application is open or closed, but when the phone is off it slows down to less than half of what it should be (only showing like 10 minutes have passed after 21 real minutes have passed).
int startTime; //time at which the button is pressed
int time; //the current time, relative to startTime
#Override
public int onStartCommand(Intent intent, int flags, int startId){
startTime = (int)(System.nanoTime()/1000000000.0);
UpdateTimeTask updateTimeTask = new UpdateTimeTask();
timer = new Timer();
timer.schedule(updateTimeTask, 0, UPDATE_PERIOD); //update period is 60,000 (60 seconds)
return START_STICKY;
}
public class UpdateTimeTask extends TimerTask {
public void run() {
updateMenu();
}
}
public void updateMenu(){
time = (int) Math.round((System.nanoTime() / 1000000000.0) - startTime);
if(serviceCallbacks != null){ //it wont be null if its on menuactivity
serviceCallbacks.updateTimeElapsed(time/3600, time/60 - (time/3600) * 60);
}
}
If I have my phone off for a while and then go back in to menuactivity, it doesn't "fix itself" after a few cycles. I thought onStartCommand might be called more than once, but the only time the service can possibly be started is when the button is pressed.
but the only time the service can possibly be started is when the
button is pressed
That is not true. When your app go into the background (is no longer visible) it becomes a candidate to be killed if the system needs memory for other higher ranked apps. Also, the user can kill your app by swiping it from the task list. Because the service returns START_STICKY from onStartCommand(), the system will restart the service after some period of time, calling onStartCommand() with a null intent. This behavior makes a service unsuitable as the home for a data item you want to retain, such as startTime.
An alternative is to persist the value in SharedPreferences. A service is not needed and the periodic update processing can be done in your activity using the postDelayed() method of any view.
This sample activity outlines the basic logic:
public class ButtonActivity extends Activity {
private static final String TIME_KEY = "time";
private static final long PERIOD = 60*1000; // 60 seconds
private SharedPreferences mPrefs;
private Button mButton;
private TextView mTimeView;
private Runnable mDisplayTask = new Runnable() {
#Override
public void run() {
// show the time elapsed since button press in milliseconds
long elapsed = System.currentTimeMillis() - mPrefs.getLong(TIME_KEY, 0);
mTimeView.setText(""+elapsed);
// schedule next display update
mTimeView.postDelayed(mDisplayTask, PERIOD);
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPrefs = getPreferences(MODE_PRIVATE);
// clear the button press time
setPressTime(0);
setContentView(R.layout.activity_demo);
mButton = (Button)findViewById(R.id.button);
mTimeView = (TextView)findViewById(R.id.time);
mButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// save the button press time
setPressTime(System.currentTimeMillis());
// start the display updates
mDisplayTask.run();
}
});
}
#Override
protected void onPause() {
super.onPause();
// activity no longer visible; stop the updates
mTimeView.removeCallbacks(mDisplayTask);
}
#Override
protected void onResume() {
super.onResume();
// activity is visible
// if the button has been pressed, start the display updates
if (mPrefs.getLong(TIME_KEY, 0) > 0) {
mDisplayTask.run();
}
}
private void setPressTime(long time) {
// persist the button press time
SharedPreferences.Editor ed = mPrefs.edit();
ed.putLong(TIME_KEY, time);
ed.commit();
}
}
The accepted answer is correct, but only solved part of the problem. The other problem was that I was using System.nanoTime() instead of .currentTimeMillis(); .nanoTime() stops when the screen is off. Putting this here for possible future googlers.

onPause, onStop, onDestroy not stopping timer

In my onCreate method in my activity i call a method from an object and pass the methods value as 1 which means to start a timer in the objects class. However I want to stop the timer whenever the app closes, loses focus or someone pressed the back button on their device and exited the app. I tried doing this below my onCreate method with an onPause, onStop, onDestroy and entered the methods value as 2 for the object which means to cancel the timer. However my problem is that whenever someone presses the back button on their device and then goes back in to the app the same timer is running twice because the app did not cancel the timer in the onStop, onPause or onDestroy. Why didn't the onStop, onPause and onDestroy stop the timer and how do i make it stop the timer so two arent running when the app is reopened?
Activity below
Ship mShip = new Ship(0,0,0);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
mShip.timerStart(1);
}
#Override
public void onPause()
{
super.onPause();
mShip.timerStart(2);
}
#Override
public void onStop()
{
super.onStop();
mShip.timerStart(2);
}
#Override
public void onDestroy()
{
super.onDestroy();
mShip.timerStart(2);
}
Ship Class below
public static int counter = 0;
public static int counterPerSec = 5;
TimerClass startTimer = (TimerClass) new TimerClass(2000,1000)
{
#Override
public void onFinish() {
counter += counterPerSec;
this.start();
}
};
public void timerStart(int x) {
if(x == 1)
{
startTimer.start();
}
if(x == 2)
{
startTimer.cancel();
}
}
Timer Class
public class TimerClass extends CountDownTimer {
public TimerClass(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
#Override // when timer is finished
public void onFinish() {
this.start();
}
#Override // on every tick of the timer
public void onTick(long millisUntilFinished) {
}
}
I can not see, why your timer is not canceled. But there is another bug in your code: You can not pause and resume a countdown timer by calling resume and start.
If your time gets canceled, you should save the old timer vaules. And if your timer has to be resumed, you can create a new timer with the old timer values. See: Android: How to pause and resume a Count Down Timer?
To your question: Can you debug and check if onPause, onStop, onDestroy is called? Is there any exception thrown? Do you have any compile warnings?
Last important question: How do you know that two timers are running?
Well, I think I can correctly assume that onPause, onStop, and onDestroy are executing, so I would venture to guess that there is a bug in your TimerClass class.

Where to put AsyncTask so that it's only started once

Right now I have a Timer starting an AsyncTask in a Fragments onCreate method (with a timer interval of 1 minute). So that is not a good idea I believe because if there is a configuration change the fragment will call the onCreate method again and I will have 2 running Timer AsyncTasks right?
So I need some way to put the AsyncTask where it's only started once during the whole lifecycle of the app.
no need of Asynctask for simple timer, try this -
class MyFragment extends Fragment{
private int currentTime;
private void startTimer(){
new Handler().
postDelayed(new Runnable(){
#Override
public void run()
{
onTick();
startTimer();
}
},1000*60);
}
private void onTick()
{
currentTime++;
}
#Override
public void onSaveInstanceState(Bundle outState)
{
outState.putInt("currentTime", currentTime);
super.onSaveInstanceState(outState);
}
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (savedInstanceState != null)
{
currentTime=savedInstanceState.getInt("currentTime",0);
startTimer();
}
}
You could use a Countdown Timer and cancel it in onDestroy(). That would guarantee that you only have a single one running at any time.
As for starting it exactly only once you will have to persist that knowledge somewhere depending on your needs. Maybe storing a boolean in onSaveInstanceState and reading it in onCreate() would do the trick?
And, as #marcin_j has pointed out, AsyncTasks are executed in sequence (unless specifically started differently), so using one will block every other Async task in your app (like downloading stuff etc).
if there is a configuration change the activity gets destroyed and re-created so no you will not have 2 timers running

timer.scheduleAtFixedRate does not stop when i call cancel

At onCreate, I run a task that repeats every minute. Here how I do it:
myTimer = new Timer();
int delay = 30000; // delay for 30 sec.
int period = 600000; // repeat every 60 sec.
doThis = new TimerTask() {
public void run() {
Log.v("TImer","repeated");
wv.reload();
}
};
myTimer.scheduleAtFixedRate(doThis, delay, period);
All that code is in onCreate. So, when the app goes off from the screen, I can see in logcat that timer steel runs, and will not stop unless the app will be destroyed. So, in onPause of activity I call myTimer.cancel(); but it didnt help. I still can see updates in logcat, even when the app is not on the screen. So, how to stop timerTask?
Here is your code put into my file, with the values delay and period tweaked so I don't have to wait so long. I run the app. I see the messages in LogCat. I press the home button on my Galaxy S3. Then the messages stop in LogCat.
public class MainActivity extends Activity {
Timer myTimer;
TimerTask doThis;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myTimer = new Timer();
int delay = 0; // delay for 30 sec.
int period = 1000; // repeat every 60 sec.
doThis = new TimerTask() {
public void run() {
Log.v("TImer","repeated");
}
};
myTimer.scheduleAtFixedRate(doThis, delay, period);
}
#Override
protected void onPause() {
myTimer.cancel();
super.onPause();
}
}
It could be that since the thread has already been sent out, it runs one last time. In the onLoad set a variable to true, then in the onPause set it to false. Then in your timer task only run your code if the variable is true.
Write to the log outside of the new if statement though. If it is indeed running it just one last time, then that might be your solution. But if it is still running it over and over multiple times after the onPause then don't take my solution.
The actual answer is: The onPause method needs to be defined correctly.
The whole story:
The questioner defined the onPause method wrong. That was the reason for asking this question.
I'm writing this because I spent too much time reading question, answers, code and comments. The real answer was hidden in the last comment.

Categories

Resources