Reputation: 521
I've seen many different types of solutions which may have worked in the past, but nothing solid that has worked for myself. And it's a minefield of what people say works, what doesn't work, what has changed, etc. But I'm trying to find not only a solution but hopefully an understanding - because right now I am seriously confused.
What I can do right now - My Xamarin Forms app (Android) can receive push notification if the app is in the Foreground/Background, I can also intercept these notifications when the user taps them so I can tell my app what to do.
What I am trying to do - Essentially the above but in the state of where the app has been completely stopped.
I have my Firebase Messaging setup which is wired to Azure Notification Hub - unfortunately, I won't be moving away from Azure (just in case anyone suggests to drop it). Most of what I have currently is information I've managed to stitch together from various Microsoft Documentation (here I don't use AppCenter - just used this to cross-reference any useful code, here, here, and here ), other StackOverflow questions (such as here, here, and here - far too many more to link) and the Xamarin forum - so again, apologies if there is any obsolete code being used (please let me know - I have tried my best to use up-to-date methods, etc).
The type of push notifications that I am sending are Data Messages which I read up on here, I'm using custom data in my notifications therefore it is my understanding this is the correct type of push I want to send, as shown below.
{
"data": {
"title": "Title Test",
"body": "Push notification body test",
"area": "SelectedPage"
}
}
Below is the current code I have setup in my project to handle push notifications thus far.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.pushtesting" android:installLocation="auto">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
<application android:label="Push Testing">
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver" android:exported="false" />
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="${applicationId}" />
</intent-filter>
</receiver>
</application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
I have LaunchMode = LaunchMode.SingleTop
, would my understanding be correct, for this is to ensure the current Activity is still used instead of creating a new one - for instance, when the user would tap a notification - implementing it plus additional code (below) seems to suggest this is true.
protected override void OnNewIntent(Intent intent) {
base.OnNewIntent(intent);
String area = String.Empty;
String extraInfo = String.Empty;
if (intent.Extras != null) {
foreach (String key in intent.Extras.KeySet()) {
String value = intent.Extras.GetString(key);
if (key == "Area" && !String.IsNullOrEmpty(value)) {
area = value;
} else if (key == "ExtraInfo" && !String.IsNullOrEmpty(value)) {
extraInfo = value;
}
}
}
NavigationExtension.HandlePushNotificationNavigation(area, extraInfo);
}
Using OnNewIntent
to intercept the push notification when the user interacts with it.
using System;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Support.V4.App;
using Android.Util;
using Firebase.Messaging;
using PushTesting.Models;
using WindowsAzure.Messaging;
namespace PushTesting.Droid.Services {
[Service]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
public class OcsFirebaseMessaging : FirebaseMessagingService {
private const String NotificationChannelId = "1152";
private const String NotificationChannelName = "Push Notifications";
private const String NotificationChannelDescription = "Receive notifications";
private NotificationManager notificationManager;
public override void OnNewToken(String token) => SendTokenToAzure(token);
/// <summary>
/// Sends the token to Azure for registration against the device
/// </summary>
private void SendTokenToAzure(String token) {
try {
NotificationHub hub = new NotificationHub(Constants.AzureConstants.NotificationHub, Constants.AzureConstants.ListenConnectionString, Android.App.Application.Context);
Task.Run(() => hub.Register(token, new String[] { }));
} catch (Exception ex) {
Log.Error("ERROR", $"Error registering device: {ex.Message}");
}
}
/// <summary>
/// When the app receives a notification, this method is called
/// </summary>
public override void OnMessageReceived(RemoteMessage remoteMessage) {
Boolean hasTitle = remoteMessage.Data.TryGetValue("title", out String title);
Boolean hasBody = remoteMessage.Data.TryGetValue("body", out String body);
Boolean hasArea = remoteMessage.Data.TryGetValue("area", out String area);
Boolean hasExtraInfo = remoteMessage.Data.TryGetValue("extraInfo", out String extraInfo);
PushNotificationModel push = new PushNotificationModel {
Title = hasTitle ? title : String.Empty,
Body = hasBody ? body : String.Empty,
Area = hasArea ? area : String.Empty,
ExtraInfo = hasExtraInfo ? extraInfo : String.Empty
};
SendNotification(push);
}
/// <summary>
/// Handles the notification to ensure the Notification manager is updated to alert the user
/// </summary>
private void SendNotification(PushNotificationModel push) {
// Create relevant non-repeatable Id to allow multiple notifications to be displayed in the Notification Manager
Int32 notificationId = Int32.Parse(DateTime.Now.ToString("MMddHHmmsss"));
Intent intent = new Intent(this, typeof(MainActivity));
intent.AddFlags(ActivityFlags.ClearTop | ActivityFlags.SingleTop);
intent.PutExtra("Area", push.Area);
intent.PutExtra("ExtraInfo", push.ExtraInfo);
PendingIntent pendingIntent = PendingIntent.GetActivity(this, notificationId, intent, PendingIntentFlags.UpdateCurrent);
notificationManager = (NotificationManager)GetSystemService(Context.NotificationService);
// Creates Notification Channel for Android devices running Oreo (8.0.0) or later
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O) {
NotificationChannel notificationChannel = new NotificationChannel(NotificationChannelId, NotificationChannelName, NotificationImportance.High) {
Description = NotificationChannelDescription
};
notificationManager.CreateNotificationChannel(notificationChannel);
}
// Builds notification for Notification Manager
Notification notification = new NotificationCompat.Builder(this, NotificationChannelId)
.SetSmallIcon(Resource.Drawable.ic_launcher)
.SetContentTitle(push.Title)
.SetContentText(push.Body)
.SetContentIntent(pendingIntent)
.SetAutoCancel(true)
.SetShowWhen(false)
.Build();
notificationManager.Notify(notificationId, notification);
}
}
}
Then finally my Firebase class where I use OnNewToken()
to register to Azure with, OnMessageReceived()
is overridden so I can handle a push notification being received and then SendNotification()
which is being used to build and show the notification.
Any and all help will be highly appreciated!
Added Sample Project to GitHub
Upvotes: 3
Views: 4497
Reputation: 1080
There were a number of issues for me (more details here):
In the end the solution was to View->Other Windows -> Device Logs and run the app a second time to avoid the Force Stopped state, to discover the ctor lifecycle issue, which I had to work around by moving out code that touched the shared app library from FirebaseMessagingService.
Upvotes: 1