Actionable push notification in Xamarin iOS - c#

var acceptAction = UNNotificationAction.FromIdentifier("AcceptAction", "Accept", UNNotificationActionOptions.None);
var declineAction = UNNotificationAction.FromIdentifier("DeclineAction", "Decline", UNNotificationActionOptions.None);
// Create category
var meetingInviteCategory = UNNotificationCategory.FromIdentifier("MeetingInvitation",
new UNNotificationAction[] { acceptAction, declineAction }, new string[] { }, UNNotificationCategoryOptions.CustomDismissAction);
// Register category
var categories = new UNNotificationCategory[] { meetingInviteCategory };
UNUserNotificationCenter.Current.SetNotificationCategories(new NSSet<UNNotificationCategory>(categories));
how can you receive a custom actionable push notification and where need to put the above code in which file?

Before an iOS app can send notifications to the user the app must be registered with the system and, because a notification is an interruption to the user, an app must explicitly request permission before sending them.
Notification permission should be requested as soon as the app launches by adding the following code to the FinishedLaunching method of the AppDelegate and setting the desired notification type (UNAuthorizationOptions):
...
using UserNotifications;
...
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
....
//after iOS 10
if(UIDevice.CurrentDevice.CheckSystemVersion(10,0))
{
UNUserNotificationCenter center = UNUserNotificationCenter.Current;
center.RequestAuthorization(UNAuthorizationOptions.Alert | UNAuthorizationOptions.Sound | UNAuthorizationOptions.UNAuthorizationOptions.Badge, (bool arg1, NSError arg2) =>
{
});
center.Delegate = new NotificationDelegate();
}
else if(UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
var settings = UIUserNotificationSettings.GetSettingsForTypes(UIUserNotificationType.Alert| UIUserNotificationType.Badge| UIUserNotificationType.Sound,new NSSet());
UIApplication.SharedApplication.RegisterUserNotificationSettings(settings);
}
return true;
}
New to iOS 10, an app can handle Notifications differently when it is in the foreground and a Notification is triggered. By providing aUNUserNotificationCenterDelegate and implementing theUserNotificationCentermethod, the app can take over responsibility for displaying the Notification. For example:
using System;
using ObjCRuntime;
using UserNotifications;
namespace xxx
{
public class NotificationDelegate:UNUserNotificationCenterDelegate
{
public NotificationDelegate()
{
}
public override 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);
}
public override void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler)
{
// Take action based on Action ID
switch (response.ActionIdentifier)
{
case "reply":
// Do something
break;
default:
// Take action based on identifier
if (response.IsDefaultAction)
{
// Handle default action...
}
else if (response.IsDismissAction)
{
// Handle dismiss action
}
break;
}
// Inform caller it has been handled
completionHandler();
}
}
}

Related

Conditional handling Firebase push notifications

I have a Xamarin.Forms app that supports Android and iPhone. The app sends and receives push notifications using Firebase for Android and Azure Notification Hub for iOS. If the app sends a notification from the sender's phone, multiple people in the group receive it and can handle it. But it is not what we need. I want only the first receiver to handle the notification, and the others should ignore it. Is there a good way to implement it?
Here is my code that handles push notifications on Android:
[Service]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
public class FirebaseService : FirebaseMessagingService
{
public override void OnMessageReceived(RemoteMessage message)
{
try
{
base.OnMessageReceived(message);
string messageBody = message.GetNotification()?.Body;
VideoCallMessage videoCallMessage = JsonConvert.DeserializeObject<VideoCallMessage>(messageBody);
if (App.UserContext.IsEmployee)
{
// convert the incoming message to a local notification
SendLocalNotification(messageBody);
}
}
catch (Exception ex)
{
Log.Debug("FirebaseService.OnMessageReceived()", $"Exception in OnMessageReceived(). ErrorMessage: {ex.Message}, Stack Trace: {ex.StackTrace}");
Microsoft.AppCenter.Crashes.Crashes.TrackError(ex); // Report the exception to App Center
}
}
For iOS in Delegate.cs:
[Export("userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:")]
public void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler)
{
completionHandler();
NSDictionary userInfo = response.Notification.Request.Content.UserInfo;
ProcessNotification(userInfo);
}
[Export("userNotificationCenter:willPresentNotification:withCompletionHandler:")]
public void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
{
completionHandler(UNNotificationPresentationOptions.Sound | UNNotificationPresentationOptions.Alert);
NSDictionary userInfo = notification.Request.Content.UserInfo;
ProcessNotification(userInfo);
}

How is the device able to receive notifications even though SBNotificationHub.RegisterNativeAsync() is never called?

This code results in the device receiving a test notification, but there's no call to RegisterNativeAsync unless there's an error. Thus, how does the hub know about the device?
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
SBNotificationHub Hub { get; set; }
public const string ConnectionString = "Endpoint=xxx";
public const string NotificationHubPath = "xxx";
public override bool FinishedLaunching(UIApplication uiApplication, NSDictionary launchOptions)
{
var settings = UIUserNotificationSettings.GetSettingsForTypes(UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound, new NSSet());
UIApplication.SharedApplication.RegisterUserNotificationSettings(settings);
UIApplication.SharedApplication.RegisterForRemoteNotifications();
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
return base.FinishedLaunching(uiApplication, launchOptions);
}
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
// Create a new notification hub with the connection string and hub path
Hub = new SBNotificationHub(ConnectionString, NotificationHubPath);
// Unregister any previous instances using the device token
Hub.UnregisterAllAsync(deviceToken, (error) =>
{
if (error != null)
{
// Error unregistering
return;
}
// Register this device with the notification hub
Hub.RegisterNativeAsync(deviceToken, null, (registerError) =>
{
if (registerError != null)
{
// Error registering
}
});
});
}
}
According to RegisteredForRemoteNotifications - Xamarin, this method has nothing to do with registering itself.1 As far a I can tell from RegisteredForRemoteNotifications never triggered — Xamarin Forums, applications are supposed to override it, and it serves as a handler that is invoked after the user allows the application to receive push notifications.
In fact, the code you've given is your code, not library's.
1And examining the source code of UnregisterAllAsync in ILSpy decompilation of Xamarin.Azure.NotificationHubs.iOS.dll confirms as such.

Have I set up my Android push notifications correctly?

I am setting up push notifications in my Xamarin Forms app for my Android project. I have been following the online documentation (https://developer.xamarin.com/guides/xamarin-forms/cloud-services/push-notifications/azure/) to get it working but I am having some problems
1 - The notifications don't arrive when I send a test from my Azure Notification Hub.
2 - The app crashes during debug after a certain amount of time during the Register method.
I believe that the problem is being caused by its registration, I don't think the registration is working and hence the app times out which causes it to crash and also the Azure Notification Hub sends the message successfully but no device picks it up (possibly because it's not registred)
To start off, here is what I have done so far.
I have setup a project in console.firebase.google.com and taken note of the sender ID and the Server Key as instructed. I have given the Project ID a name of myapp-android-app.
I have added all the code from the documtation to my Android project.
I have added a GCM notification service to my Notification Hub in Azure and supplied it with the Server Key documented from my Firebase app registration.
I have added my google account to my Andoird Emulator
Here is the source code from my Android project.
MainActivity.cs
using System;
using Android.App;
using Android.Content.PM;
using Android.OS;
using Gcm.Client;
namespace MyApp.Droid
{
[Activity(Label = "#string/app_name", Theme = "#style/MyTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
//public static MainActivity CurrentActivity { get; private set; }
// Create a new instance field for this activity.
static MainActivity instance = null;
// Return the current activity instance.
public static MainActivity CurrentActivity
{
get
{
return instance;
}
}
protected override void OnCreate(Bundle bundle)
{
instance = this;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
Microsoft.WindowsAzure.MobileServices.CurrentPlatform.Init();
LoadApplication(new App());
try
{
// Check to ensure everything's setup right
GcmClient.CheckDevice(this);
GcmClient.CheckManifest(this);
// Register for push notifications
System.Diagnostics.Debug.WriteLine("Registering...");
GcmClient.Register(this, PushHandlerBroadcastReceiver.SENDER_IDS);
}
catch (Java.Net.MalformedURLException)
{
CreateAndShowDialog("There was an error creating the client. Verify the URL.", "Error");
}
catch (Exception e)
{
CreateAndShowDialog(e.Message, "Error");
}
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
}
void CreateAndShowDialog(String message, String title)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.SetMessage(message);
builder.SetTitle(title);
builder.Create().Show();
}
}
}
GcmService.cs
using Android.App;
using Android.Content;
using Android.Media;
using Android.Support.V4.App;
using Android.Util;
using Gcm.Client;
using Microsoft.WindowsAzure.MobileServices;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
[assembly: Permission(Name = "#PACKAGE_NAME#.permission.C2D_MESSAGE")]
[assembly: UsesPermission(Name = "#PACKAGE_NAME#.permission.C2D_MESSAGE")]
[assembly: UsesPermission(Name = "com.google.android.c2dm.permission.RECEIVE")]
[assembly: UsesPermission(Name = "android.permission.INTERNET")]
[assembly: UsesPermission(Name = "android.permission.WAKE_LOCK")]
//GET_ACCOUNTS is only needed for android versions 4.0.3 and below
[assembly: UsesPermission(Name = "android.permission.GET_ACCOUNTS")]
namespace MyApp.Droid
{
[BroadcastReceiver(Permission = Gcm.Client.Constants.PERMISSION_GCM_INTENTS)]
[IntentFilter(new string[] { Gcm.Client.Constants.INTENT_FROM_GCM_MESSAGE }, Categories = new string[] { "#PACKAGE_NAME#" })]
[IntentFilter(new string[] { Gcm.Client.Constants.INTENT_FROM_GCM_REGISTRATION_CALLBACK }, Categories = new string[] { "#PACKAGE_NAME#" })]
[IntentFilter(new string[] { Gcm.Client.Constants.INTENT_FROM_GCM_LIBRARY_RETRY }, Categories = new string[] { "#PACKAGE_NAME#" })]
public class PushHandlerBroadcastReceiver : GcmBroadcastReceiverBase<GcmService>
{
public static string[] SENDER_IDS = new string[] { "xxxxxxxxxx" };
}
[Service]
public class GcmService : GcmServiceBase
{
public static string RegistrationToken { get; private set; }
public GcmService()
: base(PushHandlerBroadcastReceiver.SENDER_IDS) { }
protected override void OnRegistered(Context context, string registrationToken)
{
Log.Verbose("PushHandlerBroadcastReceiver", "GCM Registered: " + registrationToken);
RegistrationToken = registrationToken;
var push = AzureService.DefaultManager.CurrentClient.GetPush();
MainActivity.CurrentActivity.RunOnUiThread(() => Register(push, null));
}
protected override void OnUnRegistered(Context context, string registrationToken)
{
Log.Error("PushHandlerBroadcastReceiver", "Unregistered RegisterationToken: " + registrationToken);
}
protected override void OnError(Context context, string errorId)
{
Log.Error("PushHandlerBroadcastReceiver", "GCM Error: " + errorId);
}
public async void Register(Microsoft.WindowsAzure.MobileServices.Push push, IEnumerable<string> tags)
{
try
{
const string templateBodyGCM = "{\"data\":{\"message\":\"$(messageParam)\"}}";
JObject templates = new JObject();
templates["genericMessage"] = new JObject
{
{"body", templateBodyGCM}
};
await push.RegisterAsync(RegistrationToken, templates);
Log.Info("Push Installation Id", push.InstallationId.ToString());
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message + ex.StackTrace);
Debugger.Break();
}
}
protected override void OnMessage(Context context, Intent intent)
{
Log.Info("PushHandlerBroadcastReceiver", "GCM Message Received!");
var msg = new StringBuilder();
if (intent != null && intent.Extras != null)
{
foreach (var key in intent.Extras.KeySet())
msg.AppendLine(key + "=" + intent.Extras.Get(key).ToString());
}
// Retrieve the message
var prefs = GetSharedPreferences(context.PackageName, FileCreationMode.Private);
var edit = prefs.Edit();
edit.PutString("last_msg", msg.ToString());
edit.Commit();
string message = intent.Extras.GetString("message");
if (!string.IsNullOrEmpty(message))
{
CreateNotification("New todo item!", "Todo item: " + message);
}
string msg2 = intent.Extras.GetString("msg");
if (!string.IsNullOrEmpty(msg2))
{
CreateNotification("New hub message!", msg2);
return;
}
CreateNotification("Unknown message details", msg.ToString());
}
void CreateNotification(string title, string desc)
{
// Create notification
var notificationManager = GetSystemService(Context.NotificationService) as NotificationManager;
// Create an intent to show the UI
var uiIntent = new Intent(this, typeof(MainActivity));
// Create the notification
// we use the pending intent, passing our ui intent over which will get called
// when the notification is tapped.
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
var notification = builder.SetContentIntent(PendingIntent.GetActivity(this, 0, uiIntent, 0))
.SetSmallIcon(Android.Resource.Drawable.SymActionEmail)
.SetTicker(title)
.SetContentTitle(title)
.SetContentText(desc)
.SetSound(RingtoneManager.GetDefaultUri(RingtoneType.Notification)) // set the sound
.SetAutoCancel(true).Build(); // remove the notification once the user touches it
// Show the notification
notificationManager.Notify(1, notification);
}
}
}
I replaced the string in: public static string[] SENDER_IDS = new string[] { "xxxxxxxx" }; with the Sender id I recorded from my Firebase console app.
When I run the app through my Android Emulator I get the following error after about 3 minutes.
Error in GcmService.cs:
Exception: System.Threading.Tasks.TaskCanceledException: A task was canceled.
seen at Line 71:
await push.RegisterAsync(RegisteToken, templates);
Does anyone know what I have done wrong? Am I correct in thinking that it's not registering correctly?
I was able to integrate Firebase Cloud Messaging in Xamarin.Forms (both Android and iOS) following this official Xamarin guide that is well explained:
https://developer.xamarin.com/guides/android/application_fundamentals/notifications/remote-notifications-with-fcm/
Looking at your code, it seems to me that you use the old Gcm service (that is deprecated) and not the new Fcm.
You should have a google-services.json file and a FirebaseMessagingService class
Then...
Note
As of now, Xamarin Forms does not handle very well the deployment of the app while developing, making a dirty reinstallation that seems to invalidate the previously obtained Notification Token and sometimes raising IllegalStateException when calling Fcm library's methods.
That's a known bug that can be resolved by cleaning the solution and
relaunching

Xamarin - null reference exception while trying to display a toast or start an activity from a service

Both trying to display a toast and to start a new activity fail. There HAS to be a way to make this work. Is there a way to notify the UI about something happening, an event or something?
Right now I am only able to log the info about the messages to console output.
The context itself is not null, but something else, possibly related to it, is causing the null reference exception.
Here's my code:
[Service(Exported = false), IntentFilter(new[] { "com.google.android.c2dm.intent.RECEIVE" })]
class MyGcmListenerService : GcmListenerService
{
public override void OnMessageReceived(string from, Bundle data)
{
string msg = data.GetString("message");
// this fails
Toast.MakeText(this, msg, ToastLength.Long).Show();
// this fails too
Intent pa = new Intent(this, typeof(PopupActivity));
pa.AddFlags(ActivityFlags.NewTask);
StartActivity(pa);
}
}
This is a context Error. This is null therefore you get a null exception pointer. It is null because it is inside a service and not an activity.
You should try to use Application.Context instead of this. It is a static in Xamarin.Droid and should return the context.
(Note that I can't test is atm)
I resolved this issue with Xamarin.Forms and MessagingCenter.
Here's my service:
[Service(Exported = false), IntentFilter(new[] { "com.google.android.c2dm.intent.RECEIVE" })]
class MyGcmListenerService : GcmListenerService
{
public override void OnMessageReceived(string from, Bundle data)
{
string msg = data.GetString("message");
// send a string via Xamarin MessagingCenter
MessagingCenter.Send<object, string>(this, "ShowAlert", msg);
}
}
And here's part of my PCL App class constructor:
// subscribe to the messages
MessagingCenter.Subscribe<object, string>(this, "ShowAlert", (s, msg) =>
{
// run on UI thread
Device.BeginInvokeOnMainThread(() =>
{
MainPage.DisplayAlert("Push message", msg, "OK");
});
});

Registering device on Azure Notification Hub from ASP.NET

I want to make use of Azure notification hub service. In all their examples. The clients directly register with azure to give the service their device token.
I want to change this model slightly in order to gain central control and also due to compatibility with existing clients.
I want all my clients to register with GCM or APNS and obtain their Token. I then want to send that token off to my own api. ASP.NET Web API. The api will then fire off a request to the Azure notification service and register on behalf of the device.
Can I achieve this? and how would I go about registering a device from the asp.net api.
In their Documentation examples it contains code to send the actual push notifications from an asp.net app. But not how to register a device (being of any type) from asp.net
Unless im being daft and missing something...
You can find this in the official documentation: Registering from your App Backend.
public class RegisterController : ApiController
{
private NotificationHubClient hub;
public RegisterController()
{
hub = NotificationHubClient.CreateClientFromConnectionString("Endpoint=sb://buildhub-ns.servicebus.windows.net/;SharedAccessKeyName=DefaultFullSharedAccessSignature;SharedAccessKey=DuWV4SQ08poV6HZly8O/KQNWv3YRTZlExJxu3pNCjGU=", "build2014_2");
}
public class DeviceRegistration
{
public string Platform { get; set; }
public string Handle { get; set; }
public string[] Tags { get; set; }
}
// POST api/register
// This creates a registration id
public async Task<string> Post()
{
return await hub.CreateRegistrationIdAsync();
}
// PUT api/register/5
// This creates or updates a registration (with provided PNS handle) at the specified id
public async void Put(string id, DeviceRegistration deviceUpdate)
{
// IMPORTANT: add logic to make sure that caller is allowed to register for the provided tags
RegistrationDescription registration = null;
switch (deviceUpdate.Platform)
{
case "mpns":
registration = new MpnsRegistrationDescription(deviceUpdate.Handle);
break;
case "wns":
registration = new WindowsRegistrationDescription(deviceUpdate.Handle);
break;
case "apns":
registration = new AppleRegistrationDescription(deviceUpdate.Handle);
break;
case "gcm":
registration = new GcmRegistrationDescription(deviceUpdate.Handle);
break;
default:
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
registration.RegistrationId = id;
registration.Tags = new HashSet<string>(deviceUpdate.Tags);
try
{
await hub.CreateOrUpdateRegistrationAsync(registration);
} catch (MessagingException e) {
ReturnGoneIfHubResponseIsGone(e);
}
}
// DELETE api/register/5
public async void Delete(string id)
{
await hub.DeleteRegistrationAsync(id);
}
private static void ReturnGoneIfHubResponseIsGone(MessagingException e)
{
var webex = e.InnerException as WebException;
if (webex.Status == WebExceptionStatus.ProtocolError)
{
var response = (HttpWebResponse)webex.Response;
if (response.StatusCode == HttpStatusCode.Gone)
throw new HttpRequestException(HttpStatusCode.Gone.ToString());
}
}
}

Categories