I have a inner class that extends CountDownTimer. Basically its a simple countdown timer that updates a TextView in the activity and plays a sound when the timer is finished. The code for the inner class is:
public class SetTimer extends CountDownTimer
{
public SetTimer(long millisInFuture, long countDownInterval)
{
super(millisInFuture, countDownInterval);
}
@Override
public void onFinish()
{
timeLeft.setText("0");
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Ringtone r=RingtoneManager.getRingtone(getApplicationContext(), notification);
r.play();
}
@Override
public void onTick(long millisUntilFinished)
{
String t;
t=String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished), TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished)
-TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished)));
timeLeft.setText(t);
}
}
The code that creates and references the TextView is:
TextView timeLeft;
and in the onCreate method:
timeLeft=(TextView) findViewById(R.id.txtTimeLeft);
This works fine until I rotate the display. At that point the timer is still running and does play the sound at the end but it doesn't update the TextView. The TextView is declared at the top of the class and referenced in the onCreate method of the activity. If I restart the timer then it works. I used Log.d to check if the onTick method was still getting called and it was. My guess is that the reference to the TextView has changed but I can't figure out how to set it back to the timer. I tried declaring a TextView in the onTick method and updating that figuring it would then pick up a reference to the current instance of the TextView but that also didn't work. The only other thing to note is that the SetTimer object is created when the user clicks on a button. That code is:
timer=new SetTimer(interval, 100);
timer.start();
Any thoughts on how to have the SetTimer keep updating the TextView after the screen is rotated?
Update I completely rewrote the answer as I did not notice a terrible thing in your code at first glance.
You are possibly leaking resources and the fact your app does not crash - could be just a matter of time. First of all, your inner SetTimer class implicitly holds a reference to Activity (I guess, you declared this class in Activity, did not you?). That prevents your Activity from being garbage collected, and I guess, that's why you do not get an Exception when setting a value to a TextView that is "out-of-sight".
So you should either declare your class as private static class, static class (inner classes), or public class (in it's own file). That way, you will not hold an implicit reference to your Activity and will not cause a memory leak when it gets destroyed.
But now you won't be able to access a textview directly, as it is a member of your Activity class. Let's solve it that way:
Declare an interface inside SetTimer:
interface OnTickUpdateListener{
public void onTickUpdate(String text);
}
Declare an instance of such interface in your SetTimer and modify constructor:
public class SetTimer extends ... {//this class IS IN IT's OWN FILE!!!!
private OnTickUpdateListener listener;
public void registerListener(OnTickUpdateListener listener){
this.listener = listener;
}
public void unregisterListener(){
this.listener = null;
}
...
}
Let's trigger the listener when the timer ticks:
@Override
public void onTick(long millisec){
if(listener != null){
String t;
t = String.valueOf(millisec);//or whatever
listener.onTickUpdate(t);
}
}
Now, make your Activity implement your interface:
public class MyActivity extends Activity implements SetTimer.OnTickUpdateListener{
@Override
public void onTickUpdate(String text){
textView.setText(text);
}
Now to the harder part. We need to save a SetTimer instance when Activity is destroyed. That would be a nice trick to put a SetTimer inside a retained Fragment invisible to user, that would work much like an "immortal container" :)
Create a Fragment class:
public class MyFragment extends Fragment{
public static final String TAG = MyFragment.class.getSimpleName();
private SetTimer timer;
private static final int interval = 10;
private MyActivity myActivity;
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
this.myActivity = (MyActivity) activity;
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setRetainInstanceState(true);
timer = new SetTimer(interval, 10);
timer.start();
}
@Override
public void onActivityCreated(Bundle state){
super.onActivityCreated(state);
timer.registerListener(myActivity);//activity should receive ticks
}
@Override
public void onDetach(){
super.onDetach();
timer.unregisterListener();//ensure we do not post a result to non-existing Activity
}
}
And last, add your MyFragment in onCreate
method of MyActivity:
public class MyActivity extends Activity implements SetTimer.OnTickUpdateListener{
private MyFragment fragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getFragmentManager();
fragment = (MyFragment) fm.findFragmentByTag(MyFragment.TAG);
if(fragment == null){
fragment = new MyFragment();
fm.beginTransaction().add(R.id.container, fragment, MyFragment.TAG).commit();
}
}
That way, we restore the existing fragment upon Activity recreation, and MyFragment registers new MyActivity as a listener, which will receive tick updates.
PS: I wrote this from scratch and did not test it, so if you encounter any error - please post them so we could work the out.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With