79

I know how to create a simple countdown timer in Java. But I'd like to create this one in Kotlin.

package android.os;

new CountDownTimer(20000, 1000) {
    public void onTick(long millisUntilFinished) {
        mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
    }
    public void onFinish() {
        mTextField.setText("Time's finished!");
    }
}.start();

How can I do it using Kotlin?

Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
  • 2
    Countdown timer can be easy to use but not really accurate. You'll be able to see the timer skipping seconds as the time elapses, if the input time is long enough. Any lag in the system will cause milliseconds of delay on each tick and will eventually cause skipping as the delay accumulates. For more accurate timer implementation, check [this post](https://stackoverflow.com/questions/12762272/android-countdowntimer-additional-milliseconds-delay-between-ticks) – WasabiTea Jan 08 '19 at 23:19

10 Answers10

166

You can use Kotlin objects:

val timer = object: CountDownTimer(20000, 1000) {
    override fun onTick(millisUntilFinished: Long) {...}

    override fun onFinish() {...}
}
timer.start()
Blue Jones
  • 385
  • 1
  • 9
Danail Alexiev
  • 7,624
  • 3
  • 20
  • 28
25

I've solved my problem with timer in Kotlin like this:

class Timer {

    private val job = SupervisorJob()
    private val scope = CoroutineScope(Dispatchers.Default + job)

    private fun startCoroutineTimer(delayMillis: Long = 0, repeatMillis: Long = 0, action: () -> Unit) = scope.launch(Dispatchers.IO) {
        delay(delayMillis)
        if (repeatMillis > 0) {
            while (true) {
                action()
                delay(repeatMillis)
            }
        } else {
            action()
        }
    }

    private val timer: Job = startCoroutineTimer(delayMillis = 0, repeatMillis = 20000) {
        Log.d(TAG, "Background - tick")
        doSomethingBackground()
        scope.launch(Dispatchers.Main) {
            Log.d(TAG, "Main thread - tick")
            doSomethingMainThread()
        }
    }

    fun startTimer() {
        timer.start()
    }

    fun cancelTimer() {
        timer.cancel()
    }
//...
}

I've used Coroutines for a timer.

Dima Kozhevin
  • 3,602
  • 9
  • 39
  • 52
11

Chronometer can be set to count down and it seems to me the easiest way.

Add the Chronometer view in your layout xml, example

<Chronometer  
 android:id="@+id/view_timer"   
 tools:targetApi="24"  
 android:layout_width="wrap_content"  
 android:layout_height="wrap_content"/>

Then in your activity or fragment:

   view_timer.isCountDown = true
   view_timer.base = SystemClock.elapsedRealtime() + 20000
   view_timer.start()
LiA
  • 356
  • 4
  • 5
11

If you want to show a countdown with days hours minutes and seconds

private lateinit var countDownTimer:CountDownTimer
.
.
.
    fun printDifferenceDateForHours() {

            val currentTime = Calendar.getInstance().time
            val endDateDay = "03/02/2020 21:00:00"
            val format1 = SimpleDateFormat("dd/MM/yyyy hh:mm:ss",Locale.getDefault())
            val endDate = format1.parse(endDateDay)

            //milliseconds
            var different = endDate.time - currentTime.time
            countDownTimer = object : CountDownTimer(different, 1000) {

                override fun onTick(millisUntilFinished: Long) {
                    var diff = millisUntilFinished
                    val secondsInMilli: Long = 1000
                    val minutesInMilli = secondsInMilli * 60
                    val hoursInMilli = minutesInMilli * 60
                    val daysInMilli = hoursInMilli * 24

                    val elapsedDays = diff / daysInMilli
                    diff %= daysInMilli

                    val elapsedHours = diff / hoursInMilli
                    diff %= hoursInMilli

                    val elapsedMinutes = diff / minutesInMilli
                    diff %= minutesInMilli

                    val elapsedSeconds = diff / secondsInMilli

                    txt_timeleft.text = "$elapsedDays days $elapsedHours hs $elapsedMinutes min $elapsedSeconds sec"
                }

                override fun onFinish() {
                    txt_timeleft.text = "done!"
                }
            }.start()
        }

If you are navigating to another activity/fragment, make sure to cancel the countdown

countDownTimer.cancel()

Code output

51 days 17 hs 56 min 5 sec

Gastón Saillén
  • 12,319
  • 5
  • 67
  • 77
  • 4
    Please don’t teach the young ones to use the long outdated and notoriously troublesome `SimpleDateFormat` class. At least not as the first option. And not without any reservation. Today we have so much better in [`java.time`, the modern Java date and time API,](https://docs.oracle.com/javase/tutorial/datetime/) and its `DateTimeFormatter`. Yes, you can use it on Android. For older Android see [How to use ThreeTenABP in Android Project](https://stackoverflow.com/questions/38922754/how-to-use-threetenabp-in-android-project). – Ole V.V. Dec 14 '19 at 11:17
  • 1
    DateTimeFormatter needs minimum api lvl 26 (android 8), with SimpleDateFormat we can use it from api 21 in wich handles more devices, I will update also the answer to work with DateTimeFormatter :) – Gastón Saillén Dec 14 '19 at 18:24
  • 1
    There’s a backport. To use java.time with min SDK 21 (for example), add [ThreeTenABP](https://github.com/JakeWharton/ThreeTenABP) to your Android project. And make sure to import the date and time classes from the `org.threeten.bp` package with subpackages. Read more in the link at the end of my previous comment. – Ole V.V. Dec 14 '19 at 19:14
  • @GastónSaillén i have a different use case where am keeping track of time left . So is it still necessary to cancel the counter while navigating to other fragments... If yes! Why? – Enos Okello Feb 19 '22 at 08:00
11

For future readers, you may use the built-in timer inline function in Kotlin.

Example:

import kotlin.concurrent.timer
....
....
timer(initialDelay = 1000L, period = 1000L ) {
     launch {
        executeTask()
     }
}
cod3monk3y
  • 9,508
  • 6
  • 39
  • 54
Ahmed Lotfy
  • 1,065
  • 2
  • 15
  • 33
  • Just beware that unlike CountdownTimer, the callback is called on a different thread than the one you start it from. – Tenfour04 Feb 15 '23 at 16:20
8

CountDownTimer in Kotlin:

object: CountDownTimer(3000, 1000){
    override fun onTick(p0: Long) {}
    override fun onFinish() {
        //add your code here
    }
 }.start()
Ghayas
  • 1,266
  • 10
  • 17
6

Try to use objects, like this :

var countDownTimer = object : CountDownTimer(2000, 1000) {
    // override object functions here, do it quicker by setting cursor on object, then type alt + enter ; implement members
}

Try this website : https://try.kotlinlang.org/#/Kotlin%20Koans/Introduction/Java%20to%20Kotlin%20conversion/Task.kt

You have a little button "Convert from Java" on the top right that could be useful to you.

EDIT:

Do not forget to start this object when you need it, by adding .start() at the end of the declaration, or wherever in your activity / fragment :

countDownTimer.start()
Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
Mathieu
  • 1,435
  • 3
  • 16
  • 35
6
class CustomCountDownTimer(var mutableLiveData: MutableLiveData<String>) {

    lateinit var timer: CountDownTimer
    val zone = ZoneId.systemDefault()
    val startDateTime: ZonedDateTime = LocalDateTime.now().atZone(zone)

    fun start(endOn: Long) {
        if (this::timer.isInitialized) {
            return
        }
        timer = object : CountDownTimer(endOn * 1000, 1000) {

            override fun onTick(millisUntilFinished: Long) {

                val stringBuilder = StringBuilder()

                val endDateTime: ZonedDateTime =
                    Instant.ofEpochMilli(millisUntilFinished).atZone(ZoneId.systemDefault())
                        .toLocalDateTime().atZone(zone)

                var diff: Duration = Duration.between(startDateTime, endDateTime)



                if (diff.isZero() || diff.isNegative) {
                    stringBuilder.append("Already ended!")
                } else {
                    val days: Long = diff.toDays()

                    if (days != 0L) {
                        stringBuilder.append("${days}day : ")
                        diff = diff.minusDays(days)
                    }

                    val hours: Long = diff.toHours()
                    stringBuilder.append("${hours}hr : ")
                    diff = diff.minusHours(hours)

                    val minutes: Long = diff.toMinutes()
                    stringBuilder.append("${minutes}min : ")
                    diff = diff.minusMinutes(minutes)

                    val seconds: Long = diff.getSeconds()

                    stringBuilder.append("${seconds}sec")

                }

                mutableLiveData.postValue(stringBuilder.toString())
                //Log.d("CustomCountDownTimer", stringBuilder.toString())
            }

            override fun onFinish() {
            }
        }

        timer.start()
    }


    fun getTimerState(): LiveData<String> {
        return mutableLiveData
    }
}

How to use it:

val liveData: MutableLiveData<String> = MutableLiveData()
val customCountDownTimer = CustomCountDownTimer(liveData)
customCountDownTimer.start(1631638786) //Epoch timestamp
customCountDownTimer.mutableLiveData.observe(this, Observer { counterState ->
            counterState?.let {
                println(counterState)
            }
        })

Output:

22hr : 42min : 51sec //when less than 4hr are remaining

1day : 23hr : 52min : 44sec // in other cases

Chinmay
  • 354
  • 4
  • 10
5

I know I'm pretty late, but this might help someone who wants to build a countdown timer app.
The xml file:

    <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_timer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="60"
        android:textSize="30dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:padding="20dp"
        />

    <TextView
        android:id="@+id/startBtn"
        android:layout_width="160dp"
        android:layout_height="50dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_timer"
        android:text="START"
        android:gravity="center"
        android:background="@color/grey"
        android:textColor="@color/black"
        android:textStyle="bold"
        android:layout_margin="12dp"
        />
    <TextView
        android:id="@+id/pauseBtn"
        android:layout_width="160dp"
        android:layout_height="50dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/startBtn"
        android:text="PAUSE"
        android:gravity="center"
        android:background="@color/grey"
        android:textColor="@color/black"
        android:textStyle="bold"
        android:layout_margin="12dp"
        />
    <TextView
        android:id="@+id/resetBtn"
        android:layout_width="160dp"
        android:layout_height="50dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/pauseBtn"
        android:text="RESET"
        android:gravity="center"
        android:background="@color/grey"
        android:textColor="@color/black"
        android:textStyle="bold"
        android:layout_margin="12dp"
        />

</androidx.constraintlayout.widget.ConstraintLayout><br/>

MainActivity.kt file:

    package com.example.countdownapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.CountDownTimer
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    private var countdown_timer: CountDownTimer? = null
    private var time_in_milliseconds = 60000L
    private var pauseOffSet = 0L

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        tv_timer.text= "${(time_in_milliseconds/1000).toString()}"

        startBtn.setOnClickListener{
                starTimer(pauseOffSet)
        }

        pauseBtn.setOnClickListener{
            pauseTimer()
        }

        resetBtn.setOnClickListener{
                resetTimer()
        }
    }
    private fun starTimer(pauseOffSetL : Long){
        countdown_timer = object : CountDownTimer(time_in_milliseconds - pauseOffSetL, 1000){
            override fun onTick(millisUntilFinished: Long) {
                pauseOffSet = time_in_milliseconds - millisUntilFinished
                tv_timer.text= (millisUntilFinished/1000).toString()
            }

            override fun onFinish() {
                Toast.makeText(this@MainActivity, "Timer finished", Toast.LENGTH_LONG).show()
            }
        }.start()
    }

    private fun pauseTimer(){
        if (countdown_timer!= null){
            countdown_timer!!.cancel()
        }
    }

    private fun resetTimer(){
        if (countdown_timer!= null){
            countdown_timer!!.cancel()
            tv_timer.text = " ${(time_in_milliseconds/1000).toString()}"
            countdown_timer = null
            pauseOffSet =0
        }
    }
}
Amandeep Singh
  • 138
  • 2
  • 4
4

use Chronometer for minapi=24:

  <Chronometer
            android:id="@+id/timer_expire_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/spacing_m"
            android:countDown="true"
            android:textColor="@color/white"
            android:textSize="@dimen/text_size_huge"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            tools:targetApi="24" />

and in kotlin:

    binding.timerExpireTime.apply {
        base = SystemClock.elapsedRealtime()
        start()
    }
Sana Ebadi
  • 6,656
  • 2
  • 44
  • 44