Xamarin: iOS remote notification has no sound - xamarin.forms

I've been banging my head for a few days now trying to figure this out.
I followed this article to setup local notifications for both iOS and Android
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/local-notifications
I also added push notification service for both platforms, on Android everything works fine (both local and remote push notifications).
For iOS, the article doesn't mention that you have to add a line of code to make the notification alert with sound, so I modified the SendNotification method to have the content object like this
var content = new UNMutableNotificationContent()
{
Title = title,
Subtitle = "",
Body = message,
Badge = 1,
Sound = UNNotificationSound.Default
};
Now the notifications have sound on iOS for both local and remote notifications, but when the app is closed (in background) the notification still shows without sound.
What am I missing?

You could add the following code in the AppDelegate.cs file :
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
...
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
// you could register different settings
var notificationSettings = UIUserNotificationSettings.GetSettingsForTypes(
UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound, null
);
app.RegisterUserNotificationSettings(notificationSettings);
}
...
}
The code above ask for the permisson to display notifications and we could register different settings as we like. So the following code in Intialize method in Xamarin LocalNotifications sample code is not needed any more as well as the bool hasNotificationsPermission .
public void Initialize()
{
// the following code could be deleted
UNUserNotificationCenter.Current.RequestAuthorization(UNAuthorizationOptions.Alert, (approved, err) =>
{
hasNotificationsPermission = approved;
});
}
Hope it works for you.

Related

Xamarin Forms: How to launch a UWP app from another UWP app?

I am trying to launch my UWP app from another UWP app. This feature is working fine for android and ios.
I am using this blog for the UWP section.
UWP Render Code
public class OpenAppImplementation : IAppHandler
{
public async Task<bool> LaunchApp(string stringUri)
{
Uri uri = new Uri(stringUri);
return await Windows.System.Launcher.LaunchUriAsync(uri);
}
}
In the Main project
var appname = "UWP package name://";
var result = await DependencyService.Get<IAppHandler>().LaunchApp(appname);
if (!result)
{
Device.OpenUri(new Uri(windows store link));
}
I am passing the package name of the second app as appname here. If the app is installed in the device, I need to open that; else open the windows store app page for downloading the app.
When I execute this I am getting a screen like below:
Update
I have added the protocol declaration on the second app as below:
Then on the first app call like below:
var appname = "alsdk:";
var result = await DependencyService.Get<IAppHandler>().LaunchApp(appname);
if (!result)
{
Device.OpenUri(new Uri("windows store link"));
}
The second app is opening and the splash screen is showing after the above changes. But not going to the home page, the app is staying in the splash screen. Also, I need to show the windows store app page if the app is not installed on the device. What I am missing here?
The problem is you have pass the wrong uri scheme or you have not registered protocol for the launched app. For more please refer this document.
Steps:
Specify the extension point in the package manifest like the following.
<Applications>
<Application Id= ... >
<Extensions>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="alsdk">
<uap:Logo>images\icon.png</uap:Logo>
<uap:DisplayName>SDK Sample URI Scheme</uap:DisplayName>
</uap:Protocol>
</uap:Extension>
</Extensions>
...
</Application>
And you could also use visual studio manifest GUI to add the protocol.
If you have not specific the parameter you could remove // , just keep uri scheme like alsdk:.
Update
The second app is opening and the splash screen is showing after the above changes. But not going to the home page, the app is staying in the splash screen. Also, I need to show the windows store app page if the app is not installed on the device. What I am missing here?
In target app we need process OnActivated method and invoke Window.Current.Activate() finally.
protected override void OnActivated(IActivatedEventArgs args)
{
if (args.Kind == ActivationKind.Protocol)
{
ProtocolActivatedEventArgs eventArgs = args as ProtocolActivatedEventArgs;
// Navigate to a view
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{
rootFrame = new Frame();
Window.Current.Content = rootFrame;
}
// assuming you wanna go to MainPage when activated via protocol
rootFrame.Navigate(typeof(MainPage), eventArgs);
}
Window.Current.Activate();
}
Update
if you launch xamarin uwp app, you need initialize Xamarin Form component in the OnActivated method.
protected override void OnActivated(IActivatedEventArgs args)
{
if (args.Kind == ActivationKind.Protocol)
{
ProtocolActivatedEventArgs eventArgs = args as ProtocolActivatedEventArgs;
// Navigate to a view
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{
rootFrame = new Frame();
Xamarin.Forms.Forms.Init(args);
Window.Current.Content = rootFrame;
}
// assuming you wanna go to MainPage when activated via protocol
rootFrame.Navigate(typeof(MainPage), eventArgs);
}
Window.Current.Activate();
}

Xamarin Forms iOS - Saving a user tag in Azure Notification Hubs works in AppDelegate but not in a service

I'm currently trying to get push notifications working for my mobile app using Azure Notification Hubs. Android is working fine and the initial iOS set up in AppDelegate works ok with a sample tag.
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
if (deviceToken == null)
{
return;
}
SBNotificationHub hub = new SBNotificationHub(CommonConstants.LISTEN_CONNECTION_STRING, CommonConstants.NOTIFICATION_HUB_NAME);
// update registration with Azure Notification Hub
hub.UnregisterAll(deviceToken, async (error) =>
{
if (error != null)
{
System.Diagnostics.Debug.WriteLine($"Unable to call unregister {error}");
return;
}
string[] tags = new[] { "iostestpush" };
NSSet userTags = new NSSet(tags);
hub.RegisterNative(deviceToken, userTags, (error) =>
{
if (error != null)
{
System.Diagnostics.Debug.WriteLine($"Unable to call register {error}");
return;
}
});
var templateExpiration = DateTime.Now.AddDays(120).ToString(System.Globalization.CultureInfo.CreateSpecificCulture("en-US"));
hub.RegisterTemplate(deviceToken, "defaultTemplate", CommonConstants.APN_TEMPLATE_BODY, templateExpiration, userTags, (errorCallback) =>
{
if (errorCallback != null)
{
System.Diagnostics.Debug.WriteLine($"RegisterTemplateAsync error: {errorCallback}");
}
});
});
}
The issue I'm having is I need to register the UserId after a successful login. So I set up a service with the above code, saved the token to the device as string so it can be retrieved in the service and turned back into an NSData token
NSData deviceToken = new NSData(token, NSDataBase64DecodingOptions.None);
After a successful login I send the token string and the tag array to my service.
string[] userTag = new[] { loginResponse.UserId.ToString() };
await this._azureReg.SendRegistrationToServer(deviceToken, userTag);
Which, other than turning the token back into NSData and the user tag into an NSSet, is the same as above other than the name change. But Azure is claiming there is no registration even though my output shows
Registered for push notifications with token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
I thought it was the string conversion back and forth, so tested that in the AppDelegate and it worked fine.
So, I'm at a loss at how to register the UserId after a successful login and why it works in one place but not the other.
I hope that's clear and thanks for any advice in advance.
You probably ran into the same bug as me and several others.
Basically SBNotificationHub method overloads like UnregisterAll and RegisterTemplate with the callback signature do not work when you use them off the main thread, using the libraries to date. I was also using a Service for the same purpose (to handle push across platforms with different tags, especially for user id) but my implementation involved switching off the main thread for this.
The bug we logged and is now being addressed is here: https://github.com/Azure/azure-notificationhubs-ios/issues/95
The solution, for now, is to ditch SBNotificationHub completely. The Xamarin / Azure documentation is out of date, and SBNOtificationHub is legacy code. The recommended library is MSNotificationHub. https://github.com/azure/azure-notificationhubs-xamarin
As workarounds you can use the SBNotificationHub method overloads that do not involve callbacks (they return an error message instead) or the workaround in the 95 issue above.

Xamarin Forms: How to run a service every x seconds even the app is in background?

I am using the following code for calling a function every 10 seconds.
var startTimeSpan = TimeSpan.Zero;
var periodTimeSpan = TimeSpan.FromSeconds(10);
var timer = new System.Threading.Timer((e) =>
{
MyFunction();
}, null, startTimeSpan, periodTimeSpan);
MyFunction() will execute every 10 seconds when the app is on the open state. When the app is in the background, that function is not invoking.
So how can I invoke a function when the app is in the background? Are there any NuGet packages for this or we need to do this using the dependency service?
UPDATE
When I running your demo I am getting the below exception:
I have integrated the codes on my sample. The code execution coming on Start() in the StartServiceDroid. But not hitting the OnStartCommand() function. Here is my sample, could you please have a look? I need to run the MyFunction() every x seconds in the background or foreground mode.
Update 10-07-2020
#Leon Lu - MSFT I found a new solution here. :)
var second = TimeSpan.FromSeconds(10);
Device.StartTimer(second, () => {
Debug.WriteLine("Hiiiii");
return true;
});
I create a sample application with this code and it is working fine on foreground and background mode. Every 10 seconds, the Hiii message is printing on the output box even the app is running on the background.
This is included with Xamarin Forms, so no need for any platform-specific logic.
Is there any problem with this approach?
Update 15/07/2020
Today I have tested it on a real device. :)
Case 1: Run the app on Visual Studio, Service is invoking in foreground mode. Move the app to the background (Still, the app is running in VS), the background service is invoking when the app is on the background every 10 seconds.
Case 2: Open the app installed app normally, (not running it in VS), service is invoking in foreground mode, Move the app to the background, the background service is not invoking even after 10minutes.
I am totally confused now, background service is invoking when the app is running on VS and not invoking when open the installed app normally. For the second case, I have waited for more than 10 minutes, but the service that I added is not invoking. Both cases are done on debug mode.
Is it the only behavior of xamarin forms platform? If we do it in the native ios platform, is it possible to trigger the background service on every x seconds? How skype is doing background service?
Tested device model: iPhone 7
Software version: 13.5.1
In Android, you can use foreground service to keep your MyFunction() always running in background. If you used it in xamarin forms. you can use dependence service to invoke the foreground service, then execute your Timer with MyFunction in foreground service.
Here is simple code about use a DependentService to open a foreground service.
[Service]
public class DependentService : Service, IService
{
public void Start()
{
var intent = new Intent(Android.App.Application.Context,
typeof(DependentService));
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
{
Android.App.Application.Context.StartForegroundService(intent);
}
else
{
Android.App.Application.Context.StartService(intent);
}
}
public override IBinder OnBind(Intent intent)
{
return null;
}
public const int SERVICE_RUNNING_NOTIFICATION_ID = 10000;
public override StartCommandResult OnStartCommand(Intent intent,StartCommandFlags flags, int startId)
{
// From shared code or in your PCL
CreateNotificationChannel();
string messageBody = "service starting";
var notification = new Notification.Builder(this, "10111")
.SetContentTitle("Foreground")
.SetContentText(messageBody)
.SetSmallIcon(Resource.Drawable.main)
.SetOngoing(true)
.Build();
StartForeground(SERVICE_RUNNING_NOTIFICATION_ID, notification);
//=======you can do you always running work here.=====
var startTimeSpan = TimeSpan.Zero;
var periodTimeSpan = TimeSpan.FromSeconds(10);
var timer = new System.Threading.Timer((e) =>
{
MyFunction();
}, null, startTimeSpan, periodTimeSpan);
return StartCommandResult.Sticky;
}
void CreateNotificationChannel()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
// Notification channels are new in API 26 (and not a part of the
// support library). There is no need to create a notification
// channel on older versions of Android.
return;
}
var channelName = Resources.GetString(Resource.String.channel_name);
var channelDescription = GetString(Resource.String.channel_description);
var channel = new NotificationChannel("10111", channelName, NotificationImportance.Default)
{
Description = channelDescription
};
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
notificationManager.CreateNotificationChannel(channel);
}
}
Here is my demo about how to use foreground service in xamarin form.
https://github.com/851265601/ForeGroundService
In iOS, it cannot be achieved that always running your application in the background, because iOS, just allow normal applications(:Audio, VoIP,External Accessories and Bluetooth Newsstand, Location, Fetch (iOS 7+), Remote Notifications (iOS 7+) application could be allowed allways running in background, you can see this thread) running in background with in 10 minutes. You can refer to this article.
https://learn.microsoft.com/en-us/xamarin/ios/app-fundamentals/backgrounding/ios-backgrounding-techniques/ios-backgrounding-with-tasks

Can't get FCM Remote Notifications to display when Xamarin.Forms Android app is closed, stopped or not running

I used these instructions to add Azure Notification Hub FCM-based Remote Notifications to my Xamarin.Forms Android app.
https://learn.microsoft.com/en-us/azure/app-service-mobile/app-service-mobile-xamarin-forms-get-started-push
I can receive Remote Notifications when the app is open or running in the background, but when I close the application or stop the application I no longer receive Remote Notifications.
My test device is running API level 22, so I'm using the following method to to build the notification on the device.
Android.Support.V4.App.NotificationCompat.Builder builder = new
Android.Support.V4.App.NotificationCompat.Builder(this)
For API level 26+, I'm using the follow method along with a channel to build the notification on the device.
var builder = new Android.App.Notification.Builder(this)
I'm thinking that I have to use a BroadcastReceiver to achieve this, but I really have no idea after reading so many comments on this subject. My app is compiled using API 27 and targets API 27.
Method 1
I'm trying to create a BroadcastReceiver that will launch MyService using an Explicit Intent when a Notification arrives. Unfortunately, this does not work on my API Level 22 test device.
//[BroadcastReceiver]
//[IntentFilter(new[] { Android.Content.Intent.ActionBootCompleted })]
[BroadcastReceiver(Enabled = true, Exported = true)]
[IntentFilter(new[] { "com.xamarin.example.TEST" })]
public class MyBroadcastReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
string message = intent.GetStringExtra("message");
string title = intent.GetStringExtra("title");
string id = intent.GetStringExtra("id");
//Explicit Intent to launch MyService
Intent intent2 = new Intent(Application.Context, typeof(MyService));
intent2.PutExtra("message", message);
intent2.PutExtra("title", title);
intent2.PutExtra("id", id);
Application.Context.StartService(intent2);
}
}
// Service is exported and given a name so other applications can use it
[Service(Exported = true, Name = "com.mycompany.myapp.MyService")]
// Intent filter only needed for Implicit Intents
//[IntentFilter(new string[] { "com.xamarin.example.TEST" })]
public class MyService : Service
{
public static string PRIMARY_NOTIF_CHANNEL = "default";
public override IBinder OnBind(Intent intent)
{
return null;
}
[return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
var pm = PowerManager.FromContext(this);
var wakeLock = pm.NewWakeLock(WakeLockFlags.Partial, "Notification");
wakeLock.Acquire();
string message = intent.GetStringExtra("message");
string title = intent.GetStringExtra("title");
string id = intent.GetStringExtra("id");
var intent2 = new Intent(this, typeof(MainActivity));
intent2.PutExtra("id", id);
intent2.AddFlags(ActivityFlags.ClearTop);
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.OneShot);
NotificationManager notificationManager = (NotificationManager)GetSystemService(Context.NotificationService);
Android.Support.V4.App.NotificationCompat.Builder builder = new Android.Support.V4.App.NotificationCompat.Builder(this)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent)
.SetContentTitle(title)
.SetContentText(message)
.SetSound(RingtoneManager.GetDefaultUri(RingtoneType.Notification))
.SetVibrate(new long[1])
//1 is Hi
.SetPriority(1)
.SetLargeIcon(BitmapFactory.DecodeResource(Resources, SalesFlash.Droid.Resource.Drawable.Icon_lg))
.SetSmallIcon(MyApp.Droid.Resource.Drawable.icon)
.SetChannelId(PRIMARY_NOTIF_CHANNEL);
notificationManager = NotificationManager.FromContext(this);
notificationManager.Notify(0, builder.Build());
wakeLock.Release();
//return StartCommandResult.Sticky;
return base.OnStartCommand(intent, flags, startId);
}
public override void OnDestroy()
{
base.OnDestroy();
}
}
According to this post, a BroadCastReceiver won't work for FCM Notifications. https://stackoverflow.com/a/44287962/5360237
This post seems to show a BroadcastReceiver accepting a Notification.
https://stackoverflow.com/a/45616014/5360237
Any help is much appreciated. Thanks in advance!
Here is the best post that I've read that explains the problem. It also provides a manual solution for the affected devices. https://medium.freecodecamp.org/why-your-push-notifications-never-see-the-light-of-day-3fa297520793 I was able to get Notifications working (when app is closed, stopped or not running) on my Alcatel One Touch Pop by navigating to Settings - Apps and then swiping over to the Restricted tab. From here, I was able to uncheck my app.
NOTE: Messages are delivered to my Android app using the data payload. Even though I was receiving Notifications in the foreground and background, I went ahead and added the following permission to my AndroidManifest.
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
This permission is used for Auto Start. I also used Azure App Center to distribute a signed APK to my test device. Hope this helps someone else.
UPDATE: I removed the RECEIVE_BOOT_COMPLETE permission and did a Debug Build. I closed the app out of Debug (connected to Visual Studio) by swiping it off the screen. I then sent a Notification and it did not display. I then re-opened the app (not being debugged by Visual Studio) and closed it by swiping it off the screen, sent a Notification and it worked. So, it had nothing to with the RECEIVE_BOOT_COMPLETE permission and it doesn't have to be a Release version / signed APK.

scheduleLocalNotification in iOS 10 device using iOS 9.3 SDK when in foreground

I am having an issue with scheduling a local notification in an iOS 10 device when using iOS 9.3 SDK, when app is in foreground. Our application is designed in such a way that if we receive a remote notification and application is currently in foreground, we repost it to local so the user can still see the notification when they put the app in background. This works in iOS 9 devices, but not in iOS 10.
Repost code:
dispatch_async(dispatch_get_main_queue(), {
let localNotification = UILocalNotification()
localNotification.userInfo = userInfo;
localNotification.fireDate = NSDate()
localNotification.timeZone = NSTimeZone.localTimeZone()
localNotification.soundName = UILocalNotificationDefaultSoundName
if let apnsPayload = userInfo["aps"] {
if let alert = apnsPayload["alert"] as? NSDictionary {
if let message = alert["body"] as? String {
localNotification.alertBody = message
}
}
if let category = apnsPayload["category"] {
localNotification.category = category as? String
}
}
UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
})
I believe the reposting with scheduleLocalNotification is working somewhat correctly because I can see that my didReceiveLocalNotification delegate is getting called in AppDelegate. However, if I pull the notification drop down when putting app in foreground or background there is no notification present.
Has anyone else run into this problem? I have seen a lost of posts with how to use iOS 10 UNUserNotificationCenter, but I do not have access to this in iOS 9.3 SDK.
This seems to be just the new expected handling in iOS 10. If you post a notification while app is in foreground, it will call the didReceiveLocalNotification delegate, however it will not post it to the notification center tray.
To get around this, I am now storing all received notifications in a static array, and then when app goes to background I will schedule the notifications to be sent in 5 seconds, just to give the app time enough to completely go to background.
Creating/Appending the notifications:
/**
Takes a given Dictionary that should correspond to a Remote notification and reposts it (at current time)
as a Local Notification. If the app is in the foreground, then no banner, alert, or sound will play, but the
notification should appear in the Notification Center.
- parameter userInfo: Dictionary that should correspond to a Remote notification.
*/
static func repostLocalNotificationUsingRemote(userInfo: Dictionary<NSObject, AnyObject>) {
let localNotification = UILocalNotification()
localNotification.userInfo = userInfo;
localNotification.fireDate = NSDate(timeIntervalSinceNow: 5)
localNotification.timeZone = NSTimeZone.localTimeZone()
localNotification.soundName = UILocalNotificationDefaultSoundName
if let apnsPayload = userInfo["aps"] {
if let alert = apnsPayload["alert"] as? NSDictionary {
if let message = alert["body"] as? String {
localNotification.alertBody = message
}
}
if let category = apnsPayload["category"] {
localNotification.category = category as? String
}
}
// Add notification to queue
localNotificationQueue.append(localNotification)
}
Eventually Sending, called in applicationDidEnterBackground and applicationWillTerminate:
/**
Posts all pending notifications that were received while app was in foreground.
*/
static func postPendingLocalNotificationIfAny() {
for localNotification in localNotificationQueue {
UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
}
localNotificationQueue.removeAll();
}
Ideally we will be updating to iOS 10 SDK and use UNUserNotificationCenter to display notifications in the app, but until then this change matches are current iOS 9 behavior.

Resources