MaikuB / flutter_local_notifications

A Flutter plugin for displaying local notifications on Android, iOS, macOS and Linux
2.44k stars 1.39k forks source link

firebase_messaging not working if include flutter_local_notifications package on iOS #111

Closed anderscheow closed 4 years ago

anderscheow commented 5 years ago

Method on Firebase Cloud Messaging, onMessage and onResume do not get trigger when I include flutter_local_notifications package.

MaikuB commented 5 years ago

There's a note about this in the README under the iOS integration section. Please check there

rlee1990 commented 5 years ago

Any way to get around this issue. Really don't see any other packages for local notifications for ios or android

rlee1990 commented 5 years ago

@MaikuB

MaikuB commented 5 years ago

Have you read my comment above on the note in the README?

andreidiaconu commented 5 years ago

From the README:

NOTE: this plugin registers itself as the delegate to handle incoming notifications and actions. This may cause problems if you're using other plugins for push notifications (e.g. firebase_messaging) as they will most likely do the same and it's only possible to register a single delegate. iOS handles showing push notifications out of the box so if you're only using this plugin to display the notification payload on Android then it's suggested that you fork the plugin code and remove the following part in the iOS code

UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = instance;

Unfortunately, this platform limitation does mean that it's not possible to use this plugin together other plugins for push notifications on iOS. If you are in this situation, then my only advice is that you'll need to need to look at writing customised platform-specific code for your application that may involve taking bits and pieces of code from the plugins you need.

I see this project is forked 27 times. Did anyone, by any chance, get the 2 plugins to work together (perhaps using a Decorator pattern or something similar)?

radvansky-tomas commented 5 years ago

bump

MaikuB commented 5 years ago

I had raised an issue in the Flutter repository to see if they could expose functionality that would enable multiple plugins can register as a notification center delegate. I would suggesting upvoting there https://github.com/flutter/flutter/issues/22099

rlee1990 commented 5 years ago

@MaikuB Any timeline on when the flutter team will make this change?

MaikuB commented 5 years ago

@rlee1990 you'd have to ask the Flutter team

eikebartels commented 5 years ago

I think an option would be to disable the method swizzling and call the apis directly in the app delegate?

To think this throw, @MaikuB I assume this lib also uses method swizzling? If so what would be the best way to call the apis manually? With firebase messaging it is possible. https://firebase.google.com/docs/cloud-messaging/ios/client#token-swizzle-disabled https://firebase.google.com/docs/cloud-messaging/ios/receive#handle-swizzle

tsangpozheng commented 5 years ago

IOSInitializationSettings should add a switch that controls whether this plugin will register self as the only UNUserNotificationCenter delegate.

This option will save us from forking the code and remove the following part in the iOS code as the doc said. UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate = instance;

MaikuB commented 5 years ago

Had already considered that a long time ago, ignoring the need to fork, it seemed to me like it could cause more problems. See https://github.com/MaikuB/flutter_local_notifications/issues/50

rlee1990 commented 5 years ago

Hi @MaikuB will we still have this issue with the update that the Flutter team made?

MaikuB commented 5 years ago

@eikebartels apologies for the late response, somehow I've missed your message. The plugin itself isn't implemented to use method swizzling.

@rlee1990 if the update you're referring is around the issue on the Flutter repository where you had tagged me then no. That's a completely different issue and if you go back to the earlier posts, I have mentioned that I had an issue created on the Flutter repo that is relevant for the problem here. I can see you had given the post a thumbs up already too

szotp commented 5 years ago

Had already considered that a long time ago, ignoring the need to fork, it seemed to me like it could cause more problems. See #50

It should be easy to add Info.plist check for a flag to see if code should run or not. This will let you initialize at the start. I may submit PR if I need this.

minh98 commented 5 years ago

any update ?

jawand commented 5 years ago

in a nutshell, create a fork and modify the code in iOS for getting this thing work in Android.

in ios integration https://pub.dev/packages/flutter_local_notifications#ios-integration there is a NOTE: i.e.

this plugin registers itself as the delegate to handle incoming notifications and actions. This may cause problems if you're using other plugins for push notifications (e.g. firebase_messaging) as they will most likely do the same and it's only possible to register a single delegate. iOS handles showing push notifications out of the box so if you're only using this plugin to display the notification payload on Android then it's suggested that you fork the plugin code and remove the following part in the iOS code

UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate = instance; Unfortunately, this platform limitation does mean that it's not possible to use this plugin together other plugins for push notifications on iOS. If you are in this situation, then my only advice is that you'll need to need to look at writing customised platform-specific code for your application that may involve taking bits and pieces of code from the plugins you need.

calebisstupid commented 5 years ago

Just commenting to add that I am running into this same thing. From my testing, simply including both is not causing the problems, but rather whichever piece receives a push notification first.

If I receive a scheduled notification first, I do not receive push notifications; alternatively, if I receive a push notification via firebase, then I no longer get scheduled notifications.

As I am somewhat limited in native iOS experience, has anyone found any help or had any success modifying the delegate portion of this?

calebisstupid commented 5 years ago

So I began plopping in the source from firebase_messaging and this package directly into my app so I could experiment with solutions, and ... they both just work now. I just copied over the necessary files (dart, objective-c, and android), made the necessary package name changes (and manifest change on Android), and then for iOS added:

[MyFlutterLocalNotificationsPlugin registerWithRegistrar:[self registrarForPlugin:@"MyFlutterLocalNotificationsPlugin"]];
[FLTFirebaseMessagingPlugin registerWithRegistrar:[self registrarForPlugin:@"FLTFirebaseMessagingPlugin"]];

Notice, I renamed my local notification file to avoid weird dependency conflicts (not sure if that was necessary), and also included the proper imports (files were in same directory level):

#import "MyFlutterLocalNotificationsPlugin.h"
#import "FirebaseMessagingPlugin.h"

I'm going to keep messing around to make sure this is not some fluke accident, but I had not even made an attempt at consolidating both into a single delegate when they were both working.

Edit: well this was weird; it stopped working again, proceeding forward with the delegate combination tomorrow.

calebisstupid commented 5 years ago

Ok got it working.

Assuming you have the source from each project, just take mostly everything from FirebaseMessagingPlugin.m and put it in MyFlutterLocalNotificationsPlugin. Combine methods where there are duplicates (like handleMethodCall) and copy over methods that do not exist (like didReceiveRemoteNotification).

The important part is also to combine flutter channels, so that both share the same channel.

MaikuB commented 5 years ago

If possible, do you think you can post a link to a Github repository for others to check out? If it does indeed work, I think will be more of a workaround as I have created an issue on the main Flutter repository around plugins that make use of the iOS notification delegate. My hope is that with support built into flutter itself that there wouldn't be a need to fork the code or at least make it easier to integrate both. It's quite possible I'm missing something though.

I should also point out that the firebase messaging plugin is a wrapper for the firebase iOS SDK. That SDK handles the delegate methods (I believe by method swizzling). I seem to recall seeing some notes on the firebase iOS SDK repository (may have been in a PR) that talked about refactoring that code. Could be that an option to disable swizzling of the notification delegate methods will be made available down the line

calebisstupid commented 5 years ago

I should be able to once I figure out the remaining issue I am having; which is onSelectNotification not working on iOS after combining everything.

ghost commented 5 years ago

I could not reproduce this issue. Both flutter_local_notifications and firebase_messaging are working just fine together for me. I configured a local notification with the method periodicallyShow() and kept sending push notifications via Cloud Messaging. I rebooted my iOS device several times, and the notifications from periodicallyShow() and Cloud Messaging did not stop working. I also used the method schedule().

Is there any particular situation in which this problem is supposed to happen?

flutter_local_notifications 0.7.1+1 firebase_messaging 5.0.1+1 Flutter v1.2.1 stable

MaikuB commented 5 years ago

@hugocbpassos do the callbacks of the firebase messaging plugin and this plugin still work for you?

calebisstupid commented 5 years ago

edit: it is actually working for me with both 0.7.1+1 and 5.0.1+1.

edit2: onLaunch callback working on iOS for me

ghost commented 5 years ago

@MaikuB @calebisstupid I ran lots of tests and here are my conclusions:

Foreground:

Background:

Terminated:

Also, while trying to test "terminated" situations, I stop receiving any push notifications (local kept working). I rebooted the device and the pattern I just reported above started to work again.

ghost commented 5 years ago

Method on Firebase Cloud Messaging, onMessage and onResume do not get trigger when I include flutter_local_notifications package.

I'm sorry. Seems like this is the exact same behaviour from when this issue was opened. The title confused me a bit.

Is this it? Only the callbacks are not working from the beginning or we made some progress?

MaikuB commented 5 years ago

I believe just to do with callbacks though others in the community may be able to provide more details. These issues are occurring as plugins are clashing when registering themselves to handle thins like when a notification is being shown on the iOS side

calebisstupid commented 5 years ago

I actually got my combined channel approach finally working.

The reason, as it appeared to me that I was having mixed results of both working to suddenly not working was because after the initial boot, any future boot was calling back with firebase method channel commands through the local notifications plugin dart code. I merged those two dart files today and (so far) have successful pushed and local with both working through there respective channel

Another piece worth noting is that the firebase objective c code does not use the iOS 10+ notifications handler (blanking on the name at the moment and am away from my computer), which was causing the local notifications code to capture the tap events instead of firebase.

I plan to obfuscate my code and post it here in hopes that it helps others. It is rather messy as I did not take a lot of time to refactor things though. I hope to have it posted this week.

calebisstupid commented 5 years ago

Just an update; my solution is still working, but apparently only in debug builds. The build I submitted via TestFlight does not work. This is why I have not posted code yet.

Any suggestions as to why this might be would be greatly appreciated.

edit to add: after testing for a bit more, it is just flat out inconsistent beyond debug builds. sometimes nothing local works, other times they work, once we only got 3 of 4 scheduled local notifications.

ultimately I've given up on getting these to work well consistently and we're migrating over to push exclusively

lgArlequin commented 5 years ago

Hi, today, I update the firebase plugin to its latest version (5.1.2), and after some tests both plugins work together in IOS. Can anyone corroborate if it works now?

firebase_messaging: ^5.1.2 flutter_local_notifications: ^0.8.0

[✓] Flutter (Channel beta, v1.7.8+hotfix.4, on Mac OS X 10.14.5 18F132, locale en-AR)
[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[✓] Xcode - develop for iOS and macOS (Xcode 10.3)
[✓] iOS tools - develop for iOS devices
[✓] Android Studio (version 3.4)
[✓] VS Code (version 1.36.1)
[✓] Connected device (2 available)
josh-burton commented 5 years ago

The issue is that flutter_local_notifications does overwrite firebase_messaging handling like onResume, onLaunch but I think that's ok.

flutter_local_notifications provides callbacks/methods to handle those notifications, BUT they don't get called unless there is a payload.

When firebase_messaging delivers a notification it doesnt set the payload key so flutter_local_notifications ignores the message.

If we remove that restriction I think flutter_local_notifications will handle all situations ok.

praneethfdo commented 5 years ago

@lgArlequin Are you sure you are getting notifications in both background and foreground?

lgArlequin commented 5 years ago

@praneethfdo, The truth is that I am somewhat confused, last week when I tried it I had the problems described in this issue. Yesterday I updated to the latest version of firebase_messaging (^ 5.1.2) and rechecked to see if despite the conflicts, it served me for the purposes of my app.

I will try to expand on the subject a bit, these were the test results in the different states of the app:

Foreground: flutter_local_notifications: notification does not appear, execute onDidReceiveLocalNotification, payload received. firebase_messaging works fine onMessage().

Background: flutter_local_notifications: notification appear, open the app, execute onDidReceiveLocalNotification, payload received. firebase_messaging works fine onResume().

Terminated: flutter_local_notifications: notification appear, open the app, but not execute onDidReceiveLocalNotification or onSelectNotification. firebase_messaging works fine onLaunch().

I am testing the code in an app already in development, working with firebase_messaging correctly.

The tests are performed on an iPhone 6s with iOS 12.4 ... and this is what I find most strange, since in theory the onDidReceiveLocalNotification function only runs for versions less than 10 of iOS (or at least it is what I understood)

In the tests I did on an iphone XR emulator, the onSelectNotification function was correctly executed and onDidReceiveLocalNotification was ignored. But you cannot check conflicts with firebase_messaging since it does not work in the emulator.

I've been developing in Flutter for a little over a year now, so I don't consider myself an expert either, I don't rule out any other problem on my part :)

Some code:

// initialise the plugin
    var initializationSettingsAndroid =
        AndroidInitializationSettings('app_icon');
    var initializationSettingsIOS = IOSInitializationSettings(
        onDidReceiveLocalNotification: onDidReceiveLocalNotification);
    var initializationSettings = InitializationSettings(
        initializationSettingsAndroid, initializationSettingsIOS);
    flutterLocalNotificationsPlugin.initialize(initializationSettings,
        onSelectNotification: onSelectNotification);
_scheduleNotification();
Future<void> _scheduleNotification() async {
    var scheduledNotificationDateTime =
        DateTime.now().add(Duration(seconds: 30));
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'repeating channel id',
        'repeating channel name',
        'repeating description');
    var iOSPlatformChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.schedule(
        0,
        'Test',
        'Test',
        scheduledNotificationDateTime,
        platformChannelSpecifics,
        androidAllowWhileIdle: true, payload: '1234567');
}

Future<void> onDidReceiveLocalNotification(
      int id, String title, String body, String payload) async {
    print('onDidReceiveLocalNotification');
    print(payload);
}

Future<void> onSelectNotification(String payload) async {
    print('onSelectNotification');
    print(payload);
}
ghost commented 5 years ago

Rather then forking repo and commenting those two lines, why not create a branch in this repo itself and use it in pubspec.yml??? This way, plugin can stay updated for all the developers... Can someone give a thought to this?

ThinkDigitalSoftware commented 5 years ago

Rather then forking repo and commenting those two lines, why not create a branch in this repo itself and use it in pubspec.yml??? This way, plugin can stay updated for all the developers... Can someone give a thought to this?

I agree.

ThinkDigitalSoftware commented 5 years ago

@MaikuB So we have to remove this plugin completely to get it to play well, correct? Or does it register when it's initialized via FlutterLocalNotificationsPlugin.initialize() or something like that?

MaikuB commented 5 years ago

@ThinkDigitalSoftware yes regarding as there are issues with using both plugins together as one of the plugins will have callbacks that won't work. If you're not concerned with handling the callback when a user taps on a local notification but still want both plugins then the workaround for modifying the iOS code of this plugin should work.

I won't be looking to create a branch for this workaround, someone else in the community is welcome to maintain a fork and let others know. Reasons include

usmanumar90 commented 5 years ago

I was able to see notification in foreground while local notifications also started appearing but now I have another issue by tapping on local notification message my onSelectNotification function is not getting called, any iOS expert can help me I am new to iOS.

Below is how I got all this functional till this stage.

FIrst I forked the flutter_local_notifications then modified the plugin after reading the README and commented the below 2 lines.

UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate = instance;

I was able to see the push notification while application is in foreground by making these few changes in my flutter project's iOS AppDelegate.swift file in flutter project. Local Notification also appear when i call the showNotification function,

Below are the changes I made due to which both firebase_message and flutter_local_notifications started working together.

import UIKit
import Flutter
import UserNotifications

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, UNUserNotificationCenterDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    // set the delegate in didFinishLaunchingWithOptions
    UNUserNotificationCenter.current().delegate = self
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
    // This method will be called when app received push notifications in foreground
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void)
    {
        completionHandler([.alert, .badge, .sound])
    }
}
MaikuB commented 5 years ago

@usmanumar90 if you look at my the post I made before yours, this is currently the expected behaviour due to some limitations in Flutter at the moment. For it to be addressed requires

Important to note that it may take some time for the engine changes to make it to stable. Therefore, even if this all manages to be working, I'll most likely only update the plugin only once there's a new release of Flutter on the stable channel as these are breaking changes. I will however, update the instructions on what's required to get around the issue for others that don't want to wait for a stable release

usmanumar90 commented 5 years ago

@usmanumar90 if you look at my the post I made before yours, this is currently the expected behaviour due to some limitations in Flutter at the moment. For it to be addressed requires

  • Changes to the Flutter engine: apparently done and is rolled onto the master channel
  • Changes to the firebase_messaging plugin: looks like it's in-progress (see FirebaseExtended/flutterfire#121) but not yet tested
  • Changes to this plugin: there are instructions on how plugins can make use of the engine changes but have had some issues, trying to get clarification around this

Important to note that it may take some time for the engine changes to make it to stable. Therefore, even if this all manages to be working, I'll most likely only update the plugin only once there's a new release of Flutter on the stable channel as these are breaking changes. I will however, update the instructions on what's required to get around the issue for others that don't want to wait for a stable release

Thank you @MaikuB for helping, seems this might take some time while its fully functional.

minh98 commented 4 years ago

any update ?

MaikuB commented 4 years ago

@minh98 please read the above posts. You appear to be following the issue I had raised on the main Flutter repository already as well.

maryfalcon commented 4 years ago

Hello, everybody! Hope, my experience of using firebase_messaging and flutter_local_notifications together will help someone.

Code modification

I suggest changing the following code in FlutterLocalNotificationsPlugin.m in method -(void)userNotificationCenter:(UNUserNotificationCenter )center willPresentNotification :(UNNotification )notification ... :

NSNumber *presentAlertValue = (NSNumber*)notification.request.content.userInfo[PRESENT_ALERT];
NSNumber *presentSoundValue = (NSNumber*)notification.request.content.userInfo[PRESENT_SOUND];
NSNumber *presentBadgeValue = (NSNumber*)notification.request.content.userInfo[PRESENT_BADGE];
bool presentAlert = [presentAlertValue boolValue];
bool presentSound = [presentSoundValue boolValue];
bool presentBadge = [presentBadgeValue boolValue];

TO: bool presentAlert = true; bool presentSound = true; bool presentBadge = true;

(You can use default values instead of "true" or use those values only if they are not inside the notification structure)

in method - (void)userNotificationCenter:(UNUserNotificationCenter )center didReceiveNotificationResponse:(UNNotificationResponse )response you can set up payload. You may need this if you want to handle some notification data in onSelectNotification callback. It's good to return as a payload response.notification.request.content.userInfo (or even whole content), because all data fields (from firebase data message) are inside this object.

There are no changes needed for firebase plugin.

Effect

After that your app will get every message (both local and from firebase, both in foreground and in background) and make it visible with a banner. After clicking on message onSelectNotification callback will be called.

Concerning changing notification options, iphone user can change notification settings in ios anyway. Then app will create notifications according to these changed settings. That's why I consider getting presentAlert, presentSound and presentBadge from notification not so important.

NOTE! onResume, onMessage, onBackground and onLaunch still won't work. All message handling can be done in onSelectNotification. And for Android those methods work. This different workflow can be inconvenient.

Explanation

Why firebase_messaging and flutter_local_notifications don't work together? flutter_local_notifications uses UNUserNotificationCenter which can have only one UNUserNotificationCenterDelegate. firebase_messaging uses FIRMessaging which can have only one FIRMessagingDelegate. I guess that's created with NSNotificationCenter and its delegate. (Maybe it will be changed in the future, since it's recommended to use UNUserNotificationCenter and its delegate). Anyway those delegates interfere with each other. So in the end only flutter_local_notifications UNUserNotificationCenterDelegate works.

This UNUserNotificationCenterDelegate works for both firebase and local. There are two methods in this protocol:

All notifications go through our delegate FlutterLocalNotificationsPlugin. And we've modified it by changing code to handle also firebase_messaging notifications.

Disadvantages of this solution

From what i can gather there are some drawbacks in this solution: 1) Modification of plugin code in ios part means that it's harder to upgrade plugin. To make it easier you can leave pubspec.yaml as it is and overwrite ios part of FLN plugin in you app. Then it's better not to use [FlutterLocalNotificationsPlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterLocalNotificationsPlugin"]]; to avoid any conflicts. 2) You need to handle notifications in ios and android separately, since it should be done in different methods. 3) Presumably firebase sdk and firebase_messaging plugin will be changed to using recommended UNUserNotificationCenter. After that this solution may stop working or there will be a better solution.

It would be nice if someone share some thoughts about this. And I'd appreciate pointing out the problems in this solution.

MilesAdamson commented 4 years ago

in a nutshell, create a fork and modify the code in iOS for getting this thing work in Android.

in ios integration https://pub.dev/packages/flutter_local_notifications#ios-integration there is a NOTE: i.e.

this plugin registers itself as the delegate to handle incoming notifications and actions. This may cause problems if you're using other plugins for push notifications (e.g. firebase_messaging) as they will most likely do the same and it's only possible to register a single delegate. iOS handles showing push notifications out of the box so if you're only using this plugin to display the notification payload on Android then it's suggested that you fork the plugin code and remove the following part in the iOS code

UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate = instance; Unfortunately, this platform limitation does mean that it's not possible to use this plugin together other plugins for push notifications on iOS. If you are in this situation, then my only advice is that you'll need to need to look at writing customised platform-specific code for your application that may involve taking bits and pieces of code from the plugins you need.

I'm confused - if you fork the repo and make these changes will you be able to catch firebase_messaging notifications in onMessage and then display them with this plugin on BOTH platforms, or only Android?

Are the steps really this for the complete solution?

tbm98 commented 4 years ago

Hi @MaikuB. flutterfire/pull/121 just merged to master.

MaikuB commented 4 years ago

@tbm98 if you follow the issue related to that PR you should see that it doesn't actually solve the issue. The Flutter team is looking at that as part of another issue https://github.com/FirebaseExtended/flutterfire/issues/1455

tbm98 commented 4 years ago

Thanks!. waiting for flutter team :D

Christophe235 commented 4 years ago

NOTE: this plugin registers itself as the delegate to handle incoming notifications and actions. This may cause problems if you're using other plugins for push notifications (e.g. firebase_messaging) as they will most likely do the same and it's only possible to register a single delegate. iOS handles showing push notifications out of the box so if you're only using this plugin to display the notification payload on Android then it's suggested that you fork the plugin code and remove the following part in the iOS code

UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate = instance;

Unfortunately, this platform limitation does mean that it's not possible to use this plugin together other plugins for push notifications on iOS. If you are in this situation, then my only advice is that you'll need to need to look at writing customised platform-specific code for your application that may involve taking bits and pieces of code from the plugins you need.

I'm having the same problem discussed above and I think this solution is a little bit messy and there is a cleaner way to provide this fix... In flutter, it's possible to get a package from a specific repo and a specific branch like this:

flutter_local_notifications: git: url: https://github.com/MaikuB/flutter_local_notifications.git ref: branch_name

So, I was wondering if it's possible to provide a new branch with the above fix, so we are not depending on a new fork. It seems a common issue, 240 forks were already created and I think this the branch approach is a clear and easier way to achieve what is supposed.

@MaikuB, Did you considerer this?

MaikuB commented 4 years ago

@Christophe235 yes that has already been asked and I explained why I've not done this at https://github.com/MaikuB/flutter_local_notifications/issues/111#issuecomment-528308569