Register for notifications (FCM) outside AppDelegate - ios

I want to ask the user to allow notifications only when we prompt the "AskNotification" view and when he click on "Yes".
In order to do that, I did the following :
public static AppDelegate Self { get; private set; }
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
HtmlLabelRenderer.Initialize();
global::Xamarin.Forms.Forms.Init();
// Notifications
Firebase.Core.App.Configure();
//AllowNotifications();
...
LoadApplication(new App());
AppDelegate.Self = this;
return base.FinishedLaunching(app, options);
}
public void AllowNotifications()
{
//In iOS you must request permission to show local / remote notifications first since it is a user interrupting action.
if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
{
// Request Permissions
UNUserNotificationCenter.Current.RequestAuthorization(
UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound,
(granted, error) =>
{
// Do something if needed
});
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.Current.Delegate = this;
// For iOS 10 data message (sent via FCM)
Messaging.SharedInstance.Delegate = this;
}
else
{
// iOS 9 or before
var allNotificationTypes = UIUserNotificationType.Alert
| UIUserNotificationType.Badge
| UIUserNotificationType.Sound;
var settings = UIUserNotificationSettings
.GetSettingsForTypes(allNotificationTypes, null);
UIApplication.SharedApplication.RegisterUserNotificationSettings(settings);
}
Messaging.SharedInstance.ShouldEstablishDirectChannel = true;
Console.WriteLine("-------- RegisterForRemoteNotifications");
UIApplication.SharedApplication.RegisterForRemoteNotifications();
}
And then on my Code Behind when the user click on the "Allow" button (from my view) I do the following :
AppDelegate appDelegate = AppDelegate.Self;
appDelegate.AllowNotifications();
As you can see, I'm using the Singleton pattern to have an access to the AppDelegate. My problem is when the "AllowNotifications" is called inside the AppDelegate (it's commented on the code above), the system prompt ask for the user and notifications are received.
But when I call the AllowNotification method from another page with the Singleton pattern. The system popup is showing, we the user click "Yes" it allow notification on iOS parameters. But I never get into my "DidReceiveMessage" method.
Thanks for your help

This issue has nothing to do with your code.It is an expected behavior.Because you used firebase (Xamarin.Firebase.iOS.CloudMessaging from NuGet).
For devices running iOS 10 and above, you must assign your delegate object to the UNUserNotificationCenter object to receive display notifications, and the FIRMessaging object to receive data messages, before your app finishes launching. For example, in an iOS app, you must assign it in the method FinishedLaunching.
That is to say,when you register for notifications out the method FinishedLaunching,even if system popup showed,the register will still not working.
For more detail you can refer here.

Related

local notification is not displayed

I'm working on a Xamarion iOS app. I want to display a local notification when an event occurs in my app, Like Outlook does when an email is received.
I'm using the following code, after having received the right to send notification from the user, of course:
var content = new UNMutableNotificationContent();
if (!string.IsNullOrWhiteSpace(value: mySoundFile))
{
content.Sound = UNNotificationSound.GetCriticalSound(mySoundFile);
}
if (!string.IsNullOrEmpty(myChannelDescription))
{
content.ThreadIdentifier = myChannelDescription;
}
content.Title = "MyTitle";
content.Body = "MyText";
var trigger = UNTimeIntervalNotificationTrigger.CreateTrigger(0.1, false);
var request = UNNotificationRequest.FromIdentifier(notification.Id.ToString(), content, trigger);
var center = UNUserNotificationCenter.Current;
center.AddNotificationRequest(request, null);
But the notification is not displayed.
Any help appreciated.
The reason for not displaying the notification could be several things. Try the following solutions:
Make sure that the application has the required rights to send notifications and that the user has enabled them.
Check if the sound file is valid and located in the application package. It may also be necessary to add it to the Info.plist file.
Check if the thread identifier is valid and not repeatable.
Check if the trigger time is valid. Setting 0.1 seconds is very short and may not be enough to display the notification.
If the above solutions do not work, it is worth using debugging tools to more closely examine why the notification is not displayed.
On iOS, you must request permission to use notifications before attempting to schedule them. Just like this, you can try to check if the following code is added to your project:
UNUserNotificationCenter.Current.RequestAuthorization(UNAuthorizationOptions.Alert, (approved, err) =>
{
...
});
You can change your AddNotificationRequest as follows to see if there is an error in the notification:
center.AddNotificationRequest(request, (err) =>
{
if (err != null)
{
throw new Exception($"Failed to schedule notification: {err}");
}
});
For more details, you can refer to the following documents to check some permission issues:
Enhanced User Notifications in Xamarin.iOS | Microsoft
Asking permission to use notifications | Apple Developer
UpDate: If your app is in the foreground. You could try implementing the delegate userNotificationCenter(_:willPresent:withCompletionHandler:) which will be called when a notification arrives while the app is in the foreground. Refer to the following code:
UNUserNotificationCenter.Current.Delegate = new TestDelegate();
public class TestDelegate: UNUserNotificationCenterDelegate
{
public override void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
{
completionHandler(UNNotificationPresentationOptions.Alert);
}
}

Handle local notification tap Xamarin Forms iOS

I have created an application on Xamarin Forms in which I am scheduling local notifications in PCL through a Plugin named Plugin.Notifications. But I wanted to know whether the user enters into the app through notification or not.
I tried to check whether the UIApplication.LaunchOptionsLocalNotificationKey is present in the launch 'options' dictionary or not, but the launch options dictionary is always null.
Then I tried to handle it through the ReceivedLocalNotification delegate method, and I was able to get the tap event, it works fine when the app is in foreground or in the background, but when the app gets killed and opens through tapping on the notification, its getting crashed.
I am unable to find the issue for the crash.
here is what I am doing in the ReceivedLocalNotification method.
I am setting a bool value through a DependencyService.
public override void ReceivedLocalNotification(UIApplication application, UILocalNotification notification)
{
NSUserDefaults.StandardUserDefaults.Init();
DependencyService.Get<INotificationTap>().SetNoitificationTap(true);
}
The handling of the Dependency Service
[assembly: Xamarin.Forms.Dependency(typeof(NotificationTapIOS))]
namespace abc.iOS
{
public class NotificationTapIOS:NSObject,INotificationTap
{
public bool GetNotificationTap()
{
return NSUserDefaults.StandardUserDefaults.BoolForKey("notificationTapKey");
}
public void SetNoitificationTap(bool isNotificationTapped)
{
NSUserDefaults.StandardUserDefaults.SetBool(isNotificationTapped,"notificationTapKey");
NSUserDefaults.StandardUserDefaults.Synchronize();
}
}
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
if (options != null)
{
// check for a local notification
if (options.ContainsKey(UIApplication.LaunchOptionsLocalNotificationKey))
{
var localNotification = options[UIApplication.LaunchOptionsLocalNotificationKey] as UILocalNotification;
if (localNotification != null)
{
LoadApplication(new App());
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;// reset our badge
}
}
}
else
{
LoadApplication(new App());
}
}
I found a workaround for this issue, I don't know whether this is a correct approach, but it worked for me.
In Xamarin.iOS , when the app launched , the base class methods are called first(i.e applicationDidFinishLaunching) and then the Xamarin part(i.e FinishedLaunching) is called.
So I changed the FinishedLaunching parameters names , similar to the base class parameters (i.e 'app' to 'uiApplication' and 'options' to 'launchOptions' ) and I got the key in the launchOptions Dictionary.
public override bool FinishedLaunching(UIApplication uiApplication, NSDictionary launchOptions)
{
global::Xamarin.Forms.Forms.Init();
isNotificationTapped = false;
if (launchOptions != null) {
if (launchOptions.ContainsKey(UIApplication.LaunchOptionsLocalNotificationKey))
isNotificationTapped = true;
}
LoadApplication(new App());
return base.FinishedLaunching(uiApplication, launchOptions);
}
When the user starts the app through notification tap, then FinishedLaunching method of AppDelegate is executed and we can validate the launchOptions dictionary to find the tap event, and if the app is in background or running stage , and user taps on the notification then the RecieveLocalNotification method of the AppDelegate is called.
public override void ReceivedLocalNotification(UIApplication application, UILocalNotification notification)
{
isNotificationTapped = true;
}
After initializing the value , I saved it in the UserDefaults using the DependencyService

iOS RemoveDeliveredNotifications(string[] identifier) will not delete the notification(s) when app in background

maybe someone can help me.
In my app I'm using push notifications to inform the users that a new message is written to the database. One user can accept the notification and work with the content or dismiss it. If the user accepts it, a silent push is sent to all other devices which received the notification earlier. Here is my code handling this silent notification:
public override void ReceivedRemoteNotification(UIApplication application, NSDictionary remoteNotification)
{
try
{
if (remoteNotification != null)
{
var alert = remoteNotification[FromObject("aps")];
if (alert != null)
{
string id = ((NSDictionary)alert)[FromObject("deleteId")].Description;
if (!String.IsNullOrEmpty(id))
{
List<string> idents = new List<string>();
UNUserNotificationCenter.Current.GetDeliveredNotifications(completionHandler: (UNNotification[] t) =>
{
foreach (UNNotification item in t)
{
UNNotificationRequest curRequest = item.Request;
var notificationId = ((NSDictionary)curRequest.Content.UserInfo[FromObject("aps")])[FromObject("notificationId")].Description;
if (id == notificationId)
{
idents.Add(curRequest.Identifier);
}
}
UNUserNotificationCenter.Current.RemoveDeliveredNotifications(idents.ToArray());
});
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
The problem is that the notification is still visible in the notification center until the app is brought to foreground. But then it gets deleted.
Is there a way to force the method to delete the notification instantly and not only when the app is (re)opened?
When you want to clear the Notifications send from this app. Set its application's badge to 0 to achieve this.
As you said you send a silent notifications to other users, Then DidReceiveRemoteNotification() will fire. In this event we can clear all notifications:
public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, Action<UIBackgroundFetchResult> completionHandler)
{
var aps = userInfo["aps"] as NSDictionary;
if (aps["content-available"].ToString() == "1")
{
//check if this is a silent notification.
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
}
completionHandler(UIBackgroundFetchResult.NewData);
}
Please notice that starting with iOS 8.0, your application needs to register for user notifications to be able to set the application icon badge number. So please add the code below in FinishedLaunching():
UIUserNotificationSettings settings = UIUserNotificationSettings.GetSettingsForTypes(UIUserNotificationType.Badge, null);
UIApplication.SharedApplication.RegisterUserNotificationSettings(settings);
Moreover silent notifications can only be received when your app is on background or foreground. If it's terminated, this will fail.
To remove a notification, you send a silent push notification to all devices with the notification ID as payload that should be removed.
On the clients you implement a UNNotificationServiceExtension which allows you to remove currently displayed notifications by their IDs: UNUserNotificationCenter.current().removeDeliveredNotifications.
This gives you the advantage that you have full control over this logic on the server side.

DidReceiveRemoteNotification never called in Xamarin.iOS

I'm trying to implement push notifications for my iOS application using Xamarin and the Xamarin.Firebase.iOS.CloudMessaging package.
I've got everything setup and working i,e. When the application is in the foreground I can receive notifications (containing and not containing the "content-available" tag) and interact with them (tapping the notification etc.)
However, when the application is in the background, I receive the notifications but, no callbacks are called. To my understanding, the "didReceiveRemoteNotification" should be called when:
The application is in the foreground regardless of if the "content-available" tag is enabled
If the application is in the background or suspended, with the "content-available" tag enabled
When the user taps on the notification
Here are the functions I've implemented in my AppDelegate.cs:
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
Firebase.Core.App.Configure();
FireBaseRegistration();
return base.FinishedLaunching(app, options);
}
private void FireBaseRegistration()
{
// Register your app for remote notifications.
if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
{
// iOS 10 or later
var authOptions = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound;
UNUserNotificationCenter.Current.RequestAuthorization(authOptions, (granted, error) => {
Console.WriteLine(granted);
});
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.Current.Delegate = this;
}
else
{
// iOS 9 or before
var allNotificationTypes = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound;
var settings = UIUserNotificationSettings.GetSettingsForTypes(allNotificationTypes, null);
UIApplication.SharedApplication.RegisterUserNotificationSettings(settings);
}
//Register for APNs notifications
UIApplication.SharedApplication.RegisterForRemoteNotifications();
Messaging.SharedInstance.Delegate = this;
var token = InstanceId.SharedInstance.Token;
Debug.WriteLine(token);
//////Connect to FCM (Only used for Foreground notifications)
Messaging.SharedInstance.ShouldEstablishDirectChannel = true;
// Monitor token generation
InstanceId.Notifications.ObserveTokenRefresh((sender, e) => {
// Note that this callback will be fired everytime a new token is generated, including the first
// time. So if you need to retrieve the token as soon as it is available this is where that
// should be done.
token = InstanceId.SharedInstance.Token;
Console.WriteLine(token);
});
}
//Register APNs token because method swizzling is de-activated
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
Console.WriteLine("Registered for remote notifications");
Console.WriteLine(deviceToken.GetBase64EncodedString(NSDataBase64EncodingOptions.None));
Console.WriteLine(deviceToken);
}
[Export("messaging:didReceiveMessage:")]
public void DidReceiveMessage(Messaging messaging, RemoteMessage remoteMessage)
{
// Do your magic to handle the notification data
Console.WriteLine("iOS 11 Foreground");
}
//Shows local notification and is called when user taps notification
[Export("userNotificationCenter:DidReceiveRemoteNotification:withCompletionHandler:")]
public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, Action<UIBackgroundFetchResult> completionHandler)
{
Console.WriteLine("Received a notficaiton");
completionHandler(UIBackgroundFetchResult.NewData);
}
//To receive notifications in foreground on iOS 11 devices.
[Export("userNotificationCenter:willPresent:withCompletionHandler:")]
public void WillPresent(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
{
Console.WriteLine("Handling iOS 11 foreground notification");
completionHandler(UNNotificationPresentationOptions.Sound | UNNotificationPresentationOptions.Alert);
}
////Called when tapping notification
[Export("userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:")]
public void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler)
{
Console.WriteLine("Handling push notificaiton interaction");
completionHandler();
}
//Receive data message on iOS 10 devices.
public void ApplicationReceivedRemoteMessage(RemoteMessage remoteMessage)
{
Console.WriteLine("Handling iOS 10 data message notification");
}
//// To receive notifications in foreground on iOS 10 devices.
[Export("userNotificationCenter:willPresentNotification:withCompletionHandler:")]
public void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
{
Console.WriteLine("Handling foreground notification");
completionHandler(UNNotificationPresentationOptions.Alert);
}
I've tried these out on iOS 10.3.3 and iOS 11.2. Here are the functions called for both versions:
When the application is in the foreground
On iOS 10.3.3: WillPresentNotification() (with and without the "content-available" tag)
On iOS 11.2: WillPresentNotification() (with and without the "content-available" tag)
When the application is in the background:
On iOS 10.3.3: nothing Shoudln't it be DidReceiveRemoteNotification?
On iOS 11.2: nothing Shoudln't it be DidReceiveRemoteNotification?
I have the remote-notificaitons and background fetch background modes activated in my Info.plist file.
Packages info:
Xamarin.forms: 2.5.0.280555
Xamarin.Firebase.iOS.CloudMessage: 2.0.4.1
Xamarin.Firebase.iOS.Core: 4.0.13
Xamarin.Firebase.iOS.InsstanceID: 2.0.8
Remarks:
The DidReceiveMessage function is implemented in the Xamarin sample but, in my case, is never called.
The DidReceiveNotificationResponse is called when the user taps on the notificaiton.
The WillPresent is also never called.
I've tried with notifications via the firebase API and directly via the APNs (with the pusher application).
Set Messaging.SharedInstance.ShouldEstablishDirectChannel=false and send data-only messages.

iOS remote notifications do not trigger when in background mode

I do not usually post here, but this time I am stuck. I searched already on this forum, on stackoverflow and on google in general but I cannot understand what I am doing wrong.
I am developing a Xamarin.Forms app and I am having trouble to fire the methods when a remote notification is received.
Here is my code:
Just after login I execute this method to register to apple notification center:
public void RegisterForNotifications ()
{
var settings = UIUserNotificationSettings.GetSettingsForTypes(
UIUserNotificationType.Alert
| UIUserNotificationType.Badge
| UIUserNotificationType.Sound,
new NSSet());
UIApplication.SharedApplication.RegisterUserNotificationSettings(settings);
UIApplication.SharedApplication.RegisterForRemoteNotifications();
}
this call successfully trigger this method on AppDelegate
public override void RegisteredForRemoteNotifications (UIApplication application, NSData deviceToken)
{
var azureService = App.GetBackEndService ();
azureService.RegisterForNotifications (deviceToken);
}
RegisterForNotifications registers with azure notification hub:
public static async void RegisterForNotifications(this BackEndService service, NSData deviceId)
{
Microsoft.WindowsAzure.MobileServices.MobileServiceClient client = new Microsoft.WindowsAzure.MobileServices.MobileServiceClient (service.Client.ApplicationUri);
client.CurrentUser = service.Client.CurrentUser;
var push = client.GetPush ();
var tags = new List<string>() { App.GetBackEndService().Client.CurrentUser.UserId };
try{
await push.UnregisterAllAsync(deviceId);
}catch(Exception e){
var exp = e.Message;
}
await push.RegisterTemplateAsync(deviceId, NOTIFICATION_TEMPLATE, "", "NotificationTemplate", tags);
}
Now, when I send a notification and the app is on foreground the method triggered is this one (In AppDelegate):
public override void ReceivedRemoteNotification (UIApplication application, NSDictionary userInfo)
{
ShowAlerts (userInfo);
}
But when in background mode (for example when I press the Home button on the Iphone) nothing gets triggered
And nothing shows on the upper bar where the iOS notifications are usually shown.
I placed a breakpoint in ReceivedRemoteNotification, DidReceiveRemoteNotification and also on FinishedLaunching.
I properly set Remote notifications in Info.plist under Enable Background Modes
Can someone understand what I am missing?

Resources