In the previous article we got the layout and state machine defined for our app to time and measure stoppages during a football match. In this concluding article in the series we’ll hook it all up to the timer logic so that the timers update while the match is in progress.
There is a small clue to the approach that we’re going to use for the timers in the code from the previous article: There’s a Handler instance defined which never gets used, and we’re going to use Handler.postDelayed()
to schedule future UI updates.
There are a number of different ways that we could handle the timer itself, but I chose postDelayed()
Handler events to ensure that the required updates to the UI occurred on the UI thread (as they are required to); to ensure that only one timer is ever active; and to recalculate when the next time event is due each time we set the next timer to prevent drift over time due till rounding issues. Also, when we calculate the next update, if the app state is either Running or Paused, we’ll want to update every second, but if the state is Stopped then we only need to update whenever the minute changes.
Let’s have a look at the additional code (which is highlighted):
public class MainActivity extends Activity { private static final String START = "START"; private static final String ACCUMULATED = "ACCUMULATED"; private static final String ALL = "ALL"; private static final String CURRENT = "CURRENT"; . . . @Override protected void onResume() { super.onResume(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( this); mStartTime = prefs.getLong(START, 0); mAccumulated = prefs.getLong(ACCUMULATED, 0); mAllStoppages = prefs.getLong(ALL, 0); mCurrentStoppage = prefs.getLong(CURRENT, 0); update(); scheduleTick(mStartTime == 0); } @Override protected void onPause() { super.onPause(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( MainActivity.this); SharedPreferences.Editor editor = prefs.edit(); if (mStartTime > 0) { editor.putLong(START, mStartTime); editor.putLong(ACCUMULATED, mAccumulated); editor.putLong(ALL, mAllStoppages); editor.putLong(CURRENT, mCurrentStoppage); } else { editor.remove(START); editor.remove(ACCUMULATED); editor.remove(ALL); editor.remove(CURRENT); } editor.commit(); mHandler.removeCallbacks(tick); } . . . private void startTimer() { mStartTime = System.currentTimeMillis(); mCurrentStoppage = 0; scheduleTick(false); } private void stopTimer() { mAccumulated += System.currentTimeMillis() - mStartTime; mStartTime = 0; scheduleTick(true); } . . . private Runnable tick = new Runnable() { @Override public void run() { Log.v(TAG, "tick"); if (mStartTime > 0) { update(); scheduleTick(false); } else { updateTime(); scheduleTick(true); } } }; private void scheduleTick(boolean minute) { mHandler.removeCallbacks(tick); long now = System.currentTimeMillis(); long delayMillis; if (minute) { delayMillis = (((now / 60000) + 1) * 60000) - now; } else { long offset = mStartTime % 1000; delayMillis = (((now / 1000) + 1) * 1000) - now + offset; } mHandler.postDelayed(tick, delayMillis); } }
The scheduleTick method calculates when we next need to update the UI and schedules it using postDelayed
. The tick()
method is invoked at the scheduled time which updates the UI and calls scheduleTick()
to schedule the next update.
It’s worth noting that the timer only runs when the Activity is in actually running so we don’t keep scheduling UI update events while the screen has gone to sleep, and we persist the current app state in onPause to ensure that we persist the Activity state while the display is asleep. We could hold a WakeLock to keep the display awake while the app is running, but this would be a bit of a battery killer, so I decided against it.
All that remains is to run it, and we can see the various timers in operation:
This shows that the time the picture was takes was 17 minutes past one in the afternoon, the match had been playing for nine minutes eleven seconds. The actual played time was eight minutes and ten seconds, with one minute and one second of stoppages, and the current stoppage was currently of twenty one seconds.
That concludes our short series about developing for the smaller form factor of a Smart Watch. The sample app really isn’t doing much that couldn’t easily do in a phone or tablet app, but the concept of a timer seems a logical fit for a watch device, hence the choice as the sample code.
The source code for this article is available here.
Many thanks to Sebastiano Poggi and the rest of the folks at i’m SpA for arranging the loan device which have made this series of articles possible.
© 2013 – 2014, Mark Allison. All rights reserved.
Copyright © 2013 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.