Xamarin iOS Push Notification Background & Foreground issue - xamarin

I have been trying to add Push notifications targeting iOS 10 in Xamarin iOS App but
Foreground i successfully get notification first time of deployment in UNUserNotificationCenterDelegate but on second time it doesn't capture that unless i uninstall the app and reinstall it.
Background i always get the notification but i'm unable to capture its tap.
InActive is working fine, i'm able to capture its tap in FinishedLaunching.
Code
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
// Remote Notifications
SetupRemoteNotifications(app, options);
}
private void SetupRemoteNotifications(UIApplication app, NSDictionary options)
{
// Handling multiple OnAppear calls
if (!UIApplication.SharedApplication.IsRegisteredForRemoteNotifications)
{
// register for remote notifications based on system version
if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
{
// iOS 10 or later
var authOptions = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound;
UNUserNotificationCenter.Current.RequestAuthorization(authOptions, (granted, error) =>
{
if (granted)
{
InvokeOnMainThread(UIApplication.SharedApplication.RegisterForRemoteNotifications);
}
});
// Watch for notifications while the app is active
UNUserNotificationCenter.Current.Delegate = this;
}
else if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
var pushSettings = UIUserNotificationSettings.GetSettingsForTypes(
UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound,
new NSSet());
UIApplication.SharedApplication.RegisterUserNotificationSettings(pushSettings);
UIApplication.SharedApplication.RegisterForRemoteNotifications();
}
else
{
UIRemoteNotificationType notificationTypes = UIRemoteNotificationType.Alert | UIRemoteNotificationType.Badge | UIRemoteNotificationType.Sound;
UIApplication.SharedApplication.RegisterForRemoteNotificationTypes(notificationTypes);
}
}
// Check for a notification
if (options != null)
{
// check for a remote notification
if (options.ContainsKey(UIApplication.LaunchOptionsRemoteNotificationKey))
{
var remoteNotification = options[UIApplication.LaunchOptionsRemoteNotificationKey] as NSDictionary;
if (remoteNotification != null)
{
ProcessNotification(remoteNotification, true);
//new UIAlertView(remoteNotification.AlertAction, remoteNotification.AlertBody, null, "OK", null).Show();
}
}
}
}
[Export("userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:")]
public void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response,
Action completionHandler)
{
var userInfo = response.Notification.Request.Content.UserInfo;
if (userInfo != null && userInfo.ContainsKey(new NSString("aps")))
{
NSDictionary aps = userInfo.ObjectForKey(new NSString("aps")) as NSDictionary;
NSDictionary message = aps.ObjectForKey(new NSString("alert")) as NSDictionary;
NSDictionary metadataNSDict = aps.ObjectForKey(new NSString("metadata")) as NSDictionary;
var metaDataDict = metadataNSDict.ConvertToDictionary();
var metaJson = metaDataDict.FromDictionaryToJson();
var metadata = JsonConvert.DeserializeObject<NotificationMetadata>(metaJson);
if (message != null && metadata != null)
{
NotificationGoToPage(metadata);
}
}
completionHandler();
}
[Export("userNotificationCenter:willPresentNotification:withCompletionHandler:")]
public void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification,
Action<UNNotificationPresentationOptions> completionHandler)
{
// Do something with the notification
Console.WriteLine("Active Notification: {0}", notification);
// Tell system to display the notification anyway or use
// `None` to say we have handled the display locally.
completionHandler(UNNotificationPresentationOptions.Alert |
UNNotificationPresentationOptions.Sound |
UNNotificationPresentationOptions.Badge);
}
public override void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
{
ProcessNotification(userInfo, false);
}
Even i have tried using DidReceiveRemoteNotification but this doesn't capture the Background
[Export("didReceiveRemoteNotification:")]
public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo,
Action<UIBackgroundFetchResult> completionHandler)
{
if (application.ApplicationState == UIApplicationState.Active)
{
completionHandler(UIBackgroundFetchResult.NoData);
}
else if (application.ApplicationState == UIApplicationState.Background)
{
}
else
{
completionHandler(UIBackgroundFetchResult.NoData);
}
}
Entitlements.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>production</string>
</dict>
</plist>
Info.plist
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>

Try this:
public override void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
{
NSString title = ((userInfo["aps"] as NSDictionary)["alert"] as NSDictionary)["title"] as NSString;
NSString message = ((userInfo["aps"] as NSDictionary)["alert"] as NSDictionary)["body"] as NSString;
if (!App.IsMinimized) // check if app is minimized
{
// your logic
}
}
In your app
public static bool IsMinimized { set; get; } = false;
protected override void OnStart()
{
// Handle when your app starts
IsMinimized = false;
}
protected override void OnSleep()
{
// Handle when your app sleeps
IsMinimized = true;
}
protected override void OnResume()
{
// Handle when your app resumes
IsMinimized = false;
}

Related

Xamarin Forms iOS Remote Notifications Not Displaying

Remote Notifications are not displaying on the device.
We are using iOS 11.2 and Twilio.
We have generated the APN in Apple Developer Portal and exported the
certificate and key into Twilio.
Twilio says the message is "sent," but it never displays on the
device.
The goal is to send a message with a simple header and body text, and have that display as a remote push notification on the device.
The Xamarin documentation seems incomplete, and I cannot find clear instructions on how to handle displaying the notification. I have looked at the Xamarin samples, but they mostly cover local notifications.
Questions are below in the comments. What is missing?
using Foundation;
using UserNotifications;
using UIKit;
namespace MyNotifications.iOS
{
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
protected UIWindow window;
protected string deviceToken = string.Empty;
public string DeviceToken { get { return deviceToken; } }
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
// check for a notification while running
if (options != null)
{
if (options.ContainsKey(UIApplication.LaunchOptionsRemoteNotificationKey))
{
NSDictionary remoteNotification = options[UIApplication.LaunchOptionsRemoteNotificationKey] as NSDictionary;
if (remoteNotification != null)
{
//1) is this necessary to handle??? if so, how to display? what are the properties from the remoteNotification object that contain the text?
}
}
}
//this prompts for permissions, which are set
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
var notificationSettings = UIUserNotificationSettings.GetSettingsForTypes(
UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound, null
);
app.RegisterUserNotificationSettings(notificationSettings);
app.RegisterForRemoteNotifications();
}
else
{
UIRemoteNotificationType notificationTypes = UIRemoteNotificationType.Alert | UIRemoteNotificationType.Badge;
UIApplication.SharedApplication.RegisterForRemoteNotificationTypes(notificationTypes);
}
UNUserNotificationCenter.Current.GetNotificationSettings((settings) =>
{
var alertsAllowed = (settings.AlertSetting == UNNotificationSetting.Enabled);
});
// Request notification permissions from the user
UNUserNotificationCenter.Current.RequestAuthorization(UNAuthorizationOptions.Alert, (approved, err) =>
{
// 2) how do we handle this??? what comes next?
});
return base.FinishedLaunching(app, options);
}
// 3) does this override need to do anything???
public override void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
{
// 4) should all of these trigger a notification? does that have to happen manually?
if (application.ApplicationState == UIApplicationState.Active)
{
ProcessPushNotification(userInfo, true);
}
else if (application.ApplicationState == UIApplicationState.Background)
{
ProcessPushNotification(userInfo, true);
}
else if (application.ApplicationState == UIApplicationState.Inactive)
{
ProcessPushNotification(userInfo, true);
}
}
protected void ProcessPushNotification(NSDictionary userInfo, bool isAppAlreadyRunning)
{
if (userInfo == null) return;
if (isAppAlreadyRunning)
{
// 5) do we need to generate our own view???
}
else
{
// 6) how to handle in the background???
}
}
// APNS background
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
deviceToken = deviceToken.ToString();
}
// Handle errors and offline
public override void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
{
// 7) what to do here???
}
}
}
Microsoft Docs has a few guides on how to deal with push notifications on both platforms, iOS and Android. Most of them are sending notifications from an Azure Notifications Hub but in this case, it shouldn't make any difference, because your question is about displaying push notifications, and not sending them.
The guide on how to send and receive notifications in a Xamarin.Forms app gives you an idea of the complete end-to-end setup. There are also two slightly different guides with a focus on Azure here and here
And for rendering messages on iOS, if the app is backgrounded, the notification is rendered by iOS without any custom code so if the app is properly configured and signed, you should be able to see a push notifications without any additional client-side code, just make sure you test on a device (sims doesn't support pushes) and valid profile with pushes enabled.

How to do a Close Confirmation with a Xamarin Forms mac App?

I have a Xamarin.Forms application for iOS, Android, and now hopefully Mac. I made all the adjustments for the UI to look great on Mac. Submitted it for approval where it was rejected because the user can close the window while the app and menu bar is still running. So I figure I would just add a confirmation pop-up asking if they want to exit the app when they try to close the window.
OK = Terminate the App.
Cancel = Keep the window open.
I find lots of articles on how to handle this with a Xamarin.Mac app, but nothing on how to handle Xamarin.Forms on Mac. The FormsApplicationDelegate does not give access to the View Controller or the Window Delegate in order to override the WindowShouldClose method. I found that I can use NSAlert to do the pop-up which works great. Now I cannot find anything on what to do when the user responds. Open to suggestions.
private void Window_WillClose(object sender, System.EventArgs e)
{
NSNotification senderNotification = ((NSNotification)sender);
NSWindow closingWindow = (NSWindow)senderNotification.Object;
var confirmation = new NSAlert()
{
AlertStyle = NSAlertStyle.Warning,
InformativeText = "Are you sure you want to exit the App?",
MessageText = "Exit?"
};
confirmation.AddButton("OK");
confirmation.AddButton("Cancel");
var result = confirmation.RunModal();
if (result == 1001)
{
//Cancel closing the window
}
else
{
//terminate the app
}
}
After a lot of experimenting, I did find a solution. Here is what officially passed Apple's review. It requires that n menu action is linked as "New Window". It keeps tracks of the open windows and when there is only one left, it prompts to close the app. If the user closes all the windows and keeps the app running, they have the option to open a new window in the menu.
[Register("AppDelegate")]
public class AppDelegate : FormsApplicationDelegate
{
public NSWindow window;
private bool closeApp;
private List<NSWindow> openWindows;
public override NSWindow MainWindow
{
get { return window; }
}
public AppDelegate()
{
this.closeApp = false;
this.openWindows = new List<NSWindow>();
createNewWindow();
}
[Action("newWindow:")]
public void newWindow(NSObject sender)
{
createNewWindow();
this.window.MakeKeyAndOrderFront(sender);
LoadApplication(new App());
base.DidFinishLaunching(null);
}
private void createNewWindow()
{
var style = NSWindowStyle.Closable | NSWindowStyle.Resizable | NSWindowStyle.Titled;
var rect = new CoreGraphics.CGRect(200, 1000, 1024, 768);
window = new MainWindow(rect, style, NSBackingStore.Buffered, false);
window.Title = "MyApp"; // choose your own Title here
window.TitleVisibility = NSWindowTitleVisibility.Hidden;
window.WillClose += Window_WillClose;
openWindows.Add(window);
}
private void Window_WillClose(object sender, System.EventArgs e)
{
openWindows.Remove((NSWindow)((NSNotification)sender).Object);
if (openWindows.Count == 0)
{
var confirmation = new NSAlert()
{
AlertStyle = NSAlertStyle.Warning,
InformativeText = "Do you want to exit the app?",
MessageText = "Exit?"
};
confirmation.AddButton("Yes");
confirmation.AddButton("No");
var result = confirmation.RunModal();
if (result == 1001)
{
this.closeApp = false;
}
else
{
//terminate the app
this.closeApp = true;
}
}
}
public override bool ApplicationShouldTerminateAfterLastWindowClosed(NSApplication sender)
{
return closeApp;
}
public override void DidFinishLaunching(NSNotification notification)
{
Forms.Init();
LoadApplication(new App());
base.DidFinishLaunching(notification);
}
}

How to send OnActivityResult To a specific page in xamarin forms

I am using a custom button renderer for google sign In in xamarin forms page its working fine I get the signin resultin MainActivity Now i want to send this data from MainActivity and AppDelegate to the Particular page in Xamarin Forms.
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (requestCode == 9001)
{
Utilities.Configuration.UpdateConfigValue(Utilities.Constants.loggedInflag,string.Empty);
GoogleSignInResult result = Android.Gms.Auth.Api.Auth.GoogleSignInApi.GetSignInResultFromIntent(data);
if (result.IsSuccess)
{
GoogleSignInAccount acct = result.SignInAccount;
var token = acct.IdToken;
//I wan to send the 'accnt' to a Page in xamarin forms
}
else
{
//Signin Failure send response to Page in xamarin forms
}
}
}
Xamarin.Forms runs only in one Activity on Android. So if your url request comes out in a different Activity, you have to switch back to the MainActivity before you can use the normal XF navigation.
I do this when a user opens a file associated with my app.
[Activity(Label = "LaunchFileActivity")]
public class LaunchFileActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
if (Intent.Data != null)
{
var uri = Intent.Data;
if (uri != null)
{
Intent i = new Intent(this, typeof(MainActivity));
i.AddFlags(ActivityFlags.ReorderToFront);
i.PutExtra("fileName", uri.Path);
this.StartActivity(i);
}
}
this.FinishActivity(0);
}
}
And in MainActivity:
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
Intent = intent;
}
protected override void OnPostResume()
{
base.OnPostResume();
if (Intent.Extras != null)
{
string fileName = Intent.Extras.GetString("fileName");
if (!string.IsNullOrEmpty(fileName))
{
// do something with fileName
}
Intent.RemoveExtra("fileName");
}
}
Xamarin forms runs on one activity, which is most like your main activity.
There are two sample projects that show you how to communicate between native and form parts of the code, which can be found here
https://github.com/xamarin/xamarin-forms-samples/tree/master/Forms2Native
https://github.com/xamarin/xamarin-forms-samples/tree/master/Native2Forms
However, to answer your question, you would do something like the following
private const int MyRequestCode = 101;
//Start activity for result
var contactPickerIntent = new Intent(Intent.ActionPick, Android.Provider.ContactsContract.Contacts.ContentUri);
context.StartActivityForResult(contactPickerIntent, MyRequestCode);
and then in your main activity (the activity that initializes your xamarin forms application (using global::Xamarin.Forms.Forms.Init(this, bundle);)
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
if (requestCode == MyRequestCode && resultCode == Result.Ok)
{
}
}

Xamarin Form and Bluetooth disconnect

Is there an event of a Android.Bluetooth class that is raised when the device is disconnected?
You have to set add a BluetoothGattCallback
public class MyGattCallback : BluetoothGattCallback
{
public override void OnConnectionStateChange(BluetoothGatt gatt, GattStatus status, ProfileState newState)
{
base.OnConnectionStateChange(gatt, status, newState);
if(newState == ProfileState.Disconnected)
{
// disconnected
}
}
}
And when you connect your device, you pass it:
BluetoothDevice device = ...;
var callback = new MyGattCallback();
device.ConnectGatt(Application.Context, false, callback);

Xamarin iOS handleAction never call

I have an Xamarin.Forms app where I want to use push notifications. I used azure push notification hub from mobile service to push the message. I want to create an interactive banner with some action "Action1" and "Action2" on iOS. I am able to receive the push message with "Action1" and "Action2" button. But tapping on that button does not do any thing. following is my code:
private static void RegisterPushAction()
{
UIMutableUserNotificationAction acceptAction = new UIMutableUserNotificationAction ();
acceptAction.Title = "Action1";
acceptAction.Identifier = "ACCEPT_IDENTIFIER";
acceptAction.ActivationMode = UIUserNotificationActivationMode.Background;
acceptAction.Destructive = false;
acceptAction.AuthenticationRequired = false;
UIMutableUserNotificationAction denyAction = new UIMutableUserNotificationAction ();
denyAction.Title = "Action2;
denyAction.Identifier = "DENY_IDENTIFIER";
denyAction.ActivationMode = UIUserNotificationActivationMode.Background;
denyAction.Destructive = false;
denyAction.AuthenticationRequired = false;
UIMutableUserNotificationCategory acceptCategory = new UIMutableUserNotificationCategory ();
acceptCategory.Identifier = "JOIN_CATEGORY";
acceptCategory.SetActions (new UIUserNotificationAction[]{acceptAction,denyAction}, UIUserNotificationActionContext.Default);
NSSet categories = new NSSet (acceptCategory);
//iOS 7
if (Convert.ToInt16 (UIDevice.CurrentDevice.SystemVersion.Split ('.') [0].ToString ()) < 8) {
UIRemoteNotificationType notificationTypes = UIRemoteNotificationType.Alert | UIRemoteNotificationType.Badge | UIRemoteNotificationType.Sound;
UIApplication.SharedApplication.RegisterForRemoteNotificationTypes (notificationTypes);
} else {
UIUserNotificationType types = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound;
UIUserNotificationSettings settings = UIUserNotificationSettings.GetSettingsForTypes (types, categories);
UIApplication.SharedApplication.RegisterUserNotificationSettings (settings);
UIApplication.SharedApplication.RegisterForRemoteNotifications ();
}
}
To handle this I have following method:
public override void HandleAction (UIApplication application, string actionIdentifier, NSDictionary remoteNotificationInfo, Action completionHandler)
{
if (actionIdentifier.Equals ("ACCEPT_IDENTIFIER")) {
//var alert = notification.AlertBody;
//new UIAlertView ("Msg", alert, null, "OK", null).Show ();
//NotificationHelper.ReceivePushMessage (alert);
ProcessNotification (remoteNotificationInfo, false);
}
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
completionHandler ();
}
The above HandleAction method gets never called and it always calls ReceivedRemoteNotification
public override async void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
{
ProcessNotification (userInfo, false);
}
I have different types of notificatiosn (e.g. a simple banner notification and a banner with Action)
Any thoughts on this?
I do not know if you still have the problem but you should try to write the function HandleAction like this:
public override void HandleAction (UIApplication application, String actionIdentifier, NSDictionary remoteNotificationInfo, NSDictionary responseInfo, Action completionHandler)
Let me know if it worked!

Resources