Task running once a day on Xamarin Forms - xamarin.forms

I started using xamarin a few months ago, and, until now, I didn't have the need of doing something like this.
I'm developing an app that, once a day, should run a WCF web service and verify if an information is true. If it is true, it should show a notification on the device.
My problem is that I don't know how to perform it, i've read about backgrounding and schedule tasks, but I didn't understand well how can I perform this. How can I do it using Xamarin.Forms?
Thank you!

For Android, a solution needs 4 components:
AlarmManager to set daily check schedule
BroadCastReceiver to receive the daily trigger and call the IntentService
IntentService to execute awaitable calls
OnBootReceiver to ensure alarms are set again after a device reboot
For iOS, you will most likely need remote push notifications.
Some sample code below for the Android components:
AlarmManager - Setting the Alarm
[assembly: Dependency(typeof(AlarmHelper))] // above the namespace
...
class AlarmHelper: IAlarm
{
var now = Calendar.Instance;
var alarmTime = Calendar.Instance;
alarmTime.Set(CalendarField.HourOfDay, settings.AlarmHour); // Set Alarm start Hour
alarmTime.Set(CalendarField.Minute, settings.AlarmMinutes); // Set Alarm Start Minutes
if (alarmTime.Before(now))
{
alarmTime.Add(CalendarField.Hour, 24);
}
var intent = new Intent(Android.App.Application.Context, typeof(ScheduledAlarmHandler));
var pendingIntent = PendingIntent.GetBroadcast(Android.App.Application.Context, 0, intent, PendingIntentFlags.CancelCurrent);
var alarmManager = Android.App.Application.Context.GetSystemService(Context.AlarmService) as AlarmManager;
alarmManager.SetRepeating(AlarmType.RtcWakeup, alarmTime.TimeInMillis, AlarmManager.IntervalDay, pendingIntent);
}
BroadCastReceiver - Receiving the Alarm
[BroadcastReceiver]
class ScheduledAlarmHandler : WakefulBroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
Console.WriteLine("ScheduledAlarmHandler", "Starting service #" + SystemClock.ElapsedRealtime());
Intent service = new Intent(context, typeof(WakefulAPIService));
StartWakefulService(context, service);
}
}
IntentService - Executing awaitable calls
[Service]
[IntentFilter(new String[] { "com.test.testApp.WakefulAPIService" })]
class WakefulAPIService : IntentService
{
protected override void OnHandleIntent(Intent intent)
{
// Your API Call code here
Console.WriteLine("WakefulAPIService", "Completed service # " + SystemClock.ElapsedRealtime());
Android.Support.V4.Content.WakefulBroadcastReceiver.CompleteWakefulIntent(intent);
}
}
OnBootReceiver - Ensuring alarms are set again after a device reboot
[BroadcastReceiver(Enabled = true)]
[IntentFilter(new[] { "android.intent.action.BOOT_COMPLETED", "android.intent.action.QUICKBOOT_POWERON" })]
class OnBootReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
Console.WriteLine("On Boot Reveiver", "Alarm Set Again after Reboot");
var alarmHelper = new AlarmHelper();
alarmHelper.SetAlarm();
}
}
The required permissions for this to work are 'RECEIVE_BOOT_COMPLETED' and 'WAKE_LOCK'

In this case for Android you can use you can use JobScheduler, see this class
[Service(Name = "com.xamarin.samples.downloadscheduler.DownloadJob",
Permission = "android.permission.BIND_JOB_SERVICE")]
public class DownloadJob : JobService
{
public override bool OnStartJob(JobParameters jobParams)
{
Task.Run(() =>
{
//Your periodic task here
});
return true;
}
public override bool OnStopJob(JobParameters jobParams)
{
//true so we re-schedule the task
return true;
}
}
Then you can create a Factory to call this service.
public static class ReadLocationSchedulerFactory
{
public static JobInfo.Builder CreateJobBuilderUsingJobId<T>(this Context context, int jobId) where T : JobService
{
var javaClass = Java.Lang.Class.FromType(typeof(T));
var componentName = new ComponentName(context, javaClass);
return new JobInfo.Builder(jobId, componentName);
}
}
Then in your Main Activity you have to call the Factory.
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App172S.App());
#region Scheduler
var jobBuilder = this.CreateJobBuilderUsingJobId<ReadLocationScheduler>(152);
//This means each 20 mins
jobBuilder.SetPeriodic(20 * 60 * 1000);
//Persists over phone restarts
jobBuilder.SetPersisted(true);
//If Fails re-try each 2 mins
jobBuilder.SetBackoffCriteria(120 * 1000, BackoffPolicy.Linear);
var jobInfo = jobBuilder.Build();
var jobScheduler = (JobScheduler)GetSystemService(JobSchedulerService);
jobScheduler.Cancel(152);
var scheduleResult = jobScheduler.Schedule(jobInfo);
if (JobScheduler.ResultSuccess == scheduleResult)
{
//If OK maybe show a msg
}
else
{
//If Failed do something
}
#endregion
}

Related

BroadcastReceiver does not work on Android 12

In my Xamarin Forms project I have:
[BroadcastReceiver(Permission = "RECEIVE_BOOT_COMPLETED",
Exported = true,
Enabled = true)]
[IntentFilter(new[] {Intent.ActionBootCompleted})]
public class GeofenceReceiver: BroadcastReceiver
I use it for GeofenceTransitionEnter and GeofenceTransitionExit events.
I also have ACCESS_FINE_LOCATION and ACCESS_BACKGROUND_LOCATION permissions.
But OnReceive method is not called on API 31. I have not this problem with lower APIs.
Android 12 targetSDKVersion 31 challenges (Broadcast Receiver, Pending Intent) Crash Issues - doesn't work for me
I have done a sample to test. And it worked well, the OnReceive method will call a few minutes after the device restarted.
The receiver:
[BroadcastReceiver(Permission = "RECEIVE_BOOT_COMPLETED",
Exported = true,
Enabled = true)]
[IntentFilter(new[] { Intent.ActionBootCompleted })]
public class GeofenceReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
//launch our activity
if (intent.Action == "android.intent.action.BOOT_COMPLETED")
{
Toast.MakeText(context,"Device Restart",ToastLength.Long).Show();
Intent intent1 = new Intent(context,typeof(ForegroundServiceDemo));
intent1.SetFlags(ActivityFlags.NewTask);
context.StartForegroundService(intent1);
}
}
}
The foreground service:
[Service]
public class ForegroundServiceDemo : Service
{
private string NOTIFICATION_CHANNEL_ID = "1000";
private int NOTIFICATION_ID = 1;
private string NOTIFICATION_CHANNEL_NAME = "notification";
private void startForegroundService()
{
var notifcationManager = GetSystemService(Context.NotificationService) as NotificationManager;
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
createNotificationChannel(notifcationManager);
}
var notification = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
notification.SetAutoCancel(false);
notification.SetOngoing(true);
notification.SetSmallIcon(Resource.Mipmap.icon);
notification.SetContentTitle("ForegroundService");
notification.SetContentText("Foreground Service is running");
StartForeground(NOTIFICATION_ID, notification.Build());
}
private void createNotificationChannel(NotificationManager notificationMnaManager)
{
var channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME,
NotificationImportance.Low);
notificationMnaManager.CreateNotificationChannel(channel);
}
public override IBinder OnBind(Intent intent)
{
return null;
}
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
startForegroundService();
return StartCommandResult.NotSticky;
}
}
The AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
      <uses-permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" />
      <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
      <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
I added the RECEIVE_BOOT_COMPLETED into the AndroidManifes.xml, because if I just added it in the [BroadcastReceiver], the receiver will not work. So you can also try to add it in the AndroidManifes.xml.
In addition, you can create a new project to test my code. It worked on Android 12.0 emulator when I tested it. Finally, if it still not work, could you please show the code in the OnReceive method? Maybe the OnReceive method was called, but the code in it can't run background such as start an activity.
I just added something like this into GepfencePendingIntent:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
} else {
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
I found it here: https://www.flybuy.com/android-12-pendingintent-mutability-and-geofences

Xamarin Forms - Get device location every 10 seconds (when app runs in foreground/background)

I have created a Xamarin forms application. The application should periodically (every 10 sec) get the location of the device (iOS and Android). How can I achieve this? I know there are some libraries for example: Xamarin.Essentials, but I can't decide how many times the location should be taken.
It should also be possible to get the local of the device when the Xamarin forms application runs in the background (on IOS and Android).
I passed for that headache in the past, a cross-platform app that update location every x seconds and it should run in the background.
I had developed a template in Xamarin Forms that support Background Location Updates, Background permissions, these capabilities need to be adjusted depending on the OS (iOs/Android).
Check my Github repo here
I used Xamarin Essentials and Messaging Center for this purpose.
Please let me know if the template works for your needs.
Thanks.
You can do it with device timer. The timer will run periodically & will check for location updates and notify if location updated. I have used Xam.Plugins.Notifier to generate local notification on location update.
Here is the code for it :
public partial class MainPage : ContentPage
{
Location oldLocation = null;
CancellationTokenSource cts;
public MainPage()
{
InitializeComponent();
Device.StartTimer(TimeSpan.FromSeconds(10), () =>
{
GetCurrentLocation();
return true;
});
}
protected async override void OnAppearing()
{
base.OnAppearing();
await GetCurrentLocation();
}
async Task GetCurrentLocation()
{
try
{
var request = new GeolocationRequest(GeolocationAccuracy.Medium, TimeSpan.FromSeconds(10));
cts = new CancellationTokenSource();
var location = await Geolocation.GetLocationAsync(request, cts.Token);
if (location != null)
{
Debug.WriteLine($"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude: {location.Altitude}");
if (oldLocation == null)
{
oldLocation = location;
map.MoveToRegion(MapSpan.FromCenterAndRadius(
new Position(location.Latitude, location.Longitude), Distance.FromMiles(0.3)));
}
if (location.Latitude != oldLocation.Latitude || location.Longitude != oldLocation.Longitude)
{
map.MoveToRegion(MapSpan.FromCenterAndRadius(
new Position(location.Latitude, location.Longitude), Distance.FromMiles(0.3)));
oldLocation = location;
double zoomLevel = 0.5;
double latlongDegrees = 360 / (Math.Pow(2, zoomLevel));
if (map.VisibleRegion != null)
{
map.MoveToRegion(new MapSpan(map.VisibleRegion.Center, latlongDegrees, latlongDegrees));
}
var placemarks = await Geocoding.GetPlacemarksAsync(location.Latitude, location.Longitude);
var placemark = placemarks?.FirstOrDefault();
if (placemark != null)
{
var geocodeAddress =
$"AdminArea: {placemark.AdminArea}\n" +
$"CountryCode: {placemark.CountryCode}\n" +
$"CountryName: {placemark.CountryName}\n" +
$"FeatureName: {placemark.FeatureName}\n" +
$"Locality: {placemark.Locality}\n" +
$"PostalCode: {placemark.PostalCode}\n" +
$"SubAdminArea: {placemark.SubAdminArea}\n" +
$"SubLocality: {placemark.SubLocality}\n" +
$"SubThoroughfare: {placemark.SubThoroughfare}\n" +
$"Location : {placemark.Location}\n" +
$"Thoroughfare: {placemark.Thoroughfare}\n";
Debug.WriteLine(geocodeAddress);
}
CrossLocalNotifications.Current.Show("Location Updated", "You checked in to " + placemark.FeatureName + " " + placemark.Locality + " " + placemark.SubLocality, 101, DateTime.Now.AddSeconds(5));
}
}
}
catch (FeatureNotSupportedException)
{
// Handle not supported on device exception
}
catch (FeatureNotEnabledException)
{
// Handle not enabled on device exception
}
catch (PermissionException)
{
// Handle permission exception
}
catch (Exception)
{
// Unable to get location
}
}
}
For Android you can try to start a Service that uses the LocationManager of Android to start listening to Location changes. You can specify a timeinterval and a minimum distance you want to track.
This section helped me fiqure out how to use it. For me it was sending location updates even when the app was suspended (physical device running Android 6.1).
To get the location I made my Service a 'LocationListener' and implemented the ILocationListener-Interface like so:
[Service]
public class TestService : Service, ILocationListener
{
public override IBinder OnBind(Intent intent)
{
return null;
}
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
// start your location updates with the locationManager here
return StartCommandResult.Sticky; // remember to return sticky for the service to run when app is suspended
}
public override void OnDestroy() { }
...
public void OnLocationChanged(Location location)
{
// react to location changes here
}
public void OnProviderDisabled(string provider) { }
public void OnProviderEnabled(string provider) { }
public void OnStatusChanged(string provider, Availability status, Bundle extras) { }
}
For more information on Backgrounding and how to set up a service read this.
Important to note is that the locationUpdates where not consistantly timed (sometimes took more that 10 seconds), since you just give a minimumTime and the OS processes the Request based on its' capacities. But it wasn't too bad.
Update: this doesnt seem to work for Android 8.0 and above. see here

need to send local notification even after killing my app using xamarin.forms

I am using xamarin forms for my app. I need to send local notification based on SQLite table for ios. I am using the following code:
AppDelegate.cs:
using System;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Firebase.Database;
using Firebase.CloudMessaging;
using UserNotifications;
using Microsoft.AppCenter.Crashes;
using Plugin.Permissions;
using Plugin.Permissions.Abstractions;
using CoreTelephony;
using Firebase.Auth;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace MyApp.iOS
{
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate, IUNUserNotificationCenterDelegate, IMessagingDelegate
{
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
Firebase.Core.App.Configure();
Database.DefaultInstance.PersistenceEnabled = true;
LoadApplication(new MyApp.App());
RegisterForRemoteNotifications();
Messaging.SharedInstance.Delegate = this;
if (UNUserNotificationCenter.Current != null)
{
UNUserNotificationCenter.Current.Delegate = new UserNotificationCenterDelegate();
}
if (UNUserNotificationCenter.Current != null)
{
UNUserNotificationCenter.Current.Delegate = new UserNotificationCenterDelegate();
}
Xamarin.FormsMaps.Init();
UIApplication.SharedApplication.IdleTimerDisabled = true;
UIApplication.SharedApplication.SetMinimumBackgroundFetchInterval(UIApplication.BackgroundFetchIntervalMinimum);
return base.FinishedLaunching(app, options);
}
public override bool ShouldRestoreApplicationState(UIApplication application, NSCoder coder)
{
return true;
}
private async System.Threading.Tasks.Task AcessPermissionsAsync()
{
try
{
await CrossPermissions.Current.RequestPermissionsAsync(Permission.Camera);
}
catch(Exception ex)
{
Crashes.TrackError(ex);
}
}
private void RegisterForRemoteNotifications()
{
if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
{
UNUserNotificationCenter.Current.RemoveAllPendingNotificationRequests(); // To remove all pending notifications which are not delivered yet but scheduled.
UNUserNotificationCenter.Current.RemoveAllDeliveredNotifications(); // To remove all delivered notifications
}
else
{
UIApplication.SharedApplication.CancelAllLocalNotifications();
}
// Register your app for remote notifications.
if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
{
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.Current.Delegate = this;
var authOptions = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound;
UNUserNotificationCenter.Current.RequestAuthorization(authOptions, async (granted, error) =>
{
Console.WriteLine(granted);
await System.Threading.Tasks.Task.Delay(500);
await AcessPermissionsAsync();
});
}
else
{
// iOS 9 or before
var allNotificationTypes = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound;
var settings = UIUserNotificationSettings.GetSettingsForTypes(allNotificationTypes, null);
UIApplication.SharedApplication.RegisterUserNotificationSettings(settings);
}
UIApplication.SharedApplication.RegisterForRemoteNotifications();
}
public override void DidEnterBackground(UIApplication uiApplication)
{
Messaging.SharedInstance.ShouldEstablishDirectChannel = false;
}
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
Messaging.SharedInstance.ApnsToken = deviceToken;
}
[Export("messaging:didReceiveRegistrationToken:")]
public void DidReceiveRegistrationToken(Messaging messaging, string fcmToken)
{
Xamarin.Forms.Application.Current.Properties["Fcmtocken"] = Messaging.SharedInstance.FcmToken ?? "";
Xamarin.Forms.Application.Current.SavePropertiesAsync();
System.Diagnostics.Debug.WriteLine($"######Token###### : {fcmToken}");
DeviceId.FCMTocken = fcmToken;
// TODO: If necessary send token to application server.
// Note: This callback is fired at each app startup and whenever a new token is generated.
}
public override void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
{
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// With swizzling disabled you must let Messaging know about the message, for Analytics
// Print full message.
Console.WriteLine(userInfo);
}
public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, Action<UIBackgroundFetchResult> completionHandler)
{
try
{
Console.WriteLine(userInfo);
completionHandler(UIBackgroundFetchResult.NewData);
}
catch(Exception ex)
{
Crashes.TrackError(ex);
}
}
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations(UIApplication application, UIWindow forWindow)
{
switch (Device.Idiom)
{
case TargetIdiom.Phone:
return UIInterfaceOrientationMask.Portrait;
case TargetIdiom.Tablet:
return UIInterfaceOrientationMask.Landscape;
default:
return UIInterfaceOrientationMask.Portrait;
}
}
private void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs unhandledExceptionEventArgs)
{
var newExc = new Exception("CurrentDomainOnUnhandledException", unhandledExceptionEventArgs.ExceptionObject as Exception);
ExceptionFileWriter.ToLogUnhandledException(newExc);
}
public void DidRefreshRegistrationToken(Messaging messaging, string fcmToken)
{
Xamarin.Forms.Application.Current.Properties["Fcmtocken"] = Messaging.SharedInstance.FcmToken ?? "";
Xamarin.Forms.Application.Current.SavePropertiesAsync();
System.Diagnostics.Debug.WriteLine($"######Token###### : {fcmToken}");
DeviceId.FCMTocken = fcmToken;
}
public override void PerformFetch(UIApplication application, Action<UIBackgroundFetchResult> completionHandler)
{
try
{
var baseService = new DatabaseServices<object, object>();
baseService.NotificationAssetTrip();
}
catch(Exception ex)
{
Logs.LogCreate("PerformFetch:Exception: " + ex.Message.ToString());
}
completionHandler(UIBackgroundFetchResult.NewData);
}
}
}
UserNotificationCenterDelegate.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Foundation;
using iAd;
using ObjCRuntime;
using UIKit;
using UserNotifications;
namespace MyApp.iOS
{
public class UserNotificationCenterDelegate : UNUserNotificationCenterDelegate
{
private DBService dataBase;
#region Constructors
public UserNotificationCenterDelegate()
{
UNUserNotificationCenter.Current.Delegate = this;
}
#endregion
#region Override Methods
public override void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
{
completionHandler.DynamicInvoke(UNNotificationPresentationOptions.Alert);
}
#endregion
}
}
App.xaml.cs:
private void SendNotificationTripAsset(int tripid)
{
//Forexample i have table with columns like title,subtitle,message means, i give like this
var content = new UserNotifications.UNMutableNotificationContent()
{
Title = title,
Subtitle = subtitle,
Body = message,
Badge = 1
};
var request = UserNotifications.UNNotificationRequest.FromIdentifier(list.TripID.ToString(), content, null);
UserNotifications.UNUserNotificationCenter.Current.AddNotificationRequest(request, (err) =>
{
if (err != null)
{
throw new Exception($"Failed to schedule notification: {err}");
}
});
}
But the notification is not trigged after app is killed(terminated). Can anyone please help me to resolve this issue.
To send a local notification when app is killed, you have to specify the time when should the local notification been sent. In your code, when you create the request, set a proper NNotificationTrigger :
NNotificationTrigger trigger = UNTimeIntervalNotificationTrigger.CreateTrigger(3, false);
var request = UserNotifications.UNNotificationRequest.FromIdentifier(list.TripID.ToString(), content, trigger );
That means this notification will be fired after 3 seconds. You can change the time to whatever you want.
Code will not execute when your app is killed, so if you don't know the specific time to fire the notification or it fires randomly, you should use a remote notification.

Xamarin Forms: Load a contentpage when tap push notification android

I completed receiving test notification from FCM console. Now I am trying to open a page when tapping the notification. Any ideas about how to achieve this? I have searched the internet but can't find a working solution. I am also able to send the notification through the postman.
I handle the notification tapping in following way. The page loading is handled in App.xaml.cs.
On OnCreate():
//Background or killed mode
if (Intent.Extras != null)
{
foreach (var key in Intent.Extras.KeySet())
{
var value = Intent.Extras.GetString(key);
if (key == "webContentList")
{
if (value?.Length > 0)
{
isNotification = true;
LoadApplication(new App(domainname, value));
}
}
}
}
//Foreground mode
if (FirebaseNotificationService.webContentList.ToString() != "")
{
isNotification = true;
LoadApplication(new App(domainname, FirebaseNotificationService.webContentList.ToString()));
FirebaseNotificationService.webContentList = "";
}
//Normal loading
if (!isNotification)
{
LoadApplication(new App(domainname, string.Empty));
}
On FirebaseNotificationService:
[Service]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
public class FirebaseNotificationService : FirebaseMessagingService
{
public static string webContentList = "";
public override void OnMessageReceived(RemoteMessage message)
{
base.OnMessageReceived(message);
webContentList = message.Data["webContentList"];
try
{
SendNotificatios(message.GetNotification().Body, message.GetNotification().Title);
}
catch (Exception ex)
{
Console.WriteLine("Error:>>" + ex);
}
}
public void SendNotificatios(string body, string Header)
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
var intent = new Intent(this, typeof(MainActivity));
intent.AddFlags(ActivityFlags.ClearTop);
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.OneShot);
var notificationBuilder = new Android.App.Notification.Builder(this, Utils.CHANNEL_ID)
.SetContentTitle(Header)
.SetSmallIcon(Resource.Drawable.icon)
.SetContentText(body)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent);
var notificationManager = NotificationManager.FromContext(this);
notificationManager.Notify(0, notificationBuilder.Build());
}
else
{
var intent = new Intent(this, typeof(MainActivity));
intent.AddFlags(ActivityFlags.ClearTop);
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.OneShot);
var notificationBuilder = new Android.App.Notification.Builder(this, Utils.CHANNEL_ID)
.SetContentTitle(Header)
.SetSmallIcon(Resource.Drawable.icon)
.SetContentText(body)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent)
.SetChannelId(Utils.CHANNEL_ID);
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
return;
}
var channel = new NotificationChannel(Utils.CHANNEL_ID, "FCM Notifications", NotificationImportance.High)
{
Description = "Firebase Cloud Messages appear in this channel"
};
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
notificationManager.CreateNotificationChannel(channel);
notificationManager.Notify(0, notificationBuilder.Build());
}
}
I don't know what's your actual Firebase implementation, but this might help you.
There is a nice package for Firebase in Xamarin Forms that we use in our production App made by CrossGeeks team. Its working great and has all handlers for your needs. This works with iOS and Android and you don't need to write platform specific code, only configuration and some code in AppDelegate.cs and MainActivity.cs
https://github.com/CrossGeeks/FirebasePushNotificationPlugin/blob/master/docs/FirebaseNotifications.md#notification-events
I wrote a simple PushNotificationService that processes automatic refresh and/or pushes new pages considering push notif data.
When app is closed and user clicks on the notification, I store the push notif data using Akavache.
CrossFirebasePushNotification.Current.OnNotificationOpened += async (s, p) =>
{
if (App.AppBeenResumed)
{
await BlobCache.UserAccount.InsertObject("pushNotifData", p.Data);
}
else
{
await ProcessReceivedPushNotification(p.Data);
}
};
And on the landing page of the app, I check if there is an existing push notif data in the OnAppearing method of the page.
protected override void OnAppearing()
{
base.OnAppearing();
App.AppBeenResumed = false;
HandlePushNotificationIfExists();
}
private async void HandlePushNotificationIfExists()
{
IDictionary<string, object> pushNotifData;
try
{
pushNotifData = await BlobCache.UserAccount.GetObject<IDictionary<string, object>>("pushNotifData");
}
catch (KeyNotFoundException)
{
pushNotifData = null;
}
if (pushNotifData == null) return;
await BlobCache.UserAccount.InvalidateAllObjects<IDictionary<string, object>>();
await PushNotificationService.ProcessReceivedPushNotification(pushNotifData);
}
In the ProcessReceivedPushNotification you can do whatever you want ... push directly the page or whatever... call another service that will do the job of pushing a new page and some business process.
Note that App.AppBeenResumed is a static bool to determine if App has been started or resumed to handle correctly the process of treatment of the push notif (process it instant or store it in blobcache to treat it later when landing page is Appearing).
In MainActivity.cs :
protected override void OnCreate(Bundle bundle)
{
...
LoadApplication(new App(true));
}
In the App.cs :
public App(bool beenResumedOrStarted)
{
...
AppBeenResumed = beenResumedOrStarted;
...
}
protected override void OnResume()
{
AppBeenResumed = false;
}
protected override void OnSleep()
{
//iOS states are not the same so always false when device is iOS
AppBeenResumed = Device.RuntimePlatform != Device.iOS;
}

Scanning for beacons using universal beacon library

I am trying to implement a mobile app (on iPhone) that just scans for beacons and displays a notification for each one. I am a noob with beacons/bluetooth.
I implemented it using the universal beacon library (https://github.com/andijakl/universal-beacon) and i've attached my ios bluetooth implementation.
my problem is that i receive about 12 beacon added events even though i only have two (I assume it is picking up all my other bluetooth devices). I also only receive the local name in the advertisement_received event.
My questions are:
how do I distinguish that it is a beacon being added?
how do i get the unique id an url from the beacon? (they are kontakt beacons)
Thanks for any help.
My beacon service:
public BeaconService()
{
// get the platform-specific provider
var provider = RootWorkItem.Services.Get<IBluetoothPacketProvider>();
if (null != provider)
{
// create a beacon manager, giving it an invoker to marshal collection changes to the UI thread
_manager = new BeaconManager(provider, Device.BeginInvokeOnMainThread);
_manager.Start();
_manager.BeaconAdded += _manager_BeaconAdded;
provider.AdvertisementPacketReceived += Provider_AdvertisementPacketReceived;
}
}
My ios bluetooth implementation:
public class iOSBluetoothPacketProvider : CocoaBluetoothPacketProvider { }
public class CocoaBluetoothPacketProvider : NSObject, IBluetoothPacketProvider
{
public event EventHandler<BLEAdvertisementPacketArgs> AdvertisementPacketReceived;
public event EventHandler<BTError> WatcherStopped;
private readonly CocoaBluetoothCentralDelegate centralDelegate;
private readonly CBCentralManager central;
public CocoaBluetoothPacketProvider()
{
Debug.WriteLine("BluetoothPacketProvider()");
centralDelegate = new CocoaBluetoothCentralDelegate();
central = new CBCentralManager(centralDelegate, null);
}
private void ScanCallback_OnAdvertisementPacketReceived(object sender, BLEAdvertisementPacketArgs e)
{
AdvertisementPacketReceived?.Invoke(this, e);
}
public void Start()
{
Debug.WriteLine("BluetoothPacketProvider:Start()");
centralDelegate.OnAdvertisementPacketReceived += ScanCallback_OnAdvertisementPacketReceived;
// Wait for the PoweredOn state
//if(CBCentralManagerState.PoweredOn == central.State) {
// central.ScanForPeripherals(peripheralUuids: new CBUUID[] { },
// options: new PeripheralScanningOptions { AllowDuplicatesKey = false });
//}
}
public void Stop()
{
Debug.WriteLine("BluetoothPacketProvider:Stop()");
centralDelegate.OnAdvertisementPacketReceived -= ScanCallback_OnAdvertisementPacketReceived;
central.StopScan();
WatcherStopped?.Invoke(sender: this, e: new BTError(BTError.BluetoothError.Success));
}
}
internal class CocoaBluetoothCentralDelegate : CBCentralManagerDelegate
{
public event EventHandler<BLEAdvertisementPacketArgs> OnAdvertisementPacketReceived;
#region CBCentralManagerDelegate
public override void ConnectedPeripheral(CBCentralManager central, CBPeripheral peripheral)
{
Debug.WriteLine($"ConnectedPeripheral(CBCentralManager central, CBPeripheral {peripheral})");
}
public override void DisconnectedPeripheral(CBCentralManager central, CBPeripheral peripheral, NSError error)
{
Debug.WriteLine($"DisconnectedPeripheral(CBCentralManager central, CBPeripheral {peripheral}, NSError {error})");
}
public override void DiscoveredPeripheral(CBCentralManager central, CBPeripheral peripheral, NSDictionary advertisementData, NSNumber RSSI)
{
Debug.WriteLine($"Cocoa peripheral {peripheral}");
Debug.WriteLine($"Cocoa advertisementData {advertisementData}");
Debug.WriteLine($"Cocoa RSSI {RSSI}");
var bLEAdvertisementPacket = new BLEAdvertisementPacket()
{
Advertisement = new BLEAdvertisement()
{
LocalName = peripheral.Name,
ServiceUuids = new List<Guid>(),
DataSections = new List<BLEAdvertisementDataSection>(),
ManufacturerData = new List<BLEManufacturerData>()
},
AdvertisementType = BLEAdvertisementType.ScanResponse,
BluetoothAddress = (ulong)peripheral.Identifier.GetHashCode(),
RawSignalStrengthInDBm = RSSI.Int16Value,
Timestamp = DateTimeOffset.Now
};
//https://developer.apple.com/documentation/corebluetooth/cbadvertisementdataserviceuuidskey
//if (advertisementData.ContainsKey(CBAdvertisement.DataServiceUUIDsKey))
//{
// bLEAdvertisementPacket.Advertisement.ServiceUuids.Add(
// item: new BLEManufacturerData(packetType: BLEPacketType.UUID16List,
// data: (advertisementData[CBAdvertisement.DataServiceUUIDsKey])));
//}
//https://developer.apple.com/documentation/corebluetooth/cbadvertisementdataservicedatakey
//if (advertisementData.ContainsKey(CBAdvertisement.DataServiceDataKey))
//{
// bLEAdvertisementPacket.Advertisement.DataSections.Add(
// item: new BLEManufacturerData(packetType: BLEPacketType.ServiceData,
// data: advertisementData[CBAdvertisement.DataServiceDataKey]));
//}
//https://developer.apple.com/documentation/corebluetooth/cbadvertisementdatamanufacturerdatakey
if (advertisementData.ContainsKey(CBAdvertisement.DataManufacturerDataKey))
{
bLEAdvertisementPacket.Advertisement.ManufacturerData.Add(
item: new BLEManufacturerData(packetType: BLEPacketType.ManufacturerData,
data: (advertisementData[CBAdvertisement.DataManufacturerDataKey]
as NSData).ToArray()));
}
// Missing CBAdvertisement.DataTxPowerLevelKey
var bLEAdvertisementPacketArgs = new BLEAdvertisementPacketArgs(data: bLEAdvertisementPacket);
OnAdvertisementPacketReceived?.Invoke(this, bLEAdvertisementPacketArgs);
}
public override void FailedToConnectPeripheral(CBCentralManager central, CBPeripheral peripheral, NSError error)
{
Debug.WriteLine($"FailedToConnectPeripheral(CBCentralManager central, CBPeripheral {peripheral}, NSError {error})");
}
public override void UpdatedState(CBCentralManager central)
{
switch (central.State)
{
case CBCentralManagerState.Unknown:
Debug.WriteLine("CBCentralManagerState.Unknown");
break;
case CBCentralManagerState.Resetting:
Debug.WriteLine("CBCentralManagerState.Resetting");
break;
case CBCentralManagerState.Unsupported:
Debug.WriteLine("CBCentralManagerState.Unsupported");
break;
case CBCentralManagerState.Unauthorized:
Debug.WriteLine("CBCentralManagerState.Unauthorized");
break;
case CBCentralManagerState.PoweredOff:
Debug.WriteLine("CBCentralManagerState.PoweredOff");
break;
case CBCentralManagerState.PoweredOn:
Debug.WriteLine("CBCentralManagerState.PoweredOn");
central.ScanForPeripherals(peripheralUuids: new CBUUID[] { },
options: new PeripheralScanningOptions { AllowDuplicatesKey = true });
break;
default:
throw new NotImplementedException();
}
}
public override void WillRestoreState(CBCentralManager central, NSDictionary dict)
{
Debug.WriteLine($"WillRestoreState(CBCentralManager central, NSDictionary {dict})");
}
#endregion CBCentralManagerDelegate
}
So in case anyone is looking for this. The universal beacon library does not have an ios implementation that converts the ios packets to the universal packets. This need to be implemented.
how do I distinguish that it is a beacon being added?
I look for the Eddystone packets and if found I add to the observable list.
how do i get the unique id an url from the beacon? (they are kontakt beacons)
You need to loop through the advertisementData sent with the advertisement and create a BLEAdvertisementDataSection. copy the frame data as NSData.

Resources