Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions packages/firebase_messaging/firebase_messaging/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,23 @@ To get started with Firebase Cloud Messaging for Flutter, please [see the docume

To use this plugin, please visit the [Cloud Messaging Usage documentation](https://firebase.google.com/docs/cloud-messaging)

### iOS apps using UIScene

Apps that adopt the UIScene lifecycle register Flutter plugins after
`application:didFinishLaunchingWithOptions:`. Apple requires
`UNUserNotificationCenter.delegate` to be configured before that method returns, so configure
Firebase Messaging explicitly from your app delegate:

```objectivec
#import <firebase_messaging/FLTFirebaseMessagingPlugin.h>

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[FLTFirebaseMessagingPlugin configureNotificationCenterDelegate];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
```

## Issues and feedback

Please file FlutterFire specific issues, bugs, or feature requests in our [issue tracker](https://github.com/firebase/flutterfire/issues/new).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#import "AppDelegate.h"
#import <firebase_messaging/FLTFirebaseMessagingPlugin.h>
#import "GeneratedPluginRegistrant.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[FLTFirebaseMessagingPlugin configureNotificationCenterDelegate];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ @implementation FLTFirebaseMessagingPlugin {

// Guard against calling setupNotificationHandling twice
BOOL _notificationHandlingSetup;
BOOL _applicationObserverRegistered;

#if TARGET_OS_OSX
// Tracks when plugin registration occurred after the macOS launch notification.
Expand All @@ -65,15 +66,39 @@ @implementation FLTFirebaseMessagingPlugin {

#pragma mark - FlutterPlugin

- (instancetype)initWithFlutterMethodChannel:(FlutterMethodChannel *)channel
andFlutterPluginRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
- (instancetype)init {
self = [super init];
if (self) {
_initialNotificationGathered = NO;
_sceneDidConnect = NO;
_notificationHandlingSetup = NO;
_channel = channel;
_registrar = registrar;
_applicationObserverRegistered = NO;
}
return self;
}

+ (instancetype)sharedInstance {
static FLTFirebaseMessagingPlugin *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[FLTFirebaseMessagingPlugin alloc] init];
});
return sharedInstance;
}

+ (void)configureNotificationCenterDelegate {
#ifdef __FF_NOTIFICATIONS_SUPPORTED_PLATFORM
[[FLTFirebaseMessagingPlugin sharedInstance] configureNotificationCenterDelegate];
#endif
}

- (void)configureWithFlutterMethodChannel:(FlutterMethodChannel *)channel
andFlutterPluginRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
_channel = channel;
_registrar = registrar;

if (!_applicationObserverRegistered) {
_applicationObserverRegistered = YES;
// Application
// Dart -> `getInitialNotification`
// ObjC -> Initialize other delegates & observers
Expand All @@ -87,16 +112,14 @@ - (instancetype)initWithFlutterMethodChannel:(FlutterMethodChannel *)channel
#endif
object:nil];
}
return self;
}

+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
FlutterMethodChannel *channel =
[FlutterMethodChannel methodChannelWithName:kFLTFirebaseMessagingChannelName
binaryMessenger:[registrar messenger]];
FLTFirebaseMessagingPlugin *instance =
[[FLTFirebaseMessagingPlugin alloc] initWithFlutterMethodChannel:channel
andFlutterPluginRegistrar:registrar];
FLTFirebaseMessagingPlugin *instance = [FLTFirebaseMessagingPlugin sharedInstance];
[instance configureWithFlutterMethodChannel:channel andFlutterPluginRegistrar:registrar];
// Register with internal FlutterFire plugin registry.
[[FLTFirebasePluginRegistry sharedInstance] registerFirebasePlugin:instance];

Expand Down Expand Up @@ -250,6 +273,57 @@ - (void)registerForRemoteNotifications {
#endif
}

#ifdef __FF_NOTIFICATIONS_SUPPORTED_PLATFORM
- (void)configureNotificationCenterDelegate {
// Set UNUserNotificationCenter but preserve original delegate if necessary.
if (@available(iOS 10.0, macOS 10.14, *)) {
BOOL shouldReplaceDelegate = YES;
UNUserNotificationCenter *notificationCenter =
[UNUserNotificationCenter currentNotificationCenter];
id<UNUserNotificationCenterDelegate> currentDelegate = notificationCenter.delegate;

if (currentDelegate == self) {
return;
}

if (currentDelegate != nil) {
#if !TARGET_OS_OSX
// If a UNUserNotificationCenterDelegate is set and it conforms to
// FlutterAppLifeCycleProvider then we don't want to replace it on iOS as the earlier
// call to `[_registrar addApplicationDelegate:self];` will automatically delegate calls
// to this plugin. If we replace it, it will cause a stack overflow as our original
// delegate forwarding handler below causes an infinite loop of forwarding. See
// https://github.com/firebasefire/issues/4026.
if ([currentDelegate conformsToProtocol:@protocol(FlutterAppLifeCycleProvider)]) {
// Note this one only executes if Firebase swizzling is **enabled**.
shouldReplaceDelegate = NO;
}
#endif

if (shouldReplaceDelegate) {
_originalNotificationCenterDelegate = currentDelegate;
_originalNotificationCenterDelegateRespondsTo.openSettingsForNotification =
(unsigned int)[_originalNotificationCenterDelegate
respondsToSelector:@selector(userNotificationCenter:openSettingsForNotification:)];
_originalNotificationCenterDelegateRespondsTo.willPresentNotification =
(unsigned int)[_originalNotificationCenterDelegate
respondsToSelector:@selector(userNotificationCenter:willPresentNotification:
withCompletionHandler:)];
_originalNotificationCenterDelegateRespondsTo.didReceiveNotificationResponse =
(unsigned int)[_originalNotificationCenterDelegate
respondsToSelector:@selector(userNotificationCenter:didReceiveNotificationResponse:
withCompletionHandler:)];
}
}

if (shouldReplaceDelegate) {
__strong FLTFirebasePlugin<UNUserNotificationCenterDelegate> *strongSelf = self;
notificationCenter.delegate = strongSelf;
}
}
}
#endif

- (void)setupNotificationHandlingWithRemoteNotification:(nullable NSDictionary *)remoteNotification
actionIdentifier:(nullable NSString *)actionIdentifier {
// If notification handling was already set up (e.g. from
Expand Down Expand Up @@ -335,48 +409,10 @@ - (void)setupNotificationHandlingWithRemoteNotification:(nullable NSDictionary *
[_registrar addApplicationDelegate:self];
#endif

// Set UNUserNotificationCenter but preserve original delegate if necessary.
if (@available(iOS 10.0, macOS 10.14, *)) {
BOOL shouldReplaceDelegate = YES;
UNUserNotificationCenter *notificationCenter =
[UNUserNotificationCenter currentNotificationCenter];

if (notificationCenter.delegate != nil) {
#if !TARGET_OS_OSX
// If a UNUserNotificationCenterDelegate is set and it conforms to
// FlutterAppLifeCycleProvider then we don't want to replace it on iOS as the earlier
// call to `[_registrar addApplicationDelegate:self];` will automatically delegate calls
// to this plugin. If we replace it, it will cause a stack overflow as our original
// delegate forwarding handler below causes an infinite loop of forwarding. See
// https://github.com/firebasefire/issues/4026.
if ([notificationCenter.delegate conformsToProtocol:@protocol(FlutterAppLifeCycleProvider)]) {
// Note this one only executes if Firebase swizzling is **enabled**.
shouldReplaceDelegate = NO;
}
#ifdef __FF_NOTIFICATIONS_SUPPORTED_PLATFORM
[self configureNotificationCenterDelegate];
#endif

if (shouldReplaceDelegate) {
_originalNotificationCenterDelegate = notificationCenter.delegate;
_originalNotificationCenterDelegateRespondsTo.openSettingsForNotification =
(unsigned int)[_originalNotificationCenterDelegate
respondsToSelector:@selector(userNotificationCenter:openSettingsForNotification:)];
_originalNotificationCenterDelegateRespondsTo.willPresentNotification =
(unsigned int)[_originalNotificationCenterDelegate
respondsToSelector:@selector(userNotificationCenter:willPresentNotification:
withCompletionHandler:)];
_originalNotificationCenterDelegateRespondsTo.didReceiveNotificationResponse =
(unsigned int)[_originalNotificationCenterDelegate
respondsToSelector:@selector(userNotificationCenter:didReceiveNotificationResponse:
withCompletionHandler:)];
}
}

if (shouldReplaceDelegate) {
__strong FLTFirebasePlugin<UNUserNotificationCenterDelegate> *strongSelf = self;
notificationCenter.delegate = strongSelf;
}
}

// We automatically register for remote notifications as
// application:didReceiveRemoteNotification:fetchCompletionHandler: will not get called unless
// registerForRemoteNotifications is called early on during app initialization, calling this from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@
FIRMessagingDelegate,
NSApplicationDelegate,
UNUserNotificationCenterDelegate>

/// Returns the shared Firebase Messaging plugin instance.
+ (instancetype)sharedInstance;

/// Configures Firebase Messaging as the `UNUserNotificationCenter` delegate.
///
/// Apps that adopt the UIScene lifecycle should call this from
/// `application:didFinishLaunchingWithOptions:` before returning, because Apple requires the
/// notification center delegate to be assigned before application launch completes.
+ (void)configureNotificationCenterDelegate;
#else
@interface FLTFirebaseMessagingPlugin : FLTFirebasePlugin <FlutterPlugin,
FLTFirebasePlugin,
Expand All @@ -59,6 +69,16 @@ API_AVAILABLE(ios(10.0))
FlutterSceneLifeCycleDelegate
#endif
>

/// Returns the shared Firebase Messaging plugin instance.
+ (instancetype)sharedInstance;

/// Configures Firebase Messaging as the `UNUserNotificationCenter` delegate.
///
/// Apps that adopt the UIScene lifecycle should call this from
/// `application:didFinishLaunchingWithOptions:` before returning, because Apple requires the
/// notification center delegate to be assigned before application launch completes.
+ (void)configureNotificationCenterDelegate;
#else
@interface FLTFirebaseMessagingPlugin : FLTFirebasePlugin <FlutterPlugin,
FLTFirebasePlugin,
Expand Down
Loading