相关文章推荐
儒雅的椅子  ·  南京市人民政府·  6 月前    · 
文雅的数据线  ·  android - Error: The ...·  1 年前    · 
沉着的投影仪  ·  How to install or ...·  1 年前    · 
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

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?

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 – WasabiTea Jan 8, 2019 at 23:19
val timer = object: CountDownTimer(20000, 1000) {
    override fun onTick(millisUntilFinished: Long) {...}
    override fun onFinish() {...}
timer.start()
                @AkashChaudhary I think android suggets to use this method instead. I think I saw it in one codelab.
– Hanako
                Mar 31, 2021 at 22:51
    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.

This should be the accepted answer, there is a solid control over which dispatcher on which the work will happen. – abd3lraouf Jan 15, 2021 at 11:37 Very useful solution. Can be injected as an object and can be used independently using proper injection as well. Good job @Dima – A S M Sayem Jun 14, 2022 at 12:14 I tried this solution, it works however timer starts at its own,without calling, I am sure it's not called by any of my code, but register in logs, am I missing any concept of coroutine timer? – Kharak Dec 15, 2022 at 14:49

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

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, and its DateTimeFormatter. Yes, you can use it on Android. For older Android see How to use ThreeTenABP in Android Project. – Ole V.V. Dec 14, 2019 at 11:17 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, 2019 at 18:24 There’s a backport. To use java.time with min SDK 21 (for example), add 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, 2019 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, 2022 at 8:00

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()

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()
                Just beware that unlike CountdownTimer, the callback is called on a different thread than the one you start it from.
– Tenfour04
                Feb 15 at 16:20

CountDownTimer in Kotlin:

object: CountDownTimer(3000, 1000){
    override fun onTick(p0: Long) {}
    override fun onFinish() {
        //add your code here
 }.start()

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()
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

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()

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