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)
Timer import kotlin. concurrent. schedule fun main(args: Array<String>) { // Execution starting point println("Hello world!!") // Delay of 5 sec Timer(). schedule(5000){ //calling a function newMethod() } } fun newMethod(){ println("Delayed method call!") }
The postDelayed method takes two parameters Runnable and delayMillis . It adds the Runnable to the thread's message queue to be run after the specified amount of time elapses. The Runnable will execute on the thread to which this handler is attached.
You can use Handler to add a Runnable object or messages to the Looper to execute the code on the thread associated with the Looper. Android associates each Handler instance with a single thread and that thread's message queue. Whenever you create a new Handler instance, it is tied up to a single Looper .
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() } } }
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With