I have a timer that is called from a button OnPressed: startTimeOut(60).
startTimeout(mins) async {
await subscription.resume();
print("susbscription started");
return new Timer(Duration(minutes: mins), handleTimeout);
}
void handleTimeout() async {
await subscription.cancel().then((_) {
print("susbscription canceled");
});
But the user has the possibility to abort the subscription stream by calling startTimeOut(0). In this case "susbscription canceled" get's printed but the first timer call is still active, so the subscription goes on merrily till the 60 minutes are over. Then "susbscription canceled" is printed again.
How could I sort of overwrite/cancel the first call to Timer?
You will need to keep a reference to the Timer instance and call cancel on subsequent calls to startTimeout.
Related
I'm opening my chat window programtically using ToggleChatVisibility which works great, but it does not fire the relevent FlexWebChat.Action
<script>
const operatingHoursCheckMsg = async function () {
Twilio.FlexWebChat.Actions.on("afterToggleChatVisibility", (payload) => {
console.log('Not Working');
});
}
await initateWebChat.init();
}
function Test() {
operatingHoursCheckMsg();
Twilio.FlexWebChat.Actions.invokeAction("ToggleChatVisibility");
}
</script>
<button type="button" onclick="Test()">Click to open and close chat window</button>
the afterToggleChatVisibility event fires if I close and reopen the chat using the chat box ui, but not if I click my button.
How can I trigger this event properly?
I think you have a race condition causing this issue. You defined the operatingHoursCheckMsg function as async even though there isn't an asynchronous call involved (though maybe there is in your full script) but in your Test function you do not wait for the promise to resolve before invoking the action. I think this means that JavaScript placed the promise on a queue to be handled asynchronously by the event loop, and then ran the next synchronous line of code. So you invoke the action before the event listener is registered.
It also looks as though you want to use the button to continue toggling the chat open and closed, so you should probably not be attaching a new listener every time the button is clicked.
I'd recommend you set up the one listener after you have initiated the webchat, like this:
<script>
const operatingHoursCheckMsg = async function () {
// Do operating hours check
}
await initateWebChat.init();
Twilio.FlexWebChat.Actions.on("afterToggleChatVisibility", (payload) => {
console.log('Chat toggled!');
});
}
async function Test() {
await operatingHoursCheckMsg();
Twilio.FlexWebChat.Actions.invokeAction("ToggleChatVisibility");
}
</script>
<button type="button" onclick="Test()">Click to open and close chat window</button>
I am calling one API every 10 sec using Observable.timer(0,10000) and unsubscribing on ngOnDestroy() method.But this call is not getting stopped and its keep getting adds in the queue and calling even after leaving that component.
I have tried to destroy the component on ngOnDestroy() function. But it's still its getting call.
it should have called only on this component and should call once every 10 seconds but if we go to other component call is getting added in the queue and once come back to instance component it is calling multiple time every 10 secs.
Code:
this._instanceSub = Observable.timer(0,reference.INSTANCE_CALL_INTERVAL) .subscribe(() => { this.getComonentInstance(this.componentId); }); ngOnDestroy() {
if (this._instanceSub) {
this._instanceSub.unsubscribe();
}
The above code must work i have used the below code for the same
this.subject = new Subject();
ngOnInit() {
timer(0, reference.INSTANCE_CALL_INTERVAL).pipe(
takeUntil(this.subject),
).subscribe(t => this.getComonentInstance(this.componentId));
}
and in ngOndestroy
ngOnDestroy() {
this.subject.next();
}
I have this Streamsubscription in a Stateless widget
StreamSubscription < LocationResult > subscription =
Geolocation.locationUpdates(
accuracy: LocationAccuracy.best,
displacementFilter: 30.0, // in meters
inBackground: true,)
.listen((result) {
if (result.isSuccessful) {
saveResult(result);
} else {
}
});
I call this in a timer
startTimeout(mins) async {
await subscription.resume();
print("susbscription started");
return new Timer(Duration(minutes: mins), handleTimeout);
}
void handleTimeout() async{ // callback function
await subscription.cancel();
print("susbscription canceled");
}
The timer is called on button click:
startTimeout(1);
After a minute susbscription canceled is printed but the Geolocator keeps getting called.
Future cancel ()
Cancels this subscription.
After this call, the subscription no longer receives events.
The stream may need to shut down the source of events and clean up
after the subscription is canceled.
Returns a future that is completed once the stream has finished its
cleanup.
For historical reasons, may also return null if no cleanup was
necessary. Returning null is deprecated and should be avoided.
Typically, futures are returned when the stream needs to release
resources. For example, a stream might need to close an open file (as
an asynchronous operation). If the listener wants to delete the file
after having canceled the subscription, it must wait for the cleanup
future to complete.
A returned future completes with a null value. If the cleanup throws,
which it really shouldn't, the returned future completes with that
error.
OK. Looked through the possible answers, but I don't see my issue here.
I have a fairly bog-standard GCD repeating timer:
class RepeatingGCDTimer {
/// This holds our current run state.
private var state: _State = ._suspended
/// This is the time between fires, in seconds.
let timeInterval: TimeInterval
/// This is the callback event handler we registered.
var eventHandler: (() -> Void)?
/* ############################################################## */
/**
This calculated property will create a new timer that repeats.
It uses the current queue.
*/
private lazy var timer: DispatchSourceTimer = {
let t = DispatchSource.makeTimerSource() // We make a generic, default timer source. No frou-frou.
t.schedule(deadline: .now() + self.timeInterval, repeating: self.timeInterval) // We tell it to repeat at our interval.
t.setEventHandler(handler: { [unowned self] in // This is the callback.
self.eventHandler?() // This just calls the event handler we registered.
})
return t
}()
/// This is used to hold state flags for internal use.
private enum _State {
/// The timer is currently paused.
case _suspended
/// The timer has been resumed, and is firing.
case _resumed
}
/* ############################################################## */
/**
Default constructor
- parameter timeInterval: The time (in seconds) between fires.
*/
init(timeInterval inTimeInterval: TimeInterval) {
self.timeInterval = inTimeInterval
}
/* ############################################################## */
/**
If the timer is not currently running, we resume. If running, nothing happens.
*/
func resume() {
if self.state == ._resumed {
return
}
self.state = ._resumed
self.timer.resume()
}
/* ############################################################## */
/**
If the timer is currently running, we suspend. If not running, nothing happens.
*/
func suspend() {
if self.state == ._suspended {
return
}
self.state = ._suspended
self.timer.suspend()
}
/* ############################################################## */
/**
We have to carefully dismantle this, as we can end up with crashes if we don't clean up properly.
*/
deinit {
self.timer.setEventHandler {}
self.timer.cancel()
self.resume() // You need to call resume after canceling. I guess it lets the queue clean up.
self.eventHandler = nil
}
}
Works great!
...except when it doesn't.
That would be when I put the device into Airplane Mode.
At that point, the timer stops firing.
Even when I come out of Airplane Mode, the timer doesn't restart.
The app uses UIApplication.shared.isIdleTimerDisabled = true/false to keep the app awake, but that doesn't seem to keep the events coming.
Can anyone clue me into what's happening here, and how I might be able to work around it?
This app needs to work in Airplane Mode. In fact, it is most likely to be used in Airplane Mode.
OK. I think I solved this. As is often the case with these things, it's PEBCAK.
I have a routine that stops the timer when the app is backgrounded, and failed to put in a corresponding restart for when it is foregrounded.
When I slide up the Control Center, it backgrounds the app.
My bad.
Yeah, it's embarrassing, but I want to leave this question here as a warning to others.
If I get multiple push notifications whilst app is in foreground. The callback method will execute every push notification one by one.
callback : function(e) {
if (e.inBackground == 1) {
//came from background - do something.
} else {
// Titanium.UI.iPhone.setAppBadge(null);
//check type, if it is chat.
if (type == 'chat') {
//check if window is already opened or not, if so fire event handler
if (currentWindow == '_chatWindow') {
//update view directly after entering app from the background. Fire event handler
Ti.App.fireEvent('_updateChat', {});
} else if (currentWindow == '_messages') {
//refresh messages screen if on messages screen and chat message arrives
//update view directly after entering app from the background. Fire event handler
Ti.App.fireEvent('_updateMessages', {});
} else {
//display local notification
}
}
If the push notification has come from the background it is easy to deal with, as the push notification that is activated is the one the user chooses to swipe. However, if multiple push notifications come into the foreground and say it's chat, it will execute them multiple times.
How can I handle push notifications in the foreground better? Thanks
Update:
tried this code without much luck
Ti.App.addEventListener('_displayNotification', function(e) {
//store all push notifications in array
var pushArray = [];
var countPushNotifications;
//currentTime to cross reference
var currentTime = new Date();
if (currentTime - Alloy.Globals.pushTime < 3000) {
//do something
pushArray.add(e.PushNotificationData);
} else {
//after 3 seconds remove event handler
//fire event to filter array and process notification, reset time for next event
Alloy.Globals.pushTime = null;
Ti.App.removeEventListener('_displayNotification', {});
}
//first push notification, will be the current time
if(Alloy.Globals.pushTime==null){
Alloy.Globals.pushTime = currentTime;
}
});
Trying to get all the push notifications inside the array, for further filtering.
Update 2:
if (Alloy.Globals.countPushNotificationsFlag == 1) {
Alloy.Globals.countPushNotificationsFlag = null;
setTimeout(function() {
Ti.App.fireEvent('_displayNotification', {
PushMessage : message
});
}, 6000);
} else {
Alloy.Globals.countPushNotificationsFlag = 1;
Ti.App.fireEvent('_displayNotification', {
PushMessage : message
});
}
I have tried to execute push notifications alternatively.
1st notification - fires instantly.
2nd notification - fires after 6 seconds.
3rd notification - instantly.
4th notification - fires after 6 seconds.
and so on...
however the code only works for
notification 1 and 2.
Fails when it hits the 3rd notification.
you can check if Push is received in foreground or background using inBackground Property of push here is the documentation
Hope it helps.
I haven't got the Titanium experience to give you actual code, but this is the approach you need to take:
When you receive a notification, check to see if a (global) boolean receivedNotification is false
If it is false, set it to true, store the event into a global and schedule a setTimeout function for, say, 3 seconds, to process the event and reset receivedNotification to false
If receivedNotification is true, update the global event to the newer notification
In the process event method that is triggered via the timer your will do what you currently do in the first section of code.
This will ensure that the event is processed no longer than 3 seconds after it is received and that events will be processed, at most, once every three seconds.
Your code looks pretty close, except you are trying to fire the first event immediately and then subsequent events after a delay. Unfortunately I don't believe that this is possible, because you have no way of seeing if there are events queued immediately. I think that you are always going to have to incur the delay, but you can tune the delay to find a balance between responsiveness and reduced API calls -
if (Alloy.Globals.countPushNotificationsFlag == null) {
Alloy.Globals.countPushNotificationsFlag = 1;
Alloy.Globals.messageToPush=message;
setTimeout(function() {
Alloy.Globals.countPushNotificationsFlag = null;
Ti.App.fireEvent('_displayNotification', {
PushMessage : Alloy.Globals.messageToPush
});
}, 3000);
}
else {
Alloy.Globals.messageToPush=message;
}