I have a Xamarin Forms project where I use the following notifications code for Android:
[Service(Exported = false)]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
public class MyFirebaseMessagingService : FirebaseMessagingService
{
public override void OnMessageReceived(RemoteMessage message)
{
base.OnMessageReceived(message);
string messageTitle = GetTitle(message);
string messageBody = GetBody(message);
// convert the incoming message to a local notification
ConfigureAndSendLocalNotification(messageTitle, messageBody);
}
private void ConfigureAndSendLocalNotification(string title, string body)
{
NotificationCompat.Builder notificationBuilder = ConfigureNotificationBuilder(title, body);
SendNotification(notificationBuilder);
}
private NotificationCompat.Builder ConfigureNotificationBuilder(string title, string body)
{
Intent intent = new Intent(this, typeof(MainActivity));
intent.AddFlags(ActivityFlags.ClearTop);
intent.PutExtra("title", title);
intent.PutExtra("message", body);
PendingIntent pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.Immutable);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, Util.Constants.NotificationChannelName)
...//configuring notification
return notificationBuilder;
}
I'm trying to modify this code to give users a better experience. However, when i generate the APK (going to archive -> distribute -> save as), it seems not to recognize any change in this file.
I've actually commented this part of the code:
public override void OnMessageReceived(RemoteMessage message){
//base.OnMessageReceived(message);
string messageTitle = GetTitle(message);
string messageBody = GetBody(message);
// convert the incoming message to a local notification
//ConfigureAndSendLocalNotification(messageTitle, messageBody);
}
And the notification keeps appearing on the generated APK when I run it on my Android.
Creepy detail: In the Android emulator, all of my changes are correctly displayed. They really are just not reflected in the final APK.
Any ideas?
Related
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);
}
I want to display local notification when the app is closed and also app is not in the recent history of the device.How can I do this.
Please help me..
Here is my code MainPage.cs
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
var startTimeSpan = TimeSpan.Zero;
var periodTimeSpan = TimeSpan.FromMinutes(5);
Device.StartTimer(TimeSpan.FromSeconds(5), () =>
{
GetData();
return true;
});
}
public void GetData()
{
IAlarmReceiver _local = DependencyService.Get<IAlarmReceiver>();
_local.LocalNotifications("Xamarin", "Hello world");
}
private void btn_Click_Clicked(object sender, EventArgs e)
{
GetData();
}
}
Here I call the notification method in every 5 seconds.
Here is my AlarmReciever.CS
public void GetNotification(string Title, string Text)
{
Intent _intent = new Intent(Application.Context, typeof(MainActivity));
const int _pendingIntentId = 0;
PendingIntent _pintent = PendingIntent.GetActivity(Application.Context, _pendingIntentId, _intent, PendingIntentFlags.CancelCurrent);
Notification.Builder builder = new Notification.Builder(Application.Context)
.SetContentIntent(_pintent)
.SetContentTitle(Title)
.SetContentText(Text)
.SetDefaults(NotificationDefaults.Sound)
.SetAutoCancel(true)
.SetSmallIcon(Resource.Drawable.icon);
Notification _notification = builder.Build();
NotificationManager _notificationmanager = Android.App.Application.Context.GetSystemService(Context.NotificationService)
as NotificationManager;
_notificationmanager.Notify(0, _notification);
you can use ACR Notifications Plugin for Xamarin this will display the local notification when app is closed.
Example await CrossNotifications.Current.Send("Happy Birthday", "I sent this a long time ago", when = TimeSpan.FromDays(50));
If you tested your program using the debugger the notification will be terminated with the rest of your application as soon as you close it.
I tested your code (assuming _local.LocalNotifications should be _local.GetNotification) and it worked, when the app is started on the device without the debugger.
EDIT
To display the notification all x minutes, use a ScheduledJob
Add a StartJob Method to your IAlarmReceiver inteface and its implementation
public void StartJob()
{
var builder = new JobInfo.Builder(1234, new ComponentName(Android.App.Application.Context, Java.Lang.Class.FromType(typeof(ScheduledJob))));
builder.SetPeriodic(15L * 60L * 1000L); // <- change the intervall
var jobInfo = builder.Build();
var jobScheduler = (JobScheduler)Android.App.Application.Context.GetSystemService(Context.JobSchedulerService);
jobScheduler.Schedule(jobInfo);
}
This method starts the ScheduledJob (defined in the android project)
[Service(Exported = true, Permission = "android.permission.BIND_JOB_SERVICE")]
public class ScheduledJob : JobService
{
public override bool OnStartJob(JobParameters #params)
{
new AlarmReceiver().GetNotification("Hello World!", "Xamarin");
return false;
}
public override bool OnStopJob(JobParameters #params)
{
return false;
}
}
Replace
Device.StartTimer(TimeSpan.FromSeconds(5), () =>
{
GetData();
return true;
});
with
DependencyService.Get<IAlarmReceiver>().StartJob();
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.
I'm trying to whitelist my app on Android 6.0 or greater. I have seen Android code to do this, but it doesn't translate in Xamarin and Xamarin documentation only tells you that SetAction takes a string as an argument, and then a link to Android documentation which doesn't end up being the same.
Here's the Android code that Xamarin will not accept
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
It doesn't like Settings. I've read the Xamarin documentation which says that SetAction() takes a string arg, and that's all they say and point you to Android documentation.
Note, I am calling this in a javascript interface class and I tried this but it doesn't work
class MyJSInterface : Java.Lang.Object
{
Context context;
public MyJSInterface(Context context)
{
this.context = context;
}
[Export]
[JavascriptInterface]
public void SetDozeOptimization()
{
Toast.MakeText(context, "launch optimization", ToastLength.Short).Show();
setDozeComplete = false;
Intent intent = new Intent();
String packageName = context.PackageName;
PowerManager pm = (PowerManager)context.GetSystemService(Context.PowerService);
if (pm.IsIgnoringBatteryOptimizations(packageName))
intent.SetAction("ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS");
else
{
intent.SetAction("ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS");
intent.SetData(Android.Net.Uri.Parse("package:" + packageName));
}
context.StartActivity(intent);
}
}
So what is the correct syntax to accomplish this?
Android.Provider.Settings.ActionRequestIgnoreBatteryOptimizations:
android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
Android.Provider.Settings.ActionIgnoreBatteryOptimizationSettings:
android.settings.IGNORE_BATTERY_OPTIMIZATION_SETTINGS
Example:
intent.SetAction(Android.Provider.Settings.ActionRequestIgnoreBatteryOptimizations);
intent.SetAction(Android.Provider.Settings.ActionIgnoreBatteryOptimizationSettings);
Here is the code you are looking for:
Intent intent = new Intent();
String packageName = context.PackageName;
PowerManager pm(PowerManager)Android.App.Application.Context.GetSystemService(Context.PowerService);
if (!pm.IsIgnoringBatteryOptimizations(packageName))
{
intent.SetAction(Android.Provider.Settings.ActionRequestIgnoreBatteryOptimizations);
intent.SetData(Android.Net.Uri.Parse("package:" + packageName));
StartActivity(intent);
}
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