Dang Hoang
Dang Hoang

Reputation: 171

How to run a Runnable every second to update the UI

I am trying to code in kotlin android to move an image every second but I am not able to make it work. Right now I'm using a Timer to schecule a Timer Task every second but it is not working as expected.

Here is my code

class Actvt_Image<float> : AppCompatActivity() {

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

        val pict_mario = findViewById<ImageView>(R.id.img_Mario)
        val bt_down = findViewById<Button>(R.id.bt_down)
        val frame = findViewById<LinearLayout>(R.id.frame)
        val txt1=findViewById<TextView>(R.id.txt1)

        var i =100
        val timer = Timer()
        val myTask = object : TimerTask() {
            override fun run() {

                txt1.text = (i+1).toString()
                img_Mario.rotation=180f
                img_Mario.translationX +=100
                img_Mario.translationY +=20
            }
        }

        bt_down.setOnClickListener {

            i=0
            timer.schedule(myTask, 1000, 1000)

        }
    }

}

Upvotes: 4

Views: 2980

Answers (2)

Syed Ahmed Jamil
Syed Ahmed Jamil

Reputation: 2126

You are trying to update the UI on a background thread which is not possible. UI can only be updated on the UI thread. Also, using a Timer and TimerTask to create and destroy a thread every 1 second is not the right way to use threads because creating a thread is a memory expensive operation.

What you need to do is to use a Handler and tell the UI Thread to run a Runnable after every desired interval. Remove Timer and TimerTask and use the following

val handler = Handler(Looper.getMainLooper())
handler.post(object : Runnable {
            override fun run() {
                txt1.text = (i+1).toString()
                img_Mario.rotation=180f
                img_Mario.translationX +=100
                img_Mario.translationY +=20

                handler.postDelayed(this, 1000)
            }
        })

Above code is using a handler and posting a task to the UI Thread message queue. The task itself is updating the UI and posting itself again to the UI Thread message queue using the same handler but this time after 1 second delay using the handler.postDelayed() methond

EDIT : How to stop runnable

If you want to stop a specific runnable you can use the following method and pass in the same runnable object that you passed in handler.post(). Surely you have to keep a reference to the runnable at all time to stop it. The above code doesn't keep a reference. See the Complete code below.

handler.removeCallbacks(runnable) //stops a specific runnable

To stop all remaining callbacks or runnable from the UI Thread message queue use this

handler.removeCallbacksAndMessages(null) //stops any pending callback in message queue

Complete code

NOTE: I have added a stop button click listener as an addition

class Actvt_Image<float> : AppCompatActivity() {

        private lateinit var handler : Handler
        private lateinit var runnable : Runnable // reference to the runnable object
        private var i = 0

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

            val pict_mario = findViewById<ImageView>(R.id.img_Mario)
            val bt_down = findViewById<Button>(R.id.bt_down)
            val bt_stop = findViewById<Button>(R.id.bt_stop)
            val frame = findViewById<LinearLayout>(R.id.frame)
            val txt1=findViewById<TextView>(R.id.txt1)

            handler = Handler(Looper.getMainLooper())
            runnable = Runnable {
                i++
                txt1.text = i.toString()
                img_Mario.rotation=180f
                img_Mario.translationX +=100
                img_Mario.translationY +=20
                handler.postDelayed(runnable, 1000)
            }

            bt_down.setOnClickListener {

                handler.post(runnable)

            }

            bt_stop.setOnClickListener {
                //Use this to stop all callbacks
                //handler.removeCallbacksAndMessages(null)

                handler.removeCallbacks(runnable) 

            }
        }

    }

Read more about processes, threads and handler here : https://developer.android.com/guide/components/processes-and-threads https://developer.android.com/reference/android/os/Handler

Upvotes: 2

Dang Hoang
Dang Hoang

Reputation: 171

I have one code and it run as I expected

     val t = object : Thread() {
        override fun run() {
            while (!isInterrupted) {
                try {
                    Thread.sleep(1000)  //1000ms = 1 sec
                    runOnUiThread {
                        i++

                        txt1.text = i.toString()
                        img_Mario.rotation=180f
                        img_Mario.translationX +=20

                    }

                } catch (e: InterruptedException) {
                    e.printStackTrace()
                }

            }
        }
    }

    bt_down.setOnClickListener {
        i=0
        t.start()
    }

Upvotes: 0

Related Questions