Now, that's how to create your own Geofence and add it to the GeofenceMonitor. Now you might wonder whether you'd really need to define all that parameters to create a Geofence. Not actually, the least you need to do is to define an Id and a Geocircle to start your Geofence. ;) The rest of the values would use the defaults.
Handling GeoFence Notifications in the Foreground
We have done all our background work to create a Geofence but we won't be able to do a single thing unless we test our Geofence. Now, we have already specified that our Geofence should trigger events after the aforementioned dwellTime and will trigger events if I enter or leave it. So, I definitely need to attach an event handler at a certain place. Don't I?
Before that, let's create the method stub for the click event handler for CreateGeoFencingButton and start the SetupGeofence() method inside it. If you don't know how to do that, select the CreateGeoFencingButton in XAML, go to properties, select the events tab and double-click on the TextBox.
- private void CreateGeoFencingButton_Click(object sender, RoutedEventArgs e)
- {
- SetupGeofence();
- }
Then let's add the handler in the end of SetupGeofence().
- GeofenceMonitor.Current.GeofenceStateChanged += Current_GeofenceStateChanged;
Now, let's say we want to show a message every time I enter and exit my room's Geofence. The handler might look like the following:
- private async void Current_GeofenceStateChanged(GeofenceMonitor sender, object args)
- {
- var Reports = sender.ReadReports();
-
- await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
- {
- foreach (GeofenceStateChangeReport report in Reports)
- {
- GeofenceState state = report.NewState;
-
- Geofence geofence = report.Geofence;
-
- if (state == GeofenceState.Entered)
- {
- ShowMessage("You are pretty close to your room");
-
- }
- else if (state == GeofenceState.Exited)
- {
-
- ShowMessage("You have left pretty far away from your room");
- }
- }
- });
- }
There you go! That's how you set up your Geofence in an app running in the foreground.
Setting up a GeoFence in the background
Now, this is where things get a little tricky. There's a number of steps involved in this.
- Create a Windows Runtime Component Project and add it to your solution.
- Change Package.appxmanifest to declare the BackgroundTask that has been created.
- Register the BackgroundTask.
- Handle Geofence Notifications from Background Task.
Create a Windows Runtime Component Project
Usually, background tasks are a Windows runtime component so you need to add one to your solution. Please go over to your solution and add a Windows Runtime Project.
After the new project has been created go to class1.cs and rename it to your liking. I renamed it to BackgroundTask and added an IBackgroundTask interface to the class and implemented it. I didn't write anything afterwards. Let's just keep it like that for a while and move to our GeofenceTest project. Go to the references and add the newly created GeofenceTask.BackgroundGeofenceTask project to it. So our Wp8.1 project now has a reference to the GeofenceTask.BackgroundGeofenceTask project.
Change Package.appxmanifest to declare the BackgroundTask
Now let's scoot over to our Package.appxmanifest file and go to the Declarations tab. From the Available Declarations combobox select BackgroundTask and select the location under the Properties list.
Now on the Entry point field please put the full-qualified assembly name of your newly created background task. Don't get confused. It's fairly easy. Let's have a look at the BackgroundGeofenceTask class first.
- namespace GeofenceTask
- {
- public sealed class BackgroundGeofenceTask : IBackgroundTask
- {
- public void Run(IBackgroundTaskInstance taskInstance)
- {
- throw new NotImplementedException();
- }
- }
- }
Now use a name in the "namespace.classname" format so that it leaves us with "GeofenceTask.BackgroundGeofenceTask".
Register the BackgroundTask
Now for the part where you register the BackgroundTask. We need to register the background task to the app so it knows to invoke it one time. The registering method looks as in following:
- private async Task RegisterBackgroundTask()
- {
- BackgroundAccessStatus backgroundAccessStatus = await BackgroundExecutionManager.RequestAccessAsync();
-
- var geofenceTaskBuilder = new BackgroundTaskBuilder
- {
- Name = "My Home Geofence",
- TaskEntryPoint = "GeofenceTask.BackgroundGeofenceTask"
- };
-
- var trigger = new LocationTrigger(LocationTriggerType.Geofence);
- geofenceTaskBuilder.SetTrigger(trigger);
-
- var geofenceTask = geofenceTaskBuilder.Register();
- geofenceTask.Completed += GeofenceTask_Completed;
-
- switch (backgroundAccessStatus)
- {
- case BackgroundAccessStatus.Unspecified:
- case BackgroundAccessStatus.Denied:
- ShowMessage("This application is denied of the background task ");
- break;
- }
-
- }
Let's see what we have in here now. The very first one gets the BackgroundAccessStatus. That helps us to know whether our application is capable of accessing BackgroundTasks. Now, let's move up and use BackgroundTaskBuilder to create the background task we intend to create. Now, if you look at the TaskEntryPoint we provided there, youll now realize why we created our Background Task project before and added it to the manifest. Because we are using the same entry point name here. I used a name to identify the background task. This is necessary since if there is another background task with the same name you would get an exception thrown. If you want to know whether there is another BackgroundTask with the same name you can iterate through BackgroundTaskRegistration.AllTasks and then determine whether or not there is one with the same name. You can use a method like the following to do so:
- public static bool IsTaskRegistered(string TaskName)
- {
- var Registered = false;
- var entry = BackgroundTaskRegistration.AllTasks.FirstOrDefault(keyval => keyval.Value.Name == TaskName);
-
- if (entry.Value != null)
- Registered = true;
-
- return Registered;
- }
I haven't used that in this solution but feel free to definitely use this. You can even unregister to ensure that your Background Task is unregistered to allow for others.
- public static void Unregister(string TaskName)
- {
- var entry = BackgroundTaskRegistration.AllTasks.FirstOrDefault(keyval => keyval.Value.Name == TaskName);
-
- if (entry.Value != null)
- entry.Value.Unregister(true);
- }
The next thing that comes off is defining the LocationTrigger for the BackgroundTaskBuilder object. So, we defined a new LocationTrigger of type LocationTriggerType.Geofence and used the SetTrigger method to set it up. Then we used a BackgroundTaskBuilder object to register the task and we instantiated a GeofenceTask_Completed event handler for that too.
At the bottom you'll see a switch with backgroundAccessStatus and showing a MessageDialog when it's denied or unspecified.
Handle Geofence Notifications from Background Task
So, let's move to our Windows Runtime Component that is actually the Background Task project and implement the IBackgroundTask interface. A method named:
public void Run(IBackgroundTaskInstance taskInstance)
will pop up, we'd put our regular Geofence event handling in there like there in the Foreground example. Now it looks as in following when we are handling it from the background.
- public sealed class BackgroundGeofenceTask : IBackgroundTask
- {
- public void Run(IBackgroundTaskInstance taskInstance)
- {
- var Reports = GeofenceMonitor.Current.ReadReports();
- var SelectedReport =
- Reports.FirstOrDefault(report => (report.Geofence.Id == "My Home Geofence") && (report.NewState == GeofenceState.Entered || report.NewState == GeofenceState.Exited));
-
- if (SelectedReport==null)
- {
- return;
- }
-
-
- var ToastContent = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02);
-
- var TextNodes = ToastContent.GetElementsByTagName("text");
-
- if (SelectedReport.NewState == GeofenceState.Entered)
- {
- TextNodes[0].AppendChild(ToastContent.CreateTextNode("You are pretty close to your room"));
- TextNodes[1].AppendChild(ToastContent.CreateTextNode(SelectedReport.Geofence.Id));
- }
- else if(SelectedReport.NewState == GeofenceState.Exited)
- {
- TextNodes[0].AppendChild(ToastContent.CreateTextNode("You are pretty close to your room"));
- TextNodes[1].AppendChild(ToastContent.CreateTextNode(SelectedReport.Geofence.Id));
- }
-
- var settings = ApplicationData.Current.LocalSettings;
-
- if (settings.Values.ContainsKey("Status"))
- {
- settings.Values["Status"] = SelectedReport.NewState;
- }
- else
- {
- settings.Values.Add(new KeyValuePair<string, object>("Status", SelectedReport.NewState.ToString()));
- }
-
- var Toast = new ToastNotification(ToastContent);
- var ToastNotifier = ToastNotificationManager.CreateToastNotifier();
- ToastNotifier.Show(Toast);
-
- }
- }
Now, this looks a wee bit different from the first example. The first change that is noticable is that we are now selecting the reports based on our Geofence id and the report's new state. Since we are in the background now, we are using our Geofence id to get the proper Geofence from the entire list. And since we are now in the background we are using a Toast Notification instead of a MessageDialog to show our notification.
Handling it from the App Side
Now you guys might get confused about what to do with the foreground code we made earlier. Do we need the Current_GeofenceStateChanged event handler anymore? Now here we might need to be a bit careful. Now if we want our app to react differently when it is in the foreground and make some UI changes it is necessary to use a GeofenceTask_Completed event rather than Current_GeofenceStateChanged. And there's another thing to be added. We get our GeofenceMonitor reports using GeofenceMonitor.Current.ReadReports() and this step can only be done once for every change. So if your background reads it first, then your foreground app would not be able to read it. So, we need to save it somewhere when the background reads it so our foreground app can read it from there.
So we are using ApplicationData.Current.LocalSettings to save our state in the Background Task. If you look closely you'd find the following snippet:
- var settings = ApplicationData.Current.LocalSettings;
-
- if (settings.Values.ContainsKey("Status"))
- {
- settings.Values["Status"] = SelectedReport.NewState;
- }
- else
- {
- settings.Values.Add(new KeyValuePair<string, object>("Status", SelectedReport.NewState.ToString()));
- }
So, in our app side in the GeofenceTask_Completed event handler we'll read the status and fill up a new textblock.
Thus we added a new set of textblocks in the MainPage.xaml as in the following:
- <TextBlock Grid.Row="2" Text="Status" FontSize="25"/>
- <TextBlock Grid.Row="2" Grid.Column="1" Name="Status" FontSize="25"/>
Now, our GeofenceTask_Completed event handler looks as in the following:
- private async void GeofenceTask_Completed(BackgroundTaskRegistration sender, BackgroundTaskCompletedEventArgs args)
- {
- if (sender != null)
- {
-
- await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
- {
- try
- {
-
- args.CheckResult();
-
- var settings = ApplicationData.Current.LocalSettings;
-
-
- if (settings.Values.ContainsKey("Status"))
- {
- Status.Text = settings.Values["Status"].ToString();
- }
-
- }
- catch (Exception ex)
- {
- ShowMessage(ex.Message);
- }
- });
- }
-
- }
Now, if you look closely you will see all we did here is get the "Status" object from our ApplicationData.Current.LocalSettings and posted it on the TextBlock named Status. :)
Now, all we have left to do is to do a little change in the UI and add an extra button to set up the Geofence background task. Now it looks as in the following:
I created the method stub for the new buttons click event too and it looks as in the following:
- private async void CreateBackgroundServiceButton_Click(object sender, RoutedEventArgs e)
- {
- if (IsTaskRegistered("My Home Geofence"))
- {
- Unregister("My Home Geofence");
- }
- await RegisterBackgroundTask();
- }
Testing the App on the Emulator
You can definitely test the app on the emulator. Like always click the debug button with an emulator selected as the device and when it opens go to the additional tools button and open the location tab. Select a close location to your Geofence, I did select a close one to my room and the status changed itself to "Entered". It was that easy! You can even try the foreground example in the same way!
Stay frosty! I hope this will help.