handler.postDelayed cannot update boolean variable - android

I'm trying to make a code that wait until a result presents (provided by a service) or a second has passed.
My code can detect a change in result variable but cannot detect a change made to "waiting" variable. I update waiting variable in a runnable, executed with Handler.postDelayed(). I've tried using #Volatile, but still no luck.
var waiting = true
var r = Runnable {
waiting=false
Log.d("Timeout", "timeout")
}
val handler = Handler()
handler.postDelayed(r,1000)
while(returnCall==null && waiting){continue;}
// handler.removeCallbacksAndMessages(null)
I expected an object from a service or skip the process after waiting for a second. The code never goes beyond the while statement. "timeout" is never printed to Logcat.
If returnCall has been assigned, the code does proceed AND the Runnable is executed as expected (prints timeout to Logcat).
EDIT:
I use this code inside a callback function
var jsInterfaceCallback = object : MyJsInterface.myJsInterfaceCallback(){
override fun onCommand (command : String, data : HashMap<String, Any>) : MutableMap<String, Any>{
returnCall = null
// Pass intent to service
var waiting = true
var r = Runnable {
waiting=false
Log.d("Timeout", "timeout")
}
val handler = Handler()
handler.postDelayed(r,1000)
while(returnCall==null && waiting){continue;}
// handler.removeCallbacksAndMessages(null)
if(returnCall != null){
return returnCall!!
}
return mutableMapOf()
}
}

Since you are using a spin lock to block the thread, the Runnable posted on the same thread has no chances to execute (the thread is always busy doing the while loop)
Instead of using while loop to detect variable change, you should write a callback function to do the follow up action and call it when timeout or your service produced the result.
fun callback(result : YourResultType?)
{
if(result != null)
{
//handle the result produced by the service
}
else
{
//timeout, do what ever you want to follow up
}
}
var r = Runnable {
Log.d("Timeout", "timeout")
callback(null)
}
val handler = Handler()
handler.postDelayed(r,1000)

Related

Kotlin: Handler is causing my UI Thread to freeze

I want to call my function indefinitely every 1 second in a specific situation. I'm using AsyncTask to execute my API calls.
I'm using this type of call for some time but this is the first time when it actually blocked my UI Thread and I don't know why.
The handler in the code below is called inside onPostExecute.
protected fun purchaseCheck(transactionId: String){
app.sysLog("Wait for purchase...")
task = asyncTask({
api.checkPaymentStatus(transactionId)
}, taskName = "Purchase Status") { r ->
r.js?.let {
when(r.httpCode){
HTTP_PAYMENT_CHECK_PENDING -> {
App.log("purchaseCheck: response pending purchase - try again")
MainActivity.afterDelay(1000){
purchaseCheck(transactionId)
}
}
else -> {
App.log("purchaseCheck: response purchase success")
onPurchaseSuccessfullyCompleted()
}
}
}?:kotlin.run {
when(r.httpCode){
HTTP_PAYMENT_CARD_EXPIRED -> {
App.log("purchaseCheck: response card expired")
showApiErrorAndRetry(r, App.getString("err_purchase_card_expired"))
}
else -> {
App.log("purchaseCheck: response error (retry)")
MainActivity.afterDelay(1000){
purchaseCheck(transactionId)
}
}
}
}
}
}
Basically
MainActivity.afterDelay(1000){
purchaseCheck(transactionId)
}
is causing my ProgressBar animation to freeze. When I remove that delay it is working as intended.
Here is afterDelay function:
fun afterDelay(delay: Int, body: () -> Unit): Cancellable {
class DelayRun : Runnable, Cancellable {
override fun run() = body()
override fun cancel() {
removePost(this)
}
}
return DelayRun().also {
post(delay, it)
}
}
fun removePost(runnable: Runnable) {
App.handler.removeCallbacks(runnable)
}
fun post(delay: Int, runnable: Runnable){
App.handler.postDelayed(runnable, delay.toLong())
}
Handler in Application class:
class App : Application(), Application.ActivityLifecycleCallbacks{
companion object {
val handler = Handler()
}
...
}
Edit:
After suggestion from post below I implemented Handler like this:
class App : Application(), Application.ActivityLifecycleCallbacks{
companion object {
val handler: Handler by lazy {
HandlerThread("MyHandlerThread").let {
it.start()
Handler(it.looper)
}
}
}
...
}
but it is still freezing my UI Thread. (ProgressBar is lagging)
By default, Handler posts tasks on Main (UI) thread. Therefore any job/task you send to your handler will be executed on UI thread - that is the reason why UI freezes - it waits for job to finish before redrawing.
You want to make your handler using another thread. The most simple way is to create HandlerThread.
val handlerThread = new HandlerThread("MyHandlerThread")
handlerThread.start()
val looper = handlerThread.getLooper()
val handler = new Handler(looper)
After these four lines of code, handler will execute it jobs on another thread. But let's take a problem further - you are using AsyncTask, which is deprecated. You also do not want your delay to be counted by afterDelay function, handler can do it for you.
In your case you can just do something like this:
handler.postDelayed(1000, { ... your job ... }).
Getting it together:
protected fun purchaseCheck(transactionId: String){
app.sysLog("Wait for purchase...")
val runnable = {
val status = api.checkPaymentStatus(transactionId)
status.js?.let { ... }
}
handler.postDelayed(1000, runnable)
}
I also recommend you to declare handler on Activity level rather than Application. Since you usually don't want it to be global.

The current thread must have a looper

Here I am trying to add a view over main_layout for a specific time duration, for that I write this
main_layout.addView(linearLayout)
Handler(Looper.getMainLooper()).post(Runnable {
val timer = Timer()
timer.schedule(object : TimerTask() {
override fun run() {
main_layout.removeView(imageView)
timer.cancel()
}
}, 8000)
})
but app gets crash with the error :
java.lang.IllegalStateException: The current thread must have a looper!
kindly let me know, what else I have to do here.
I have resolved my problem by using HandleMessage() method in handler.
mainHandler = object :Handler(){
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
if (msg!!.what ==1){
removeView()
}
}
}
private fun removedata() {
main_layout.removeView(imageView)
}
also, to perform the desired operation, this method sends the required sets of instruction
mainHandler.sendEmptyMessageDelayed(1,5000)
so as you can see that to call remove(), it requires 1 to fullfil the condition and second parameter is the desired delay I wanted to set.

Kotlin - How to pass a Runnable as this in Handler

I'm beginner in kotlin. I try to create a task that will repeat every 2 seconds. So I created something like this.
val handler = Handler()
handler.postDelayed(Runnable {
// TODO - Here is my logic
// Repeat again after 2 seconds
handler.postDelayed(this, 2000)
}, 2000)
But in postDelayed(this) it gives error - required Runnable!, found MainActivity. I've tried even this#Runnable but it didn't work.
But when I write the same function like this, it works
val handler = Handler()
handler.postDelayed(object : Runnable {
override fun run() {
// TODO - Here is my logic
// Repeat again after 2 seconds
handler.postDelayed(this, 2000)
}
}, 2000)
So why the this keyword doesn't work in first function, but in second function it works good?
You have several options to go about here:
make both the runnable and the handler be in the same scope
//class scope
val handler = Handler()
val runnable = object : Runnable {
override fun run () {
handler.removeCallbacksAndMessages(null)
//make sure you cancel the
previous task in case you scheduled one that has not run yet
//do your thing
handler.postDelayed(runnable,time)
}
}
then in some function
handler.postDelayed(runnable,time)
You can run a timertask, which would be better in this case
val task = TimerTask {
override fun run() {
//do your thing
}
}
val timer = Timer()
timer.scheduleAtFixedRate(task,0L, timeBetweenTasks)
The first one is a function that accepts a lambda and returns a Runnable. In this case this means nothing.
The second one you're defining an anonymous object that implements Runnable. In this case this refers to that object instance.
The below example will work.
val runnable = object : Runnable {
override fun run() {
handler.postDelayed(this,1000)
}
}
In your case , when use this it means "local final class <no name provided> : Runnable" , refer to a header runnable.
runnable=object : Runnable {
override fun run() {
// i is a counter
println("No. "+i++)
// Repeat again after 2 seconds
handler.postDelayed(this, 2000)
}
}
handler.postDelayed(runnable,0)
Where runnable is used inside of a method. As Handler() is deprecated, we must use like this:
var handler: Handler = Handler(Looper.getMainLooper())
var runnable: Runnable = Runnable { }
Moreover, anywhere you can stop this method by:
handler.removeCallbacks(runnable)

Kotlin Handler class: How to stop a thread - from inside of run() - when a condition is met?

I am currently learning how to develop android applications. For my first app, I wanted to build a simple clicker with a primitive combat sistem.
In my code, I have created a Handler instance and a Runnable object. Inside of this "delayed loop" the enemy attacks the player instance every 3 seconds, dealing damage. Inside the loop I also test when the player's hp is less or equals 0; when the condition is met I want to stop the runnable object.
How do i do this? I tried calling a function stopCombat() which contains Handler.removeCallbacks, but i can't call a function before declaration. I tried putting .removeCallbacks(this) inside run(), but it also doesn't work.
val mainHandler = Handler(Looper.getMainLooper())
fun playerDied() {
ongoingFight = false
combatLog.append("${myPlayer.name} has been defeated!\n")
myPlayer.currentHitPoints = myPlayer.maxHitPoints / 2
myPlayer.gold = 0
gold.text = "${myPlayer.gold} gold"
}
val performTask = object : Runnable {
override fun run() {
val enemyHit = enemyAttack(myPlayer, myEnemy)
// Call a function to stop this thread if the condition is met:
if (myPlayer.takeDamage(enemyHit)) { //myPlayer.takeDamage returns true if hp <= 0
playerDied()
stopCombat() // called before declaration
mainHandler.removeCallbacks(this) // tried, doesn't work
}
playerHP.text = "${myPlayer.currentHitPoints} / ${myPlayer.maxHitPoints}"
combatLog.append("${myEnemy.name} deals $enemyHit damage!\n")
mainHandler.postDelayed(this, 3000)
}
}
fun stopCombat(){
mainHandler.removeCallbacks(performTask)
}
Set a boolean to determine if you’re going to post the runnable again
override fun run() {
val enemyHit = enemyAttack(myPlayer, myEnemy)
val died = myPlayer.takeDamage(enemyHit))
if (died)
playerDied()
playerHP.text = "${myPlayer.currentHitPoints} / ${myPlayer.maxHitPoints}"
combatLog.append("${myEnemy.name} deals $enemyHit damage!\n")
if (!died)
mainHandler.postDelayed(this, 3000)
}
Sorry for formatting—on my phone.
I notice that you are using kotlin. I think you can use coroutines. It's more concise and lightweight. Cancellation and Timeouts
If you still want to use thread. Try the following code.
val executorService = Executors.newSingleThreadExecutor()
val runnables = Runnable {
}
val task = executorService.submit(runnables)
task.cancel(true)

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

Categories

Resources