Alex Karapanos
Alex Karapanos

Reputation: 955

Kotlin: call a function every second

I want to create a simple countdown for my game, when the game starts I want this function to be called every second:

fun minusOneSecond(){
  if secondsLeft > 0{
     secondsLeft -= 1
     seconds_thegame.text = secondsLeft.toString()
  }
}

I tried this:

var secondsLeft = 15

timer.scheduleAtFixedRate(
   object : TimerTask() {

      override fun run() {
         minusOneSecond()
      }

    },0, 1000
)   // 1000 Millisecond  = 1 second

But the app unfortunately stops, the 2nd time the run function is called

I just started with android development and Kotlin 3 weeks ago and so far I understand the most out of it.

With swift in Xcode I use this line and I thought something similar would work with Kotlin

setTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(minusOneSecond), userInfo: nil, repeats: true)

Upvotes: 58

Views: 93387

Answers (9)

milan pithadia
milan pithadia

Reputation: 870

if you use any background task or background service try this code

val timer = Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate({
            Log.d("RUNNING ","Thread")
        },0,10,TimeUnit.SECONDS)

if you work with UI thers like update UI layout try this code

val timer = Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate({
            Log.d("RUNNING ","BACKGROUN Thread")
            runOnUiThread {
                Log.d("RUNNING ","Update UI Thread")
                btnUpdate.setText(System.currentTimeMillis().toString())
            }
        },0,1,TimeUnit.SECONDS)

Upvotes: 3

arkfalak
arkfalak

Reputation: 1

var isActionAchieved = false
var secondsPassed = 0

fun cDTimer(){
    if (!isActionAchieved && secondsPassed < 10){  // repeat check if Action NOT Achieved for max of 10 seconds 
        Handler(Looper.getMainLooper()).postDelayed({
            repeatThisFunction()
            repeater()
            secondsPassed++
        }, 1000) //one second till next execution
    }
}

fun repeater(){
    cDTimer()
}

Upvotes: 0

Thiago
Thiago

Reputation: 13302

My solution

viewModelScope.launch(Dispatchers.IO) {
            while(isActive) {
                when(val response = repository.getApi()) {
                    is NetworkState.Success -> {
                        getAllData.postValue(response.data)
                    }
                    is NetworkState.Error -> [email protected] = false
                }

                delay(API_CALL_DELAY)
            }
        }

Upvotes: 3

Adil Aslam Sachwani
Adil Aslam Sachwani

Reputation: 964

val timer = object: CountDownTimer(10000, 1000) {
    override fun onTick(millisUntilFinished: Long) {
        // do something
    }
    override fun onFinish() {
        // do something
    }
}
timer.start()

You can also use CountDownTimer for this purpose. As this takes two parameters (the total time and the interval time)
Plus it also provides an on finish method to perform any task when the total time is finished.

Upvotes: 11

Hussain Tarek
Hussain Tarek

Reputation: 31

I'm using recursion with Coroutine its very simple

  private fun loop() {
    CoroutineScope(IO).launch {
        delay(5000)
        CoroutineScope(Main).launch {
            ManagerToWorker()
            loop()
        }
    }
}

Upvotes: -1

Aftab Alam
Aftab Alam

Reputation: 2049

I am calling my function every second like this

val handler = Handler()
  handler.postDelayed(object : Runnable {
      override fun run() {
            //Call your function here
            handler.postDelayed(this, 1000)//1 sec delay
        }
}, 0)

Upvotes: 4

Son Truong
Son Truong

Reputation: 14173

Problem: Timer class uses a background thread with a queue to queue and execute all tasks sequentially. From your code, because you update UI (changing TextView content in minusOneSecond function). That why the app throws the following exception and make your app crash.

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

Solution: There are many ways to achieve your task, but I prefer using post() and postDelayed() method from Handler class. Because it's simple and easy to understand.

val mainHandler = Handler(Looper.getMainLooper())

mainHandler.post(object : Runnable {
    override fun run() {
        minusOneSecond()
        mainHandler.postDelayed(this, 1000)
    }
})

Update: From author's comment about how to pause/resume the task from Handler. Here is an example.

class MainActivityKt : AppCompatActivity() {

    lateinit var mainHandler: Handler

    private val updateTextTask = object : Runnable {
        override fun run() {
            minusOneSecond()
            mainHandler.postDelayed(this, 1000)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Your logic code
        ...
        mainHandler = Handler(Looper.getMainLooper())
    }

    override fun onPause() {
        super.onPause()
        mainHandler.removeCallbacks(updateTextTask)
    }

    override fun onResume() {
        super.onResume()
        mainHandler.post(updateTextTask)
    }

    fun minusOneSecond() {
        if secondsLeft > 0 {
            secondsLeft -= 1
            seconds_thegame.text = secondsLeft.toString()
        }
    }
}

Upvotes: 87

Sandy Genedy
Sandy Genedy

Reputation: 226

please use

inline fun Timer.schedule(
    time: Date, 
    period: Long, 
    crossinline action: TimerTask.() -> Unit
): TimerTask

reference: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.concurrent/java.util.-timer/schedule.html

Upvotes: 12

gmetax
gmetax

Reputation: 3973

I am using this code to update a clock every minute

 fixedRateTimer("timer", false, 0L, 60 * 1000) {
     [email protected] {
         tvTime.text = SimpleDateFormat("dd MMM - HH:mm", Locale.US).format(Date())
     }
 }

so you have to run it with paratemer 1000 instead of 60*1000

Upvotes: 21

Related Questions