cordova-plugin-local-notifications
cordova-plugin-local-notifications copied to clipboard
Code inside notification on click does'nt execute when app is killed in ios.
Hello, i want to execute some code inside notification.on('click') this works well when the app is in front on in the background but does'nt when the app is killed on ios. This does work on android even if the app is killed but it does'nt on ios.
Your Environment
- Plugin version: ^0.9.0-beta.3
- Platform: IOS
- OS version: 12.1
- Device manufacturer / model: IPHONE 6
- Cordova version (
cordova -v
): 8.1.2 - Cordova platform version (
cordova platform ls
):4.5.5 - Plugin config
- Ionic Version (if using Ionic) ionic 4
Expected Behavior
When clicking on the notification we should get redirected to the specified page.
Actual Behavior
When clicking on the notification the app starts and opens the home page. It does'nt redirect.
Steps to Reproduce
In my app.component.ts :
initializeApp() {
this.platform.ready().then(() => {
this.localNotifications.on('click').subscribe((data) => {
this.router.navigateByUrl('/notification/' + data.id);
});
});
This works well in android in the three different states of the app, but does'nt on IOS when the app is killed.
Context
Im trying to execute the code inside the click event, it does work when the app is opened or in the background but not when the app is killed.
Debug logs
Include iOS / Android logs
- ios XCode logs
- Android: $ adb logcat
+1 Getting exactly the same results with Ember CLI
Im trying to execute the code inside the click event, it does work when the app is opened or in the background but not when the app is killed.
Plugin version: ^0.9.0-beta.2 Platform: IOS OS version: 12.2 Device manufacturer / model: IPHONE 6 Cordova version (cordova -v): 7.0.1 Cordova platform version (cordova platform ls): ios 5.0.0 Ember CLI Version ember-cli: 3.6.1 node: 8.11.2 os: darwin x64
HI, I am also having the same issue. To achieve this we need to get the launchDetails in ondeviceready event. There you can find the notification id. Based on the ID you can do whatever you have return in on click event.
Here I have query to development team. I can get only notification id and event in launch details object. Can you please send the Data as well in the LaunchDetails Object? So we can get more details and according to that data we can navigate to some other page instead home screen
And one more query. Can you add any deeplink in the data object and when click notification, launch the deep link so that app will launch the required page directly instead home screen?
Thanks Mani
Anybody solved this?
HI, I am also having the same issue. To achieve this we need to get the launchDetails in ondeviceready event. There you can find the notification id. Based on the ID you can do whatever you have return in on click event.
Here I have query to development team. I can get only notification id and event in launch details object. Can you please send the Data as well in the LaunchDetails Object? So we can get more details and according to that data we can navigate to some other page instead home screen
And one more query. Can you add any deeplink in the data object and when click notification, launch the deep link so that app will launch the required page directly instead home screen?
Thanks Mani
Can you provide sample code for launch details? Because we have tried multiple time its doesn't work also getting LaunchDetails is undefined.
you use the below code for checking the launchdetails on device ready event
cordova.plugins.notification.local.launchDetails
I determined this problem also on Android platform. My analysis point to a simple fact: The LocalNotification#fireEvent is performed before a listener could even be registered at app startup. I also registered my listener within app.component.ts, but unfortunately this is simply too late in real application scenarios. It's a timing issue.
The "deviceready" event of the (native) plugin is called before the actual "deviceready" is performed on Typescript side (not Javascript side!). So there is actually no chance to handle the click event on app cold start with things like Ionic's ```Platform.ready()`` within your app components at all.
I implemented a (really ;) ) "hacky" workaround, which does the trick by intercepting the click event: (workaround goes directly into index.html)
<body>
...
<script>
document.addEventListener("deviceready", function(){
var cordovaPluginsNotificationLocalCoreFireEvent = cordova.plugins.notification.local.core.fireEvent;
cordova.plugins.notification.local.core.fireEvent = function(name, notification, event) {
window.appStart = { notification: notification };
cordovaPluginsNotificationLocalCoreFireEvent.call(cordova.plugins.notification.local.core, name, notification, event);
}
});
</script>
</body>
With that you are able to retrieve an once occurred local notification afterwards when you app code actually is up an running by checking:
let appStart: { notification: { id: number }};
if(window['appStart']) {
appStart = window['appStart'];
if(appStart.notification) {
callback(appStart.notification);
}
}
EDIT 2 Another thing I stumbled into: For me I had to schedule notifications for 3 days (~180 pushs). Did not work. There is a limit of 64 local notifications which can be scheduled.
So I ended up having 2 queues. The ones which are to be fired and the ones which should be fired. And then always updating onResume and on user interaction when the user canceled some out.
EDIT: Made some important additional notes at the end!
@guntherhoppe @pandeeswaran @adamalexander @kasback @petenickless @maniveltvl
There are different things one needs to consider if using a framework like Angular and Ionic.
- Setup event listener before the events fire
- Run code to these events in the right context
First app start: What happens?
Usual setup
-
Our app starts, native code gets executed
-
Native local push plugin code gets executed and stores (queues) native information for later when our HTML5 world is available
-
Parallel a WebView gets instantiated and loads the index.html, which then loads everything we need for our HTML5 app. 3.1 Async loading all JS plugin files (the bridge between our HTML5 and the native world). 3.2 Async loading our angular / ionic application file/s.
-
The local-notification.js file loaded first. 4.1 local-notification.js attaches and listens for the
deviceready
event. 4.2 All plugins loaded and thedeviceready
event fires. 4.3 By default the following code fireQueuedEvents gets executed on thedeviceready
event. 4.4* Now all queued events from our local push plugin fire: None at this point (first app start, remember?).
*We jump later back here
- Meanwhile the angular and ionic files necessary to bootstrap our application loaded.
5.1 Our application gets bootstrapped
5.2 We even maybe go crazy and lazy load a LocalPushModule where we store all our Push handling.
5.3 In Angular / Ionic: Wait for the platform ready
await this.platform.ready();
5.4 Now the plugins are there for sure.Setup the event listeners
.
Run code in the right context
Angular uses zoneJS for change detection. Sometimes something is triggered in pure JS and your code gets executed (you would see an alert message or a console.log), but the change detection of angular does not get triggered and anything you would write in there to get updated in the angular / ionic world, would not take effect. Therefore we use NgZone
and run our code to be executed in the angular zone and change detection will work - also the routing.
import { Injectable } from '@angular/core';
@Injectable({providedIn: 'root'})
export class PushService {
constructor(
private readonly platform: Platform,
private readonly router: Router,
private readonly ngZone: NgZone) {
this.init();
}
private get localNotifications() {
return cordova && cordova.plugins.notification && cordova.plugins.notification.local;
}
private async init(): Promise<void> {
await this.platform.ready();
this.localNotifications.on('click').subscribe(notification => {
this.ngZone.run(() => {
this.router.navigateByUrl('/notification/' + notification.id);
});
});
}
}
-
Any events? Nope. Let's create and schedule some (2x: one push in 10 and another one in 30 seconds)! 6.1 App runs in background, bootstrapped and listening for our events. 6.2 Push comes - we do tap the push - the click event fires, our subscribe block gets executed in the angular context, change detection runs and voila, we did route to the notification detail page.
-
Let's force quit / close the app and wait for the 2nd push to arrive and tap it.
We start again at the top at point 1 and go up to point 4.4. This time we do start the app the 2nd time and we do have one queued event! Wooohoo! Proceed as before and fire and forget all the queued events!
Does it now work as before when the app was in front- or background? No
Did we already had setup our listeners in Angular / Ionic before the push plugin fired the event? No - We did not setup event listener before the events fired from local-notification.js.
Will the click subscription not just fire after we subscribed because there was some data and someone should have stored it? No - It makes no sense firing events just when someone did subscribe. Just because you subscribed multiple times, does not mean you clicked multiple times.
Is there some data now? No - We were too late. The push click event did fire just a moment before.
Can we fix this? Yes
Can we skip initial firing the events of the local push plugin and postpone the events until we know that we have setup everything in our application (HTML5 world)?
Yes - As you've maybe seen the skipLocalNotificationReady is being checked before firing the events. Set the global skipLocalNotificationReady
variable to true
before the local push plugin is loaded.
Working setup
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<script>
window.skipLocalNotificationReady = true;
</script>
<body>
<app-root></app-root>
</body>
</html>
Can we now manually trigger the queued events of the local push plugin?
Yes - Modify our PushService to fire the queued events with calling the fireQueuedEvents()
method:
import { Injectable } from '@angular/core';
@Injectable({providedIn: 'root'})
export class PushService {
constructor(
private readonly platform: Platform,
private readonly router: Router,
private readonly ngZone: NgZone) {
this.init();
}
private get localNotifications() {
return cordova && cordova.plugins.notification && cordova.plugins.notification.local;
}
private async init(): Promise<void> {
await this.platform.ready();
console.log("launchDetails", cordova.plugins.notification.local.launchDetails);
this.localNotifications.on('click').subscribe(notification => {
this.ngZone.run(() => {
this.router.navigateByUrl('/notification/' + notification.id);
});
});
this.localNotifications.fireQueuedEvents();
// ^ THIS IS NEW
}
}
Hope this helps someone.
EDIT: Additional notes
The above ONLY applies to the "CLICK" event ( cordova.plugins.notification.local.on('click',..)
.
I have setup alert messages for the click and trigger event. The click always came after a tap on the Notification (App foreground, App background and App was closed (did force quit the app)).
The trigger
event however did ONLY appeared when the app was in foreground. It did NOT fire the trigger event, when my app was in background and I then opened the app (via click or just opened the app by tapping the app icon).
Can we work around this? Yes
How? Save a list of all push alerts you want to trigger with their time locally on the device (persistently, not local storage). Update the list also, if you need to and times change etc.
Then on app start and on resume, compare the device time with the time of all notifications.
Attention: The timeout is needed, in case some native plugins will be called (for example reading the persistent data) or there will be some problems.
Source: Cordova 9 iOS resume event
When called from a resume event handler, interactive functions such as alert() need to be wrapped in a setTimeout() call with a timeout value of zero, or else the app hangs.
this.platform.resume.subscribe(() => setTimeout(() => {
this.ngZone.run(() => {
this.updateCurrentStatus();
});
}, 100));
Get triggered notifications could be something like that (I used linqTS
async getTriggeredNotifications(): Promise<{ id: number, dateTime: string }[]> {
const alerts = await this.settingsService.getAllAlerts();
const now = moment().add(5, 'seconds'); // Just in case you had the app open and the device is super fast and compares everything in some milliseconds (which it does..).
return new List(alerts)
.Where(x => x.dateTime && moment(x.dateTime).isBefore(now, 'seconds'))
.OrderByDescending(key => key.id).FirstOrDefault()
.ToArray();
}
// Then later somewhere:
const triggeredNotifications = await this.getTriggeredNotifications();
const latestTriggeredNotification = new List(triggeredNotifications).OrderByDescending(key => key.id).First();