I'm coding a map tracking app on Xamarin forms. I'm using async tasks(with Device.StartTimer) to keep track of the location and another task for counter timer, but when I need to open another app, like a music app or whatever the app can't keep track while it is in the background. All tasks are stopping when I send the app in the background. When I start the app again, the tasks are not continued.
I just need to keep working while app running.
How could this be done?
You should use background task in this case, which is running outside the life cycle of the application. You can find more information here Xamarin background tasks
Due to Background Execution Limits in Android 8.0 or later, Normal service will be killed when in the Background.
In the Android 8.0 or later, I suggest you to achieve a foreground service to achieve that( receive higher priority than a "regular" service and a foreground service must provide a Notification that Android will display as long as the service is running).
You can use dependence service to open a foreground service in xamarin forms.
IService.cs create a interface for android to start service.
public interface IService
{
void Start();
}
Then achieved DependentService to start a Foreground Service.
DependentService.cs
[assembly: Xamarin.Forms.Dependency(typeof(DependentService))]
namespace TabGuesture.Droid
{
[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(Resources.GetString(Resource.String.app_name))
.SetContentText(messageBody)
.SetSmallIcon(Resource.Drawable.main)
.SetOngoing(true)
.Build();
StartForeground(SERVICE_RUNNING_NOTIFICATION_ID, notification);
//do you work
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 a similar thread about your needs.
How to create service doing work at period time in Xamarin.Forms?
For IOS background Tasks. you can refer to this thread
Related
I have a Xamarin Forms application which raises an Android Notification but I'm having trouble creating a simple page that will interact with the user when they click the Notification.
I understand that in Xamarin.Forms there is only 1 activity and so the pending Intent must be to that mainActivity
I have set the LaunchMode to SingleTop and and Intent Filter to match the Intent name used in the pendingIntent
Now when I click the Notification I do get routed to the OnResume of the MainActivity but I don't understand how to:
1) Recognise that I am in this activity because of the notification click - I tried adding an Extra to the pending Intent but it is not there when I inspect this.Intent.Extras
2) Even if I know that I'm in the activity due to the notification click, how do I launch a specific page from the Activity. I'm new to Xamarin but I can't see how to navigate to a Content Page or access the Navigation Stack.
This must be a really common use case but I can't find anything relevant.
Ensure the you have set LaunchMode.SingleTop on your MainActivity:
LaunchMode.SingleTop
[Activity(~~~, LaunchMode = LaunchMode.SingleTop, ~~~]
public class MainActivity
{
~~~~
In your MainActivity (the FormsAppCompatActivity subclass) add a OnNewIntent override:
OnNewIntent:
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
NotificationClickedOn(intent);
}
Now you can check the intent.Action / intent.HasExtra to determine if it is your notification that was send and thus process it. With Xamarin.Forms the easiest would be to use MessagingCenter to send a message that is subscribed to within your .NetStd/PCL Xamarin.Forms code base.
NotificationClickedOn:
void NotificationClickedOn(Intent intent)
{
if (intent.Action == "ASushiNotification" && intent.HasExtra("MessageFromSushiHangover"))
{
/// Do something now that you know the user clicked on the notification...
var notificationMessage = intent.Extras.GetString("MessageFromSushiHangover");
var winnerToast = Toast.MakeText(this, $"{notificationMessage}.\n\nš£ Please send 2 BitCoins to SushiHangover to process your winning ticket! š£", ToastLength.Long);
winnerToast.SetGravity(Android.Views.GravityFlags.Center, 0, 0);
winnerToast.Show();
}
}
Send notification example:
void SendNotifacation()
{
var title = "Winner, Winner, Chicken Dinner";
var message = "You just won a million StackOverflow reputation points";
var intent = new Intent(BaseContext, typeof(MainActivity));
intent.SetAction("ASushiNotification");
intent.PutExtra("MessageFromSushiHangover", message);
var pending = PendingIntent.GetActivity(BaseContext, 0, intent, PendingIntentFlags.CancelCurrent);
using (var notificationManager = NotificationManager.FromContext(BaseContext))
{
Notification notification;
if (Android.OS.Build.VERSION.SdkInt < Android.OS.BuildVersionCodes.O)
{
#pragma warning disable CS0618 // Type or member is obsolete
notification = new Notification.Builder(BaseContext)
.SetContentTitle(title)
.SetContentText(message)
.SetAutoCancel(true)
.SetSmallIcon(Resource.Drawable.icon)
.SetDefaults(NotificationDefaults.All)
.SetContentIntent(pending)
.Build();
#pragma warning restore CS0618 // Type or member is obsolete
}
else
{
var myUrgentChannel = BaseContext.PackageName;
const string channelName = "Messages from SushiHangover";
NotificationChannel channel;
channel = notificationManager.GetNotificationChannel(myUrgentChannel);
if (channel == null)
{
channel = new NotificationChannel(myUrgentChannel, channelName, NotificationImportance.High);
channel.EnableVibration(true);
channel.EnableLights(true);
channel.SetSound(
RingtoneManager.GetDefaultUri(RingtoneType.Notification),
new AudioAttributes.Builder().SetUsage(AudioUsageKind.Notification).Build()
);
channel.LockscreenVisibility = NotificationVisibility.Public;
notificationManager.CreateNotificationChannel(channel);
}
channel?.Dispose();
notification = new Notification.Builder(BaseContext)
.SetChannelId(myUrgentChannel)
.SetContentTitle(title)
.SetContentText(message)
.SetAutoCancel(true)
.SetSmallIcon(Resource.Drawable.icon)
.SetContentIntent(pending)
.Build();
}
notificationManager.Notify(1331, notification);
notification.Dispose();
}
}
I'm using the library https://github.com/aritchie/notifications and I can create and schedule notifications properly.
I wish to process them in Android so that depending on the notification - it will navigate to a particular page when the user taps on it.
I've found that the below event is fired when I tap on a notification (in my Android Project)
protected override void OnNewIntent(Intent intent)
{
}
However, I can't find any info in the intent from my notification in order to build up navigation to a particular page.
Any advice would be appreciated.
Cheers!
Edit #1 (Adding additional code for a related issue):
If I fire off a notification, and close the app before the notification is received - I receive an error saying the app has crashed. If I receive the notification and close the app - I can load the app from the notification OK.
I have a dependency service which hits the following methods.
public void Remind(DateTime dateTime, string msgtype, string usermedid)
{
DateTime now = DateTime.Now;
var diffinseconds = (dateTime - now).TotalSeconds;
Intent alarmIntent = new Intent(Forms.Context, typeof(AlarmBroadcastReceiver));
alarmIntent.PutExtra("notificationtype", msgtype);
alarmIntent.PutExtra("id", id);
PendingIntent pendingIntent = PendingIntent.GetBroadcast(Forms.Context, 0, alarmIntent, PendingIntentFlags.UpdateCurrent);
AlarmManager alarmManager = (AlarmManager)Forms.Context.GetSystemService(Context.AlarmService);
//TODO: For demo set after 5 seconds.
alarmManager.Set(AlarmType.ElapsedRealtime, SystemClock.ElapsedRealtime() + diffinseconds * 1000, pendingIntent);
}
[BroadcastReceiver(Enabled = true)]
[IntentFilter(new string[]{"android.intent.action.BOOT_COMPLETED"}, Priority = (int) IntentFilterPriority.LowPriority)]
public class AlarmBroadcastReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
string notificationtype = intent.GetStringExtra("notificationtype");
PowerManager.WakeLock sWakeLock;
var pm = PowerManager.FromContext(context);
sWakeLock = pm.NewWakeLock(WakeLockFlags.Partial, "GCM Broadcast Reciever Tag");
sWakeLock.Acquire();
intent = new Intent(Forms.Context, typeof(MainActivity));
intent.PutExtra("notificationtype", notificationtype);
intent.AddFlags(ActivityFlags.IncludeStoppedPackages);
// Instantiate the builder and set notification elements, including pending intent:
NotificationCompat.Builder builder = new NotificationCompat.Builder(Forms.Context)
.SetDefaults((int)NotificationDefaults.Sound | (int)NotificationDefaults.Vibrate)
.SetAutoCancel(true)
.SetContentIntent(PendingIntent.GetActivity(Forms.Context, 0, intent, 0)).SetContentTitle("Sample Notification")
.SetContentText("Hello World! This is my first action notification!")
.SetTicker("New Notification")
.SetSmallIcon(Resource.Drawable.icon);
// Build the notification:
Android.App.Notification notification = builder.Build();
notification.Flags = NotificationFlags.AutoCancel;
// Get the notification manager:
//NotificationManager notificationManager = Forms.Context.GetSystemService(Context.NotificationService) as NotificationManager;
var manager = NotificationManagerCompat.From(context);
// Publish the notification:
const int notificationId = 0;
manager.Notify(notificationId, notification);
sWakeLock.Release();
}
}
How do I keep my Broadcast Receiver alive when the app is closed?
Ok so it took me some time to figure this one out. OnNewIntent is called when the app is in the background and the notification is clicked on. It is also called each time the app is minimized and the brought back up... so to tell the difference between the 2 events, you need to check the passed in Intent for what extra data is inside it. The extra data would have come from the Intent you made when you first initiated the notification.
Also make sure to set your MainActivity's LaunchMode to LaunchMode.SingleTop so that your app does not get restarted each time the notification is clicked on.
[Activity(LaunchMode = LaunchMode.SingleTop, ....)]
public class MainActivity : FormsApplicationActivity {
....
/// <summary>
/// Called when the app is in the background and a notification is clicked on (also called each time the app is minimized and the brought back up), a new <c>Intent</c> is created
/// and sent out, since we use <c>LaunchMode</c> set to <c>SingleTop</c> this method is called instead of the app being restarted.
/// </summary>
/// <param name="intent">The <c>Intent</c> that was set when the call was made. If started from a notification click, extra <c>string</c> values can be extracted.</param>
protected override void OnNewIntent(Intent intent) {
if(intent.HasExtra("Some special key you made up")) { //Here is where you check for special notification intent extras
//Do something brilliant now that you know a notification was clicked on
}
base.OnNewIntent(intent);
}
To see how you can add data to the Intent you can check out the Xamarin Sport App, but do not get too bogged down in all the other stuff they are doing like I always tend to do. Just focus on the PutExtra part.
Edit #1:
If your app is completely closed, you need to pull the data from the Intent passed into OnCreate and pass it into your App class or do something else with it:
protected override async void OnCreate(Android.OS.Bundle bundle) {
base.OnCreate(bundle);
Forms.Init(this, bundle);
string parameterValue = Intent.GetStringExtra("Some special key you made up"); //This would come in from the Push Notification being clicked on
Console.WriteLine("\nIn MainActivity.OnCreate() - Param Intent Extras: {0}\n", parameterValue);
//MessagingCenter.Send("nothing", ConstantKeys.NewNotification); //Do something special with the notification data
LoadApplication(parameterValue != null ? new App(parameterValue) : new App()); //Do something special with the notification data
}
Edit #2:
Some changes I would recommend to your OnReceive method based on my current code (some may not be necessary, but it is just what I am doing):
Label your Broadcast Receiver
Add stupid Xamarin constructors
Used constant property instead of string for IntentFilter
Remove IntentFilter Priority
Check for null Intent (might not be necessary)
Use Application.Context instead of Forms.Context (I use Forms.Context in other parts of my app so not sure about this one, but
can't hurt)
Do not overwrite the passed in Intent
Create startup intent instead of regular
Add IncludeStoppedPackages flag before pulling out extras
Check for boot completed event
Use Notification.Builder instead of NotificationCompat.Builder (though you might need to change this back)
Add following flags to pendingintent: PendingIntentFlags.UpdateCurrent | PendingIntentFlags.OneShot
-- Use NotificationManager (unless you have a specific reason you commented it out)
[assembly: UsesPermission(Android.Manifest.Permission.Vibrate)]
[assembly: UsesPermission(Android.Manifest.Permission.WakeLock)] //Optional, keeps the processor from sleeping when a message is received
[assembly: UsesPermission(Android.Manifest.Permission.ReceiveBootCompleted)] //Allows our app to be opened and to process notifications even when the app is closed
namespace Your.App.Namespace {
[BroadcastReceiver(Enabled = true, Label = "GCM Alarm Notifications Broadcast Receiver")]
[IntentFilter(new []{ Intent.ActionBootCompleted })]
public class AlarmBroadcastReceiver : BroadcastReceiver {
#region Constructors
// ReSharper disable UnusedMember.Global
public AlarmBroadcastReceiver() { }
public AlarmBroadcastReceiver(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer) { }
// ReSharper restore UnusedMember.Global
#endregion
public void Remind(DateTime dateTime, string msgtype, string usermedid) {
DateTime now = DateTime.Now;
var diffinseconds = (dateTime - now).TotalSeconds;
Intent alarmIntent = new Intent(Application.Context, typeof(AlarmBroadcastReceiver));
alarmIntent.PutExtra("notificationtype", msgtype);
alarmIntent.PutExtra("id", id);
PendingIntent pendingIntent = PendingIntent.GetBroadcast(Application.Context, 0, alarmIntent, PendingIntentFlags.UpdateCurrent);
AlarmManager alarmManager = (AlarmManager)Application.Context.GetSystemService(Context.AlarmService);
//TODO: For demo set after 5 seconds.
alarmManager.Set(AlarmType.ElapsedRealtime, SystemClock.ElapsedRealtime() + diffinseconds * 1000, pendingIntent);
}
public override void OnReceive(Context context, Intent intent) {
#region Null Check
if(intent == null) {
Console.WriteLine("\nIn AlarmBroadcastReceiver.OnReceive() - Intent is null\n");
return;
}
#endregion
intent.AddFlags(ActivityFlags.IncludeStoppedPackages);
string action = intent.Action;
Console.WriteLine("\nIn AlarmBroadcastReceiver.OnReceive() - Action: {0}\n", action);
#region Boot Completed Check
if(action.Equals("android.intent.action.BOOT_COMPLETED")) {
PowerManager pm = PowerManager.FromContext(context);
PowerManager.WakeLock sWakeLock = pm.NewWakeLock(WakeLockFlags.Partial, "GCM Broadcast Receiver Tag");
sWakeLock.Acquire();
Console.WriteLine("\nIn AlarmBroadcastReceiver.OnReceive() - Process Shared Preferences Notifications\n");
#region Process Saved Scheduled Notifications
//Get list of saved scheduled notifications that did not fire off before the device was turned off (I store them in SharedPreferences and delete them after they are fired off)
//Go through the list and reschedule them
#endregion
sWakeLock.Release();
return;
}
#endregion
string notificationtype = intent.GetStringExtra("notificationtype");
Intent startupIntent = Application.Context.PackageManager.GetLaunchIntentForPackage(Application.Context.PackageName);
startupIntent.PutExtra("notificationtype", notificationtype);
// Instantiate the builder and set notification elements, including pending intent:
Notification.Builder builder = new Notification.Builder(Application.Context)
.SetDefaults((int)NotificationDefaults.Sound | (int)NotificationDefaults.Vibrate)
.SetAutoCancel(true)
.SetContentIntent(PendingIntent.GetActivity(Application.Context, 0, intent, PendingIntentFlags.UpdateCurrent | PendingIntentFlags.OneShot))
.SetContentTitle("Sample Notification")
.SetContentText("Hello World! This is my first action notification!")
.SetTicker("New Notification")
.SetSmallIcon(Resource.Drawable.icon);
// Build the notification:
Android.App.Notification notification = builder.Build();
// Get the notification manager:
NotificationManager notificationManager = Application.Context.GetSystemService(Context.NotificationService) as NotificationManager;
// Publish the notification:
int notificationId = ??;//This should be a real unique number, otherwise it can cause problems if there are ever multiple scheduled notifications
notificationManager.Notify(notificationId, notification);
}
}
}
I'm currently working on a Xamarin application working both on iOS and Android, but the problem I'm going to explain only concerns the Android application (this is not yet implemented in the iOS app).
Actually, when I receive a given push notification, I need to open a specific page in my application. It works very well if the application is open when the push notification is received, but the app crashes if my app is closed or run in background.
Well, when I receive the notification, I end up in the method called "OnShouldOpenCommand" :
private void OnShouldOpenCommand(string commandId)
{
NotifyNewCommand(AppResources.AppName, AppResources.CommandNotificationText, commandId);
Device.BeginInvokeOnMainThread(() =>
{
try
{
App.MasterDetailPage.Detail = new NavigationPage(new CommandAcceptancePage(commandId))
{
BarBackgroundColor = Color.FromHex("1e1d1d")
};
App.MasterDetailPage.NavigationStack.Push(((NavigationPage)(App.MasterDetailPage.Detail)).CurrentPage);
}
catch(Exception e)
{
Log.Debug("PushAsync", "Unable to push CommandAcceptancePage : "+ex.Message);
}
});
}
private void NotifyNewCommand(string Title,string Description, string commandId)
{
var intent = new Intent(this, typeof(MainActivity));
if (!String.IsNullOrEmpty(commandId))
{
intent.PutExtra("CommandId", commandId);
}
intent.AddFlags(ActivityFlags.ClearTop);
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0);
var notificationBuilder = new Notification.Builder(this)
.SetSmallIcon(Resource.Drawable.icon)
.SetContentTitle("Kluox")
.SetContentText(Description)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent);
Notification notification = notificationBuilder.Build();
var notificationManager = (NotificationManager)GetSystemService(Context.NotificationService);
notificationManager.Notify(0, notification);
}
And the code
App.MasterDetailPage.Detail = new NavigationPage(new CommandAcceptancePage(commandId))
{
BarBackgroundColor = Color.FromHex("1e1d1d")
};
is generating an exception of type :
Java.Lang.IllegalStateException: Can not perform this action after
onSaveInstanceState
So well, I suppose I can't access "App" and redirect to another page if my application is not running in foreground. Well, this is when I receive the push notification an not when I click on it. But well, I do not intend to reopen my app by doing this.
Because afther that, when I click on the push notification called Kluox (and this is supposed to reopen my app), the app crashes and I really don't know why, I don't know where to put breakpoints to be able to debug because Visual Studio just tells me "An unhandled exception occured.".
Could anyone help me ? If you need any piece of code, you can just ask me, I'll edit my message and give you any information you need !
EDIT 1 : Here is the code of my OnCreate method :
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
var info = Intent.Extras?.GetString("CommandId", "");
global::Xamarin.Forms.Forms.Init(this, bundle);
if (String.IsNullOrEmpty(info))
{
LoadApplication(new App());
}
else
{
LoadApplication(new App(info));
}
if (instance == null)
{
instance = this;
RegisterWithGCM();
}
else
{
instance = this;
}
}
After overriding all the methods of MainActivity, I finally found the cause of the crash : the method OnDestroy was called twice, and threw a IllegalStateException because the activity was already destroyed. I found this workaround :
protected override void OnDestroy()
{
try
{
base.OnDestroy();
}
catch (Java.Lang.IllegalStateException ex)
{
Log.Debug("MainActivity.OnDestroy", ex, "The activity was destroyed twice");
}
}
And the exception is simply logged, the application can open and be used without problems.
I'll edit this answer when the redirection works too.
EDIT : how to redirect to a page
First, we needed to register for the MessagingCenter, in the constructor
public static MyPackage.Model.Command CurrentCommand { get; set; }
public App()
{
InitializeComponent();
MainPage = new ContentPage();
MessagingCenter.Subscribe<object, DataLib.Model.Command>(this, "Command", (sender, arg) => {
try
{
CurrentCommand = arg;
}
catch(Exception ex)
{
CurrentCommand = null;
}
});
}
And send the message when we get the push notification :
private void OnMessage(string serializedCommand)
{
//stuff happens
MessagingCenter.Send<object, MyPackage.Model.Command>(this, "Command", command);
}
Finally, when we get the OnStart() of App.Xaml.cs
if (CurrentCommand != null)
{
App.MasterDetailPage.Detail = new NavigationPage(new CommandAcceptancePage(CurrentCommand, service))
{
BarBackgroundColor = Color.FromHex("1e1d1d")
};
}
For now, it seems to do the trick ! More debugging will follow, but the code seems to work. Thanks a VERY lot to #BraveHeart for their help !
well luckily for you I was there few days ago and lost a lot of hair till I got it working in Android (and still in the strugle for iOS).
When you kill your app and instantiate it again form the icon or from the notification in both cases you will go to the main activity .
If we want to take some information in the main activity from the notification that instantiated it we do it like this in OnCreate():
var info = Intent.Extras?.GetString("info", "");
Now in your case I would add extra information to the notification showing that which View/Page this notification is about, something like the name of it for example)
This extra piece of information you can pass it to the constructor of the App before you load it.
In the constructor of the app you can check if there are extra info or not , if not that means to start the app's mainPage is the default MainPage, otherwise it is a certain page.
In regards to Android auto how can i get a callback when user has plugged the device into a car ? I'd like to trigger an action to occur based on when the user actually connects to android auto, is it possible ?
You should be able to get USB connection broadcast. From there you will have to write your own logic to figure out Android Auto is in foreground.
(may need to introduce a slight delay in case android auto takes time to come up. Launcher seems to be part of google play service)
Here is how I did it in my services onCreate method (right form the example code):
IntentFilter filter = new IntentFilter(CarHelper.ACTION_MEDIA_STATUS);
mCarConnectionReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String connectionEvent = intent.getStringExtra(CarHelper.MEDIA_CONNECTION_STATUS);
mIsConnectedToCar = "media_connected".equals(connectionEvent);
LogHelper.i(TAG, "Connection event to Android Auto: ", connectionEvent,
" isConnectedToCar=", mIsConnectedToCar);
if(mIsConnectedToCar ) {
if(mService == null) {
bindMusicService();
if (mService != null) {
try {
initMediaMetaData();
toggleMediaPlaybackState(mService.isPlaying());
} catch (RemoteException e) {
Log.d(TAG, e.toString());
}
}
}
}
}
};
registerReceiver(mCarConnectionReceiver, filter);
Issue
I'm trying to trigger a BackgroundTask when a Geofence Event (Enter / Exit) occurs in WP8.1 (WinRT). I've written a sample application to try to get it working, but can't seem to be able to do so.
So far, these are the steps I've taken to try to get Geofences working in the background:
Check for Location Capabilities
Create + Register a Geofence
Create + Register a BackgroundTask that listens for LocationTrigger(LocationTriggerType.Geofence);
In my background task, trigger a simple popup notification
Things I have done to Troubleshoot
I have enabled in my app.manifest:
Toast Capable => Yes
Capabilities: Location, Internet(Client &
Server)
Declarations: BackgroundTasks (Location). EntryPoint = BackgroundTask.GeofenceBackgroundTask
My background task is located in a separate project, titled BackgroundTask. It is a WindowsRT Component and contains one class GeofenceBackgroundTask.
Sample Project
The code for the project can be found at this [link](https://github.com/kiangtengl/GeofenceSample):
How To Test
Run the code in the emulator
Set Location to to: Latitude = 01.3369, Longitude = 103.7364
Click the Register Geofence + BackgroundTasks button
Exit the app (press the home button)
Change the current location to anywhere 100m away from the location you set previously. A notification should pop out.
Project Code:
Check for Location Capabilities
public static async Task GetLocationCapabilities()
{
try
{
var geolocator = new Geolocator();
await geolocator.GetGeopositionAsync();
var backgroundAccessStatus = await BackgroundExecutionManager.RequestAccessAsync();
Debug.WriteLine("background access status" + backgroundAccessStatus);
}
catch (UnauthorizedAccessException e)
{
Debug.WriteLine(e);
}
catch (TaskCanceledException e)
{
Debug.WriteLine(e);
}
}
Create Geofence
public static void CreateGeofence(BasicGeoposition position, double radius, string id = "default")
{
// The Geofence is a circular area centered at (latitude, longitude) point, with the
// radius in meter.
var geocircle = new Geocircle(position, radius);
// Sets the events that we want to handle: in this case, the entrace and the exit
// from an area of intereset.
var mask = MonitoredGeofenceStates.Entered | MonitoredGeofenceStates.Exited;
// Specifies for how much time the user must have entered/exited the area before
// receiving the notification.
var dwellTime = TimeSpan.FromSeconds(1);
// Creates the Geofence and adds it to the GeofenceMonitor.
var geofence = new Geofence(id, geocircle, mask, false, dwellTime);
try
{
GeofenceMonitor.Current.Geofences.Add(geofence);
}
catch (Exception e)
{
Debug.WriteLine(e);
// geofence already added to system
}
}
Register Background Task
public static async Task RegisterBackgroundTask()
{
try
{
// Create a new background task builder
var geofenceTaskBuilder = new BackgroundTaskBuilder()
{
Name = GeofenceBackgroundTaskName,
TaskEntryPoint = "BackgroundTask.GeofenceBackgroundTask"
};
// Create a new location trigger
var trigger = new LocationTrigger(LocationTriggerType.Geofence);
// Associate the location trigger with the background task builder
geofenceTaskBuilder.SetTrigger(trigger);
var geofenceTask = geofenceTaskBuilder.Register();
// Associate an event handler with the new background task
geofenceTask.Completed += (sender, e) =>
{
try
{
e.CheckResult();
}
catch(Exception error)
{
Debug.WriteLine(error);
}
};
}
catch(Exception e)
{
// Background task probably exists
Debug.WriteLine(e);
}
}
BackgroundTask Code to Trigger Toast
namespace BackgroundTask
{
public sealed class GeofenceBackgroundTask : IBackgroundTask
{
public void Run(IBackgroundTaskInstance taskInstance)
{
var toastTemplate = ToastTemplateType.ToastText02;
var toastXML = ToastNotificationManager.GetTemplateContent(toastTemplate);
var textElements = toastXML.GetElementsByTagName("text");
textElements[0].AppendChild(toastXML.CreateTextNode("You have left!"));
var toast = new ToastNotification(toastXML);
ToastNotificationManager.CreateToastNotifier().Show(toast);
}
}
}
I've discovered that the above code sample, as well as the above code works. The problem that I was facing was that Windows Phone 8.1 does not automatically trigger a Geofence event. You have to wait a certain amount of time <5 mins before the BackgroundTask is triggered.
This applies to Geofencing in the foreground as well.
I'm busy with the same stuff, and I also noticed this behaviour, but for me its 2 mins.
Unfortunately it always triggers after 2 min, even, if there was no change in location and still inside the fence..