I'm trying to update an custom Dialog in real time So far I have the custom dialog working and looking how I want
The problem is that the dialog shows with no problem with the text "Loading...", it freezes for the amount of ms (from the sleep) and when its over it changes to "3"
I hope someone can give a little help, thanks!
Code
package com.slimakoi.myfirstapp
import android.annotation.SuppressLint
import android.app.Dialog
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
class TrackerActivity : AppCompatActivity() {
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_tracker)
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder().permitAll().build())
val dialog = Dialog(this)
dialog.setContentView(R.layout.notification)
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
val nMsg = dialog.findViewById<TextView>(R.id.notificationMessage)
val nImg = dialog.findViewById<ImageView>(R.id.notificationImage)
nMsg.text = "Loading..."
nImg.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.error))
dialog.show()
Handler(Looper.getMainLooper()).post {
runOnUiThread {
for (i in 0 until 5) {
nMsg.text = "1"
SystemClock.sleep(500)
nMsg.text = "2"
SystemClock.sleep(500)
nMsg.text = "3"
SystemClock.sleep(500)
}
}
}
}
}
CodePudding user response:
It's freezing because you're blocking the main thread with sleep
- nothing can happen while that's going on, because the system can't update the display or respond to touches until your code has finished executing. That's why it freezes up and you don't see any visual changes until your code finally finishes.
There are lots of ways to do what you're trying to do, which is run some code asynchronously - coroutines are the most modern, general-purpose way to make a task do a thing whenever, but here's how you can use a Handler
like you're trying to:
// get a reference to the handler, we're gonna reuse it
val handler = Handler(Looper.getMainLooper())
// declaring an object instead of a lambda (which is a shortcut for defining an object
// with a single function) so we can put a stateful variable in it
handler.post(object : Runnable {
// state variable, so we can keep track of where we're up to
var number = 0
override fun run() {
// update the current state and display it
number
nMsg.text = "$number"
// if we're not done yet, post this runnable to run again in 500ms
if (number < 5) handler.postDelayed(this, 500)
}
})
The point of posting runnables to a handler (especially a handler for the main thread) is that you're giving it a task to run when it can, without blocking the thread in the meantime. By posting one with a delay, you're saying "hey, run this at this time" and it'll do its best to hit that target, inbetween all the other tasks it's doing (like updating the display)
By doing Thread#sleep
(which is basically what SystemClock#sleep
is) you're saying "everything stop" and nothing happens until your code is ready to continue - all tasks are delayed, because your task hasn't finished yet, and part of what it's doing is grinding everything to a halt for several seconds
Notice up there I'm not using runOnUiThread
either, because you're explicitly running this code on the UI (main) thread anyway, by posting it to a handler for the main Looper
(the main thread's task queue if you like) - which is the whole reason the sleeping is a problem!
But if you like, you could run a dedicated thread for this, and sleep it all you like:
// create and start a new worker thread
thread(start=true) {
(1..5).forEach {
// now you do need to call this, because we're not on the main thread anymore
runOnUiThread { nMsg.text = "$it" }
// you could check 'it < 5' if you care about the thread sleeping
// an extra 500ms at the end
Thread.sleep(500)
}
}
or since you have access to a View
, you can post
messages through that, instead of needing to grab a Handler
for the main looper (the view already has one, so it lets you post through it as a convenience)
// instead of Activity#runOnUiThread
nMsg.post { nMsg.text = "$it" }
There are other concerns, like long-running tasks being active while your app is in the background, keeping stuff like activities in memory, etc. If it's something very short like this it's probably fine, but really you should be keeping references to stuff you create, cancelling threads and posted runnables when appropriate, things like that. Lifecycle-aware stuff like coroutines can help a lot, but that's all a different topic!