capawesome-team / capacitor-firebase

⚡️ Firebase plugins for Capacitor. Supports Android, iOS and the Web.
https://capawesome.io/plugins/firebase/
Apache License 2.0
366 stars 92 forks source link

bug(messaging): notificationActionPerformed not fired on iOS if the system has garbage collected/terminated the WebView on an app in the background #345

Open jswilliams opened 1 year ago

jswilliams commented 1 year ago

Plugin(s): Messaging; "@capacitor-firebase/messaging": "^1.4.0", and below

Platform(s): iOS only

Current behavior: If a user opens a notification from the Notification Center while the app is in the background, and iOS has terminated the WebView, the notificationActionPerformed listener will not trigger inside the refreshed WebView once capacitor reloads it for you.

Context:

Expected behavior: The notificationActionPerformed listener should be triggered inside the WebView once .reload() completes.

Steps to reproduce: On iOS, open any Capacitor app and let it finish starting. Move the app to the background. Send a notification and leave it unopened in the Notification Center for now. At this point open quite a few other apps (our goal is to get iOS to automatically garbage collect/terminate the Capacitor app’s WebView for system memory reasons, but not to terminate the native app shell. iOS manages the memory/garbage collection of WebView's separately from your app).

Now if you open the notification from the Notification Center, the app will come to the foreground from the background. But since the WebView was terminated, .reload() gets called on it to refresh the current URL and the notificationActionPerformed listener never fires within the WebView.

Related code: Reproducible with any implementation of the plugin that uses the notificationActionPerformed listener.

Other information: This mainly affects users in the following way:

As far as I can tell this seems to affect all of the majority, if not all, notification capacitor plugins out there. This doesn't happen on Android because Android won't ever terminate the WebView independently from the app's native shell.

I'm thinking there should be a way to save the latest opened notification on the native side and rebroadcast it to notificationActionPerformed once the WebView is available again.

Capacitor doctor:

💊   Capacitor Doctor  💊 

Latest Dependencies:

  @capacitor/cli: 4.7.1
  @capacitor/core: 4.7.1
  @capacitor/android: 4.7.1
  @capacitor/ios: 4.7.1

Installed Dependencies:

  @capacitor/cli: 4.3.0
  @capacitor/core: 4.3.0
  @capacitor/android: 4.3.0
  @capacitor/ios: 4.3.0

[success] iOS looking great! 👌
[success] Android looking great! 👌
jswilliams commented 1 year ago

To be clear this isn’t necessarily an issue with the plug-in, and more so how Capacitor works on iOS once Apple implemented their more aggressive memory cleanup of backgrounded WebViews. It could be fixed inside the plug-in itself but it’d be kind of hacky and ideally Capacitor would handle this situation for plugins on its own.

Since this plug-in uses retainUntilConsumed: true when calling notifyListeners, users of the plug-in can implement a work around in their own web code. The idea is that Capacitor will retain an event to be emitted if there are 0 active listeners to that event. We can use this to our advantage to get Capacitor to rebroadcast the event only once the new .reload’ed WebView is available.

Here’s how:

Once you have this all set up notificationActionPerformed will always fire when expected on iOS, even if the system killed the WebView in the interim.

Hope this helps anybody who has noticed reports of notifications intermittently not opening properly from their iOS users.

robingenz commented 1 year ago

Thank you for your detailed request. This problem might be related to #210 and #244. I will take a closer look again at all three issues soon.

AnastasiosF commented 1 year ago

Any news about the issue?

AnastasiosF commented 1 year ago

@jswilliams did you implement that and fixed the issue? I am planing to migrate from official plugin to solve the issue. (Also happened to official push notification plugin). I implements your proposal to official plugin and still the issue not solved.

jswilliams commented 1 year ago

@AnastasiosF I did, and it fixed the issue in our app. Here's how the workaround is implemented. setupNotifications() gets called one time from app.component.ts's ngOnInit(). Let me know if this helps.

image

AnastasiosF commented 1 year ago

@jswilliams which version of plugin do you use? I am using capacitor 3 version and it doesn't work. Also notice that "retainUntilConsumed" is missing in that version. I am talking about "@capacitor-firebase/messaging": "^0.5.1".

Also the same pattern with official plugin doesn't work.

jswilliams commented 1 year ago

@AnastasiosF We’re using the Capacitor 4 version, @capacitor-firebase/messaging: “^1.4.0”.

The work around definitely relies on retainUntilConsumed being used by the plugin. So you might end up needing to fork 0.5.1 to add retainUntilConsumed to the notificationActionPerformed event, if you can’t upgrade.

I’m still surprised something similar doesn’t also work with the official plug-in if it’s using retainUntilConsumed. Maybe Capacitor 3 handles retainUntilConsumed differently than Capacitor 4?

AnastasiosF commented 1 year ago

I migrate to Capacitor 4 and implement your solution. I am on testing to see if it works. However have you find, how to simulate the termination of Webview from system?

jswilliams commented 1 year ago

The only way I found was trial and error. I had to open the app, put it in the background, and then open anywhere between 18-30 different apps. Then bring my app back to the foreground from the app switcher.

Had to do it a few times before I got lucky and the system had finally terminated the WebView (could tell because there’d be a white flash showing the WebView reloading/the Angular page reloading). If the Splash Screen shows instead then you went too far and the system also terminated the native shell.

You might also try opening the other apps and locking your phone for 10 minutes or so before coming back and checking to give the phone more time to do its clean up.

AnastasiosF commented 1 year ago

Ok the same way I did. Apple must do something!

Στις Πέμ 4 Μαΐ 2023 στις 4:26 μ.μ., ο/η Justin @.***> έγραψε:

The only way I found was trial and error. I had to open the app, put it in the background, and then open anywhere between 18-30 different apps. Then bring my app back to the foreground from the app switcher.

Had to do it a few times before I got lucky and the system had finally terminated the WebView (could tell because there’d be a white flash showing the WebView reloading/the Angular page reloading). If the Splash Screen shows instead then you went too far and the system also terminated the native shell.

You might also try opening the other apps and locking your phone for 10 minutes or so before coming back and checking to give the phone more time to do its clean up.

— Reply to this email directly, view it on GitHub https://github.com/capawesome-team/capacitor-firebase/issues/345#issuecomment-1534779432, or unsubscribe https://github.com/notifications/unsubscribe-auth/AJNSDIAGCUJG2RNF56S5K3LXEOU67ANCNFSM6AAAAAAWFQUCYE . You are receiving this because you were mentioned.Message ID: @.***>

AnastasiosF commented 1 year ago

@jswilliams the issue seems not to be fixed. It's appeared again.

jswilliams commented 1 year ago

@AnastasiosF do you also have the removeAllListeners() in your set up code, so that it handles if the WebView reloads while it’s in the foreground as well?

None of our users have reported this issue since we changed our implementation to what is in that screenshot. And I’m not able to get it to happen on my test device.