In my code I need to implement instant spam action for message. For this I change my message without waiting for server response. But sometime on server request errors I need to revert my message state to original. handler.removeCallbacks(runnable) not letting me know whether runnable has already run. How I can accomplish this? I also open to other suggestions to perform these delayed jobs via RxJava, or other technologies.
var handler = Handler()
fun spamMessage(originalMessage: ContentMessage) {
var runnable: Runnable? = null
val modifiedMessage = originalMessage.cloneUsingJson()
if (modifiedMessage != null) {
modifiedMessage.setSpamReported(true)
runnable = Runnable {
updateMessage(message)
}
updateWithDelay(runnable)
}
ApiClient.service.spamMessage(message.serverId).subscribeAsync(subscriptions, object : ApiSubscriberNew<Void>() {
override fun onFailure() {
var canceled : Boolean = handler.removeCallbacks(runnable)
if (!canceled) {
//if couldn't cancel, or was late, revert the changes
revertMessageUpdate(originalMessage)
}
}
})
}
private fun updateWithDelay(runnable: Runnable) {
handler.postDelayed(runnable, 100)
}
Related
I have this function:
suspend fun functionCall(): Job {
return MainScope().launch {
var i = 0
while(i < 3) {
i++
delay(3000)
yield()
}
}
cancel()
}
And I am calling from an external function when a button is clicked:
MainScope().launch {
if(functionCall().isActive) {
functionCall().cancelAndJoin()
}
}
Both of these functions are being run in a repository class.
And it is still iterating through the whole while loop even after the above if statement is triggered. What I'm noticing while debugging is "i" is also being reset to 0 which could indicate the job is being triggered more than once but it is definitely being triggered only once so I'm confused about what is happening.
What I want to happen is after that if statement for the entire job to cancel and for the entire function to return and run no more code.
I've also tried while(ensureActive) and the same thing is happening.
How do I do this?
Since this is Android, you should launch your UI-related coroutines from lifecycleScope. If you have a job that needs to survive screen rotations, you should launch it from inside a ViewModel from viewModelScope, and it must not touch any UI elements.
If you want to cancel a specific coroutine when an event happens, you should store that coroutine Job in a property so you can call cancel() on it. So a typical pattern inside an Activity for example might be:
private var fooJob: Job? = null
private fun fooSomething() {
fooJob = lifecycleScope.launch {
repeat(5) {
delay(1000)
Log.i("count", it.toString())
}
}
}
private fun cancelCurrentFoo() {
fooJob?.cancel()
}
Suppose you have a coroutine job you can start by calling one of the functions of your ViewModel, but you want the Activity/Fragment to be able to cancel it early. Then you expose a function that returns the coroutine Job:
fun foo() = viewModelScope.launch {
repeat(5) {
delay(1000)
Log.i("count", it.toString())
}
}
The Activity can call this function and it gets a Job instance in return that it can call cancel() on whenever it wants.
Can anyone see any reason why this would not be working?
override fun resetAnimations() {
Log.d("MainActivity", "start")
Handler().postDelayed( { reset()}, 1500)
Log.d("MainActivity", "end")
}
fun reset(){
Log.d("MainActivity", "reset")
}
I'm calling this in some arbitrary place in my activity but the reset() method is never called. In the logs I'm only getting the following
D/MainActivity: start
It looks like its blocking on postDelay.. even when I set the value to 1 or replace postDelay with pose, doesn't work.
Update:
when i implement like this, it works;
private lateinit var handler : Handler
override fun onCreate(savedInstanceState: Bundle?) {
handler = Handler()
resetAnimations()
}
override fun resetAnimations() {
handler.postDelayed( { reset()}, 1500)
}
I think the problem has something to do with the fact that I was calling resetAnimations() from a background thread, and creating the handler on the background thread..
I was calling resetAnimations() from a background thread.
That's the problem. Your handler couldn't post a Message onto a MessageQueue, because there isn't any MessageQueue on your background thread.
So, instead of this:
Handler().postDelayed( { reset()}, 1500)
Perform this:
Handler(Looper.getMainThread()).postDelayed( { reset()}, 1500)
I have some code like this in java that monitors a certain file:
private Handler mHandler = new Handler();
private final Runnable monitor = new Runnable() {
public void run() {
// Do my stuff
mHandler.postDelayed(monitor, 1000); // 1 second
}
};
This is my kotlin code:
private val mHandler = Handler()
val monitor: Runnable = Runnable {
// do my stuff
mHandler.postDelayed(whatToDoHere, 1000) // 1 second
}
I dont understand what Runnable I should pass into mHandler.postDelayed. What is the right solution? Another interesting thing is that the kotlin to java convertor freezes when I feed this code.
Lambda-expressions do not have this, but object expressions (anonymous classes) do.
object : Runnable {
override fun run() {
handler.postDelayed(this, 1000)
}
}
A slightly different approach which may be more readable
val timer = Timer()
val monitor = object : TimerTask() {
override fun run() {
// whatever you need to do every second
}
}
timer.schedule(monitor, 1000, 1000)
From: Repeat an action every 2 seconds in java
Lambda-expressions do not have this, but object expressions (anonymous classes) do. Then the corrected code would be:
private val mHandler = Handler()
val monitor: Runnable = object : Runnable{
override fun run() {
//any action
}
//runnable
}
mHandler.postDelayed(monitor, 1000)
runnable display Toast Message "Hello World every 4 seconds
//Inside a class main activity
val handler: Handler = Handler()
val run = object : Runnable {
override fun run() {
val message: String = "Hello World" // your message
handler.postDelayed(this, 4000)// 4 seconds
Toast.makeText(this#MainActivity,message,Toast.LENGTH_SHORT).show() // toast method
}
}
handler.post(run)
}
var handler=Handler()
handler.postDelayed(Runnable { kotlin.run {
// enter code here
} },2000)
I'd like my actor to wait for some event to occur, but I want it to still receive messages and proceed with messages. How can I achieve it?
My code is as follows:
class MyActor extends UntypedActor {
//onReceive implementation etc...
private void doSomething(ActorRef other){
String decision = (String) Await.result(ask(other, new String("getDecision"),1000), Duration.create(1, SECONDS));
while(decision.equals(""){
Thread.sleep(100)
decision = (String) Await.result(ask(other, new String("getDecision"),1000), Duration.create(1, SECONDS));
}
}
}
But this blocks entire actor until it receives proper decision. How can I achieve something like that without blocking my actor ?
That kind of code is the good candidate for the use of Futures.
You can find more information here: http://doc.akka.io/docs/akka/snapshot/java/futures.html
In your case, it would look like:
final ExecutionContext ec = context().dispatcher();
private void doSomething(ActorRef other){
Future<Object> decision = (Patterns.ask(other, new String("getDecision"), 1000));
decision.onSuccess(new OnSuccess<Object>() {
public void onSuccess(Object result) {
String resultString = (String) result;
System.out.println("Decision: " + result);
}
}, ec);
}
You should always try to avoid Await.result which like you said causes the thread to block. You can use callbacks such as onSuccess or onComplete to execute code once the future returns without waiting for the result.
The below code details an id read when a serial event happens,an id is generated every fews seconds when the device is powered on(Serial Event), and no serial data is received when it is powered off .problem is i need a url call to be sent once when the id is received and once when not visible(powered down).
I believe im close but cannot seem to get it right.I would be very grateful if someone could help with this and how to set flags and scheduler to achieve the above case and possibly explain where im going wrong.
int numberOfEmptyIds = 0;
int maxNumberOfAttempts = 5;
boolean urlSent = false;
long timeoutInMillis = 10000; // let's say 10000 millis, equivalent to 10 seconds
Timer timer = null;
public void connect(String portName) throws Exception {
...
scheduleTimer();
}
public void serialEvent(SerialPortEvent evt) {
if(evt.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
try {
while(in.read(buffer) > -1) {
String asHexStr = DatatypeConverter.printHexBinary(buffer);
if(asHexStr.contains("FB1")) {
scheduleTimer();
numberOfEmptyIds = 0;
} else {
numberOfEmtyIds++;
if(numberOfEmptyIds == maxNumberOfAttempts && !urlSent) {
// send the url here
}
}
}
} catch (IOException ex) {
// Log the exception here
}
}
}
private void scheduleTimer() {
timer = new Timer("Timeout");
TimerTask task = new TimerTask() {
#Override
public void run() {
if(!urlSent) {
// send the url here
}
}
};
timer.schedule(task, timeoutInMillis);
}
Problem is i need a url call to be sent once when the id is received
and once when not visible(powered down).
The second part is done by the timer, if no data arrives to the serial port then the scheduled task will sent the URL (if not sent yet). In my answer to your previous question I forgot to cancel the timer when the task is re-scheduled :
private void scheduleTimer() {
if(timer != null) {
timer.cancel();
}
timer = new Timer("Timeout");
TimerTask task = new TimerTask() {
#Override
public void run() {
if(!urlSent) {
// send the url here
}
}
};
timer.schedule(task, timeoutInMillis);
}
This way there would be a single scheduled task. From Timer.cancel() javadoc:
Terminates this timer, discarding any currently scheduled tasks. Does
not interfere with a currently executing task (if it exists). Once a
timer has been terminated, its execution thread terminates gracefully,
and no more tasks may be scheduled on it.
Note that calling this method from within the run method of a timer
task that was invoked by this timer absolutely guarantees that the
ongoing task execution is the last task execution that will ever be
performed by this timer.
About the first part you can manage it with boolean flags just like urlSent. If you need to send the URL just a single time then you can have a flag for the URL sent by ID arriving and another flag for URL sent due no data (or empty ID's) received.
Edit
Based on the flow-chart you've posted here and shown below:
Enter description here http://dl6.fileswap.com/storage_previews/02112014/54cd147697479d29c43c530b93d5fa83/52fe9168/aW1hZ2UvanBlZw%3D%3D/4cf59787af56f18847df6235cdc20816.jpg
You maybe can change your current approach using Timer.scheduleAtFixedRate() method to check at a fixed rate of time a if the serial port stops reading the ID. As you need to send the ID received notification just once then you may set urlSent flag to true when this URL is efectively sent. Also I think you can get rid of check if the received data doesn't contain the expected ID. Something like this:
boolean urlSent = false;
long lastIdArrivalTime = 0;
long timeTolerance = 60000;
long timeoutInMillis = 300000; // 5 minutes
Timer timer = null;
public void connect(String portName) throws Exception {
...
scheduleTimer();
}
public void serialEvent(SerialPortEvent evt) {
if(evt.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
try {
while(in.read(buffer) > -1) {
String asHexStr = DatatypeConverter.printHexBinary(buffer);
if(asHexStr.contains("FB100000010F0801")) {
lastIdArrivalTime = System.currentTimeMillis();
if(!urlSent) {
// send the URL notifying the ID
urlSent = true; // next time url will not be sent
}
}
}
} catch (IOException ex) {
// Log the exception here
}
}
}
private void scheduleTimer() {
timer = new Timer("Timeout");
TimerTask task = new TimerTask() {
#Override
public void run() {
long currentTime = System.currentTimeMillis();
if((currentTime - lastIdArrivalTime) >= timeTolerance) {
// sent the URL notifying the device is off
urlSent = false; // this way the next ID arrival will be notified
}
}
};
timer.scheduleAtFixedRate(task, timeoutInMillis, timeoutInMillis);
}
Some notes:
The timer is scheduled just once this time because it will execute the task every 5 minutes. If you need to shut down the connection don't forget to call timer.cancel() method.
The variable lastIdArrivalTime holds the last time in milliseconds when an ID arrives.
The variable timeTolerance is a max time tolerance to assume the connection is down. As you've said the device sends the ID at a seconds fixed period, so if spent 1 minute since the last ID arrival then you can assume the connection is down (or device is off).
Some hints on your code available here:
TimerTask implements Runnable interface and is intended to be used using Timer class which will create a separate thread to execute this task when the scheduled time comes, so don't use TimerTask in a new thread.
Don't mess with
Threads
unless you know exactly what are you doing. It's extremely easy make a
mistake and mess the things up.