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.
Related
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.
In my code, I have a time-out functionality and I want to use a countdown timer but after a lot of research, I couldn't find similar functionality as a countdown timer in Kotlin coroutine (able to start, cancel and catch finish callback). Then I decided to use GlobalScope.launch. I know this is a bad solution but my code is working perfectly.
Here is my code
viewModelScope.launch {
val timer = object: CountDownTimer(Constants.PAYMENT_TIMER, 1000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
GlobalScope.launch {
_eventFlow.emit(UIPaymentEvent.NavigateToBack)
}
}
}
timer.start()
collectPaymentIntentUseCase.invoke(currentPaymentIntent!!).onEach { result ->
when (result) {
is Resource.Success -> {
timer.cancel()
if (result.data?.exception == null) {
My question is how can find a 100% similar function to avoid using GlobalScope but be able to use the countdown timer (start, cancel,onComplete callback)?
Note: I am using GlobalScope.lanch to be able to emit UIPaymentEvent.NavigateToBack event to my view
You don't need a CountDownTimer here. Just use the delay() suspend function.
viewModelScope.launch {
val job = launch {
delay(Constants.PAYMENT_TIMER) // Wait for timeout
_eventFlow.emit(UIPaymentEvent.NavigateToBack)
}
collectPaymentIntentUseCase.invoke(currentPaymentIntent!!).onEach { result ->
when (result) {
is Resource.Success -> {
job.cancel() // Cancel the timer
if (result.data?.exception == null) {
You can use callbackFlow for listen your timer. I just code this editor. I hope it will be helpful.
fun timerFlow() = callbackFlow<UIPaymentEvent> {
val timer = object : CountDownTimer(10, 1000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
CoroutineScope().launch {
_eventFlow.emit(UIPaymentEvent.NavigateToBack)
}
}
}
timer.start()
awaitClose()
}
Coroutines are launched inside a CoroutineScope which are similar to lifecycle for android. As such, Android automatically provide coroutine's scope for components like activity or fragment and bound them to theirs lifecycle.
While it's not recommended to use the global scope that starts and ends with android's process. There are no restriction on creating your own and limiting it to a specific view of time. Creating one starts its life and cancelling it stops all tasks inside.
In your case a countdown can be done with only coroutines. As stated in this answer.
But without changing too much of your existing code you could reuse the viewModelScope that launched your timer to emit your event.
viewModelScope.launch {
_eventFlow.emit(UIPaymentEvent.NavigateToBack)
}
Beware of the life of your scope. If the viewmodelScope is dead when the timer finish, the event will never be sent.
I use Handler for creating a timer in a Widget.
I use the recommended constructor, i.e. passing a Looper to it.
private val updateHandler = Handler(Looper.getMainLooper())
#RequiresApi(Build.VERSION_CODES.Q)
private val runnable = Runnable {
updateDisplay()
}
#RequiresApi(Build.VERSION_CODES.Q)
private fun updateDisplay () {
updateHandler?.postDelayed(runnable, TIMER_MS)
// some other code
}
The TIMER MS is set to 3000 ms.
The timer runs fine for a while and execute the given code. However after a random time elapsed the timer stops working and no more execution of the given code happens.
Please advise what the problem could be ond how to fix it.
Alternatively, can I use some other timer? (The timer should go off every few second - this is the reason why I use Handler)
Thank you for any advice in advance
You could always try using a Coroutine for something like this:
class TimedRepeater(var delayMs: Long,
var worker: (() -> Unit)) {
private var timerJob: Job? = null
suspend fun start() {
if (timerJob != null) throw IllegalStateException()
timerJob = launch {
while(isActive) {
delay(delayMs)
worker()
}
}
}
suspend fun stop() {
if (timerJob == null) return
timerJob.cancelAndJoin()
timerJob = null
}
}
suspend fun myStuff() {
val timer = Timer(1000) {
// Do my work
}
timer.start()
// Some time later
timer.stop()
}
I haven't tested the above, but it should work well enough.
You can use CountDownTimer from Android framework to achieve the same. It internally uses Handler for timer
val timer = object: CountDownTimer(1000,1000){
override fun onTick(millisUntilFinished: Long) {
}
override fun onFinish() {
}
}
timer.start()
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)
private val timer = object : CountDownTimer(result, 1000) {
override fun onFinish() {
//delete the database entry
}
override fun onTick(millisUntilFinished: Long) {
//more code
}
}
As far as my knowledge in kotlin, object gets called before the result gets assigned a value
Initially, the result value is 0, then it gets updated in another function, but the timer gets called with result value as 0.
So what should be the best replacement for object here?
You can keep object, you just need to change order of initialization. One way would be to use by lazy, like this:
var result = 0L
private val timer: CountDownTimer by lazy {
object : CountDownTimer(result, 1000) {
override fun onFinish() {
// delete the database entry
}
override fun onTick(millisUntilFinished: Long) {
// more code
}
}
}
// 'init' block just as an example; the below code works anywhere
// such as in onCreate(), onStart() or wherever
init {
result = 1000
// 'timer' is initialized with result=1000 and then started
timer.start()
}
lazy is a so called property delegate, you can read more about it in the official docs for example.
I don't see how this is related to Kotlin?
You have a member val: timer
It is initialized when the object, the val resides in, is created.
So the problem is not what syntax you are using. It is the time you create timer. If you know when you are going to use it, and you are sure that by this time the result will be intialized, you can use lazy initialization.