Push Notification use payload to open correct page - android

First off I am using Xamarin Forms for a WP8, iOS and Android app.
Goal:
I want to go to a specific page when the toast is clicked depending
upon the payload information of the toast notification.
I have push notifications using Azure Notification Hubs all setup and working well. I use MVVMLight and their dependency injection to setup push notifications specifically for each platform.
Each payload needs to be sent a little different due to the different formats required. With each you will notice I want to send a SignalId in the payload to perform a different action as required on the receiving device from regular push notifications.
Android
{
"data" : {
"msg" : "message in here",
"signalId" : "id-in-here",
},
}
iOS
{
"aps" : { "alert" : "message in here" },
"signalId" : "id-in-here"
}
Windows Phone 8
<?xml version="1.0" encoding="utf-8"?>
<wp:Notification xmlns:wp="WPNotification">
<wp:Toast>
<wp:Text1>category</wp:Text1>
<wp:Text2>message in here</wp:Text2>
<wp:Param>?signalId=id-in-here</wp:Param>
</wp:Toast>
</wp:Notification>
.
Question:
How do I get this information in a Xamarin Forms app and redirect to
the appropriate page when the application is reactivated because the
user clicked on the toast notification?
I want to get the payload information when the app loads, then say, yes this contains a SignalId, lets redirect to this page.
At the moment all it does it show the application when a toast notification is clicked. Must I do it specific to the app, or is there a Xamarin Forms way?
Any help appreciated even if you only know how to do it for one platform, I can probably work my way around the other platforms from there.

I have found the way to do it for all platforms. Windows has been tested, Android and iOS haven't.
Windows and iOS work on a show toast notification if the app is in the background, or let your code deal with it if the app is in the foreground. Android shows the toast regardless of application status.
With Windows Phone 8 I need to go to the MainPage.xaml.cs and add in this override.
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (this.NavigationContext.QueryString.ContainsKey("signalId"))
{
var signalId = this.NavigationContext.QueryString["signalId"];
var id = Guid.Empty;
if (signalId != null
&& Guid.TryParse(signalId, out id)
&& id != Guid.Empty)
{
this.NavigationContext.QueryString.Clear();
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
// Do my navigation to a new page
});
}
}
}
For Android in the GcmService
protected override void OnMessage(Context context, Intent intent)
{
Log.Info(Tag, "GCM Message Received!");
var message = intent.Extras.Get("msg").ToString();
var signalId = Guid.Empty;
if (intent.Extras.ContainsKey("signalId"))
{
signalId = new Guid(intent.Extras.Get("signalId").ToString());
}
// Show notification as usual
CreateNotification("", message, signalId);
}
Then in the CreateNotification function put some extra information in the Intent.
var uiIntent = new Intent(this, typeof(MainActivity));
if (signalId != Guid.Empty)
{
uiIntent.PutExtra("SignalId", signalId.ToString());
}
Then in the MainActivity.cs override this function
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
if (data.HasExtra("SignalId"))
{
Guid signalId = new Guid(data.GetStringExtra("SignalId"));
if (signalId != Guid.Empty)
{
data.RemoveExtra("SignalId");
// Do you navigation
}
}
}
In iOS you will notice I have enhanced the default ProcessNotification()
void ProcessNotification(NSDictionary options, bool fromFinishedLaunching)
{
// Check to see if the dictionary has the aps key. This is the notification payload you would have sent
if (null != options && options.ContainsKey(new NSString("aps")))
{
//Get the aps dictionary
var aps = options.ObjectForKey(new NSString("aps")) as NSDictionary;
var alert = string.Empty;
//Extract the alert text
// NOTE: If you're using the simple alert by just specifying
// " aps:{alert:"alert msg here"} " this will work fine.
// But if you're using a complex alert with Localization keys, etc.,
// your "alert" object from the aps dictionary will be another NSDictionary.
// Basically the json gets dumped right into a NSDictionary,
// so keep that in mind.
if (aps.ContainsKey(new NSString("alert")))
alert = ((NSString) aps[new NSString("alert")]).ToString();
// If this came from the ReceivedRemoteNotification while the app was running,
// we of course need to manually process things like the sound, badge, and alert.
if (!fromFinishedLaunching)
{
//Manually show an alert
if (!string.IsNullOrEmpty(alert))
{
var signalId = new Guid(options.ObjectForKey(new NSString("signalId")) as NSString);
// Show my own toast with the signalId
}
}
}
}
Then in the FinishedLaunching function check if there is any payload
// Check if any payload from the push notification
if (options.ContainsKey("signalId"))
{
var signalId = new Guid(options.ObjectForKey(new NSString("signalId")) as NSString);
// Do the navigation here
}

Related

How do I handle when user taps on local notification in Xamarin.Forms?

I am so frustrated with the Xamarin documentations. I am looking at doing one of the most basic thing, which is:
When a local notification comes out, a user taps the notification. It launches the App.
How do I handle this so that the app launches and acts according to the notification?
This is the official Xamarin Local Notifications documentation....
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/local-notifications
In Stack Overflow or google searches I can't find any Solutions.
Nothing is mentioned on how to act on a tap to the notification
Ah yes, the docs can be improved! They just assume that you understand it, you should add a pull request or an issue to improve that page!
After you have created:
The INotificationManager interface in the Core project,
The iOS platform changes- iOSNotificationManager, iOSNotificationReceiver, AppDelegate changes
The Android platform changes- AndroidNotificationManager, MainActivity changes, if needed BroadcastReceiver
Then in the constructor of the page where you want to handle the changes, get the instance of the NotificationManager, and then subscribe to the NotificationReceived event which should get called when a user taps on the notification item.
public DashboardPage()
{
InitializeComponent();
notificationManager = DependencyService.Get<INotificationManager>();
notificationManager.NotificationReceived += (sender, eventArgs) =>
{
var evtData = (NotificationEventArgs)eventArgs;
Console.WriteLine("Title & Message: " + evtData.Title + evtData.Message);
};
}
You could try Plugin.LocalNotification too, it has a feature can hanle tap event, like:
public partial class App : Application
{
public App()
{
InitializeComponent();
// Local Notification tap event listener
NotificationCenter.Current.NotificationTapped += OnLocalNotificationTapped;
MainPage = new MainPage();
}
private void OnLocalNotificationTapped(NotificationEventArgs e)
{
// your code goes here
}
}
edit:
On iOS:
You can add code in DidRecieveNotiticationResponse(),like:
public override void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler)
{
if (response.IsDefaultAction)
{
ProcessNotification(response.Notification);
}
Console.WriteLine("Called");
App.Current.MainPage=new Page();
completionHandler();
}

NullReferenceException on App.Current.MainPage

I have a Xamarin.Forms app that uses push notifications. For some reason, the following line:
(App.Current.MainPage as MainPage)?.AddMessage(body);
that is called from Android native OnMessageReceived(), throws NullReferenceException.
Why can this happen? Isn't App.Current to be accessible from the platform-specific project?
Edit:
Here is the full OnMessageReceived() code:
public override void OnMessageReceived(RemoteMessage message)
{
base.OnMessageReceived(message);
string messageBody;
if (message.GetNotification() != null)
{
messageBody = message.GetNotification().Body;
}
// NOTE: test messages sent via the Azure portal will be received here
else
{
messageBody = message.Data.Values.First();
}
// convert the incoming message to a local notification
SendLocalNotification(messageBody);
// send the incoming message directly to the MainPage
SendMessageToMainPage(messageBody);
}
App.Current.MainPage may contain any Page, not necessary your type MainPage, for example it could be NavigationPage. As the result of casting is null it is clear that it is of some other type.
Also it could happen that nothing is assigned to it.

Filter AppCenter push notifications depending on the current logged in user

Using AppCenter I am able to send push notification to all my devices with my Xamarin Forms (android only) app.
Since my devices, are going to be shared, I can't do the filter of the notifications on the AppCenter side based on the devices IDs.
I need to make the filter based on the current logged in user to my application. For this with the push notification I also send the WorkerID, which serves as a filter.
While I'm able to do this filter when the app is in foreground, its not working when the app is in background or not running.(normal behaviour since the push event is in App Start)
protected override void OnStart()
{
// This should come before AppCenter.Start() is called
// Avoid duplicate event registration:
if (!AppCenter.Configured)
{
Push.PushNotificationReceived += (sender, e) =>
{
var title = e.Title;
var message = e.Message;
// If app is in background title and message are null
// App in foreground
if (!string.IsNullOrEmpty(title))
{
foreach (string key in e.CustomData.Keys)
{
if (e.CustomData[key] == Settings.WorkerID)
{
Current.MainPage.DisplayAlert(title, message, "OK");
}
}
}
};
}
// Handle when your app starts
AppCenter.Start("android=xxxxxxxxxxxxxxxxxx", typeof(Push));
}
Is there a way to intercept and filter the push notifications when the app is in background and block them when the app is not running (since no user is yet logged in) ?
To filter users when sending push notifications it's preferable to set user id:
AppCenter.SetUserId("your-user-id");
Then on the appcenter.ms go to Push > Send notificaiton. Fill in the required fields.
And then in the second step select User list instead of All registered devices. Then enter user ids, separated by commas.
Through the document, you can enable-or-disable-push-at-runtime, so can enable or disable push when use go to background or go back to foreground, like this:
protected override void OnSleep()
{
// Handle when your app sleeps
Push.SetEnabledAsync(false);
}
protected override void OnResume()
{
// Handle when your app resumes
Push.SetEnabledAsync(true);
}
You can also enable or disable push when user login or login out:
public void userLogin() {
Push.SetEnabledAsync(true);
}
public void userLoginOut() {
Push.SetEnabledAsync(false);
}
Set it in the right place to meet your requirement.

How to utilize Android Nougat's Direct Reply feature with a NotificationListener?

My app is using a NotificationListener to read out messages from various 3rd party apps, for example WhatsApp.
So far I was able to send a reply if only one chat is unread, the code is below.
However, in the case with WhatsApp, getNotification().actions returns a null object when more than two chats are unread, as the messages are bundled together. As you can see in the pictures below, if the notifications are extended there is an option to send a direct reply as well, therefore I am certain that it is possible to utilize this, also I think apps like PushBullet are using this method.
How could I access the RemoteInput of that notification?
public static ReplyIntentSender sendReply(StatusBarNotification statusBarNotification, String name) {
Notification.Action actions[] = statusBarNotification.getNotification().actions;
for (Notification.Action act : actions) {
if (act != null && act.getRemoteInputs() != null) {
if (act.title.toString().contains(name)) {
if (act.getRemoteInputs() != null)
return new ReplyIntentSender(act);
}
}
}
return null;
}
public static class ReplyIntentSender {
[...]
public final Notification.Action action;
public ReplyIntentSender(Notification.Action extractedAction) {
action = extractedAction;
[...]
}
private boolean sendNativeIntent(Context context, String message) {
for (android.app.RemoteInput rem : action.getRemoteInputs()) {
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putCharSequence(rem.getResultKey(), message);
android.app.RemoteInput.addResultsToIntent(action.getRemoteInputs(), intent, bundle);
try {
action.actionIntent.send(context, 0, intent);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
return false;
}
}
Some explanation how the above code works: Once a notification is received the app tries to get the actions and checks if the name is in the title of a remoteInput (normally it is in the format of "Reply to $NAME"), if that is found the Action is saved into a ReplyIntentSender class, which, when triggered by sendNativeIntent, cycles through all RemoteInputs of that Action and adds the message to the intent. If more than one chat is unread, getNotification().actions returns null.
Below are two screenshots, the first one where it is working without any problems and the second one where it doesn't.
You can consider this as my suggestion. I have done bit research on this and come up with following conclusions.(Also it looks like you have done plenty of research on this so it might be possible that you aware about what I wrote below)
Numerous apps send Wear specific notifications, and many of those contain actions accessible from an Android Wear device. We can grab those Wear notifications on the device, extracting the actions, finding the reply action (if one exists), populating it with our own response and then executing the PendingIntent which sends our response back the original app for it to send on to the recipient.
To do so you can refer this link (A nice workaround by Rob J). You can also refer this link in this context (Great research work done by MichaƂ Tajchert).(You might need to work around with NotificationCompat.isGroupSummary)
This is what I feel(Might be I am totally wrong)
.actions method returns Array of all Notification.Action
structures attached to current notification by addAction(int,
CharSequence, PendingIntent), Here addAction method is deprecated
one so it might not working as intended.
I am not able to test this at my end otherwise I will love to provide a working solution with code.
Hope this will help you. Happy Coding!!!

Message specific user in a Phonegap/Cordova app using SignalR 2

I am attempting to create a real-time communication capability for a Phonegap/Cordova app. I am using SignalR 2 to handle the communication.
The thing I am struggling with is getting a message to a particular user. Every single example out there shows saving Context.User.Identity.Name, which is useless to me because the remote site's User.Identity context is not shared by my phonegap app.
In essence, I am not authenticating a user in the traditional sense, so I need another way of linking the SignalR connectionID with the username I pass along.
Taken from the official ASP.NET signalr Examples, I have the following code which overrides the OnConnected event. Unfortunately it takes no parameters and expects User.Identity to be not null:
public override Task OnConnected()
{
using (var db = new UserContext())
{
// Retrieve user.
var user = db.Users
.Include(u => u.Rooms)
.SingleOrDefault(u => u.UserName == Context.User.Identity.Name);
// If user does not exist in database, must add.
if (user == null)
{
user = new User()
{
UserName = Context.User.Identity.Name
};
db.Users.Add(user);
db.SaveChanges();
}
else
{
// Add to each assigned group.
foreach (var item in user.Rooms)
{
Groups.Add(Context.ConnectionId, item.RoomName);
}
}
}
return base.OnConnected();
}
Now, maybe what I'd need is to have a version of this method that takes a string as a parameter and then I'd use that as my user identifier.
But how to go about that?
You need to create a new IUserIdProvider for the user and use dependency injection to register your provider and use it.
public interface IUserIdProvider
{
string GetUserId(IRequest request);
}
Register your provider with Global Host
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => new MyIdProvider());
Usage:
public class MyHub : Hub
{
public void Send(string userId, string message)
{
Clients.User(userId).send(message);
}
}
Taken from: http://www.asp.net/signalr/overview/guide-to-the-api/mapping-users-to-connections#IUserIdProvider

Categories

Resources