Kotlin: Persistent Chronometer, keeps counting after restart


Topics interesting only for Kotlin / Android / SQLite programmers

Link to this posting

Postby Ursego » 01 Mar 2020, 22:17

STEPS:

@ Create object AppPrefs as described here. It allows to store LocalDateTime values in the application's SharedPreferences in a convenient way (we need to remember the moment when the Chronometer started - that moment will be used to calculate the new base when the Chronometer's parent Activity is resuming).

@ In "util" package, created in first step, create a Kotlin file named ExtensionFunctions (if you don't have it) and add the following code to it:

Code: Select all
import android.content.Context
import android.os.SystemClock
import android.widget.Chronometer
import java.time.Duration
import java.time.LocalDateTime

// -----------------------------------------------------------------------------------------------------------------
// ------- Extend Chronometer functions to allow persistence (http://code.intfast.ca/viewtopic.php?t=826):
// -----------------------------------------------------------------------------------------------------------------

// The key, by which the base time of the Chronometer (as a LocalDateTime) will be stored in SharedPreferences,
// to be then retrieved onResume() and used to calculate the new base to make the Chronometer keeping counting:
val Chronometer.startLdtPrefKey get() = "${this.id} START LDT" // LDT = LocalDateTime

fun Chronometer.start(context: Context) { // ******* must be called instead of start(<no args>) when user clicks START button
    // Starts Chronometer and makes it counting from the curr. moment (now).
    // To count form a provided moment, use the overload start(LocalDateTime, Context).

    // Remember the curr. moment, so resume(), called from the Activity onResume(), will make the Chronometer counting from it:
    AppPrefs.put(this.startLdtPrefKey, LocalDateTime.now()!!, context)

    this.base = SystemClock.elapsedRealtime() // milliseconds since boot, including time spent in sleep
    this.start()
} // start(Context)

fun Chronometer.start(startLdt: LocalDateTime, context: Context) {
    // Starts Chronometer and makes it counting from the provided moment (startLdt).
    // To count form the curr. moment (now), use the overload start(Context).

    // Remember that moment, so resume(), called from the Activity onResume(), will make the Chronometer counting from it:
    AppPrefs.put(this.startLdtPrefKey, startLdt, context)

    val now = LocalDateTime.now()!!
    var deltaInMilli = Duration.between(startLdt, now)!!.toMillis()

    val secondsInMilli = 1000L
    val minutesInMilli = secondsInMilli * 60
    val hoursInMilli = minutesInMilli * 60
    val daysInMilli = hoursInMilli * 24

    deltaInMilli %= daysInMilli

    val elapsedHours = deltaInMilli / hoursInMilli
    deltaInMilli %= hoursInMilli

    val elapsedMinutes = deltaInMilli / minutesInMilli
    deltaInMilli %= minutesInMilli

    val elapsedSeconds = deltaInMilli / secondsInMilli

    val elapsedTimeMilliseconds =
        elapsedHours * 60 * 60 * 1000 + elapsedMinutes * 60 * 1000 + elapsedSeconds * 1000

    this.base = SystemClock.elapsedRealtime() - elapsedTimeMilliseconds
    this.start()
} // start(LocalDateTime, Context)

fun Chronometer.finish() { // ******* must be called instead of stop() when user clicks STOP button
    this.stop()
    this.base = SystemClock.elapsedRealtime() // reset displayed time to "00:00"
} // finish()

fun Chronometer.resume(context: Context) { // ******* must be called from onResume() of the Chronometer's parent Activity
    val startLdt = AppPrefs.getLocalDateTime(this.startLdtPrefKey, context)
        ?:
        return // it's null because start(context: Context) has never been called

    start(startLdt, context)
} // resume()

@ Create the Chronometer on the Activity. Give it a name (ID). Let's say, you called it myChronometer.

@ Write in the function which starts myChronometer (for example, when user clicks the START button):

Code: Select all
myChronometer.start(this)

@ Write in the function which stops myChronometer (for example, when user clicks the STOP button):

Code: Select all
myChronometer.finish(this)

@ Write in onResume() of myChronometer's parent Activity (that's where the magic happens! :lol: ):

Code: Select all
myChronometer.resume(this)

@ If you want to listen to time changes in your Chronometer (i.e. to do some action when some time has been achieved), do these steps.

REMARK

Pay attention that the start time of the Chronometer is stored in the SharedPreferences using the key, build by the pattern "${this.id} START LDT". The actual key value is looking something like "2131165300 START LDT". I am new to Android, and I don't know why the mnemonic name was converted to a number (I was expecting something like "myChronometer START LDT"), but, anyway, if you have more than one Chronometer in your app, I would suggest to give them unique names (IDs) even if they are placed on different Activities - just to cover the ass.
User avatar
Ursego
Site Admin
 
Posts: 143
Joined: 19 Feb 2013, 20:33



IF you want to ((lose weight) OR (have unbelievable (brain function AND mental clarity))) THEN click:




cron
Traffic Counter

free counters

eXTReMe Tracker