Creating smarter apps with geofencing

Creating smarter apps with geofencing

  • Comments 1
  • Likes

Windows 8 introduced the Geolocation API, which let your app get the current location or get regular updates on the current location. We covered some of that functionality in a previous blog post (Create location aware apps using geolocation and Bing Maps). Windows 8.1 Preview introduces the Geofencing API, by which your app can be notified—in a smart, timely, power-efficient manner—whenever the user moves into or out of a defined geographic region. You don’t have to poll for location info anymore; the geofencing API does all the work of getting the location info from various sources such as GPS, Wi-Fi, or an IP address on the location platform, and of checking to see if the device has entered or exited a geographic region. The platform encapsulates the implementation details so that it will work on a broad range of devices.

By using geofencing, your app can do things like:

  • Provide location-based reminders—for example, to pick up a jacket from friend’s house or get milk at the grocery store.
  • Provide contextual info at an area of interest, like a coupon or special offer at a coffee shop, or a reminder to use a loyalty card at a store.
  • Alert the user. Your app can notify users before they arrive at their bus stop or let others know when the user leaves or arrives at a destination.
  • Take automatic actions. For example, your app can do an automatic social check-in or check-out at a site of interest, or adapt its settings to the user’s location.
  • Be a virtual tour guide: display pictures and info when the user gets close to a landmark.

Using geofencing is fairly straightforward. It just requires:

  1. Making sure your app has the proper permissions.
  2. Defining your geographic region of interest (that is, the geofence).
  3. Defining the events you want to be notified of – typically the Entered or Exited events.
  4. Handling the events and taking action when the events occur.

In the rest of this post, we’ll walk you through each of these steps in more detail. Let’s get started.

Making sure your app has permission

Before your app can receive geofencing event notifications, it must have location permissions. The best way to do this is to get the current location asynchronously from the main UI thread. This results in a permission request prompt that will automatically be shown to the user the first time that it is called.

After making a choice, the user can always go to the settings for your app to modify the permissions.  Because a user can change permissions at any time, it’s a good idea for your app to be notified of permissions changes. It can register for the DeviceAccessStatus.OnAccessChanged event so that it can receive and handle these notifications appropriately.

geofencing-1
Location permission prompt

It’s best to get permission right before you define a geofence, so you can prompt the user to change the permissions if necessary. For sample code that checks for the proper permissions, see Quickstart: Setting up a geofence. Note that you could define a geofence without location permissions, but you would not receive geofence notifications until the permissions were enabled.

Creating the geofence

Now that you know your app has permission to receive location info, you can go ahead and define a geofence.

Currently, only circular geographic regions can be defined for a geofence. To define a geofence, you specify:

  • The circular region to monitor, defined in a Geocircle object. This includes the latitude, longitude, and radius of the circle.
  • An ID that uniquely identifies the geofence.
  • The MonitoredStates property, which indicates what geofence events you want your app to receive notifications for. Your app can be alerted when a user has entered or exited the defined region. It can also be alerted if a geofence has been removed. A geofence may be removed because it expired or because the SingleUse flag had been set.
  • (Optional) A DwellTime value, which indicates how long the user must be in or out of the defined area before the enter or exit events are triggered.
  • (Optional) The StartTime, which indicates when to start monitoring the geofence.
  • (Optional) The Duration, which indicates how long to monitor the geofence.

Location data can come from a GPS device, Wi-Fi, or an IP address. Each of these location sources has a spectrum of accuracies. When you define the radius of the geofence, consider what hardware your users will have. For example, very small fences will be effective only if the device has GPS.

Now let’s look at some code for creating the geofence.

C#

private void CreateGeofence()
{
    Geofence geofence = null;
 
    string fenceKey = new string(Id.Text.ToCharArray());
 
    BasicGeoposition position;
    position.Latitude = Double.Parse(Latitude.Text);
    position.Longitude = Double.Parse(Longitude.Text);
    position.Altitude = 0.0;
    double radius = Double.Parse(Radius.Text);
 
    // The geofence is a circular region.
    Geocircle geocircle = new Geocircle(position, radius);
 
    bool singleUse = (bool)SingleUse.IsChecked;
 
    // Set up listening for enter geofence, exit geofence, 
    // and remove geofence events.
    // You can select a subset of these event states.
    MonitoredGeofenceStates mask = 0;
 
    mask |= MonitoredGeofenceStates.Entered;
    mask |= MonitoredGeofenceStates.Exited;
    mask |= MonitoredGeofenceStates.Removed;
 
    // Specify how long the user must be in the geofence 
    // for the enter event to fire.
    TimeSpan dwellTime;
 
    if ("" != DwellTime.Text)
    {
        dwellTime = new TimeSpan(ParseTimeSpan(DwellTime.Text, defaultDwellTimeSeconds));
    }
    else
    {
        dwellTime = new TimeSpan(ParseTimeSpan("0", defaultDwellTimeSeconds));
    }
 
    // Specify how long the geofence is to be active.
    TimeSpan duration;
 
    if ("" != Duration.Text)
    {
        duration = new TimeSpan(ParseTimeSpan(Duration.Text, 0));
    }
    else
    {
        duration = new TimeSpan(ParseTimeSpan("0", 0));
    }
 
    // Specify the start time of the geofence.
    DateTimeOffset startTime;
 
    if ("" != StartTime.Text)
    {
        startTime = DateTimeOffset.Parse(StartTime.Text); 
    }
    else
    {
        // If you don't set the start time in C#, it defaults to 1/1/1601.
        calendar.SetToNow();
 
        startTime = calendar.GetDateTime();
    }
 
    geofence = new Geofence(fenceKey, geocircle, mask, singleUse, dwellTime, startTime, duration);
    GeofenceMonitor.Current.Geofences.Add(geofence);
 

 

JavaScript

function CreateGeofence() {
    var geofence = null;
    decimalFormatter = new Windows.Globalization.NumberFormatting.DecimalFormatter();
 
    try {
        var fenceKey = nameElement.value;
 
        var position = {
            latitude: decimalFormatter.parseDouble(latitude.value),
            longitude: decimalFormatter.parseDouble(longitude.value),
            altitude: 0
        };
        var radiusValue = decimalFormatter.parseDouble(radius.value);
 
        // The geofence is a circular region.
        var geocircle = new Windows.Devices.Geolocation.Geocircle(position, radiusValue);
 
        var singleUse = false;
 
        // Set up listening for enter geofence, exit geofence, and remove geofence events.
        var mask = 0;
 
        mask = mask | Windows.Devices.Geolocation.Geofencing.MonitoredGeofenceStates.entered;
        mask = mask | Windows.Devices.Geolocation.Geofencing.MonitoredGeofenceStates.exited;
        mask = mask | Windows.Devices.Geolocation.Geofencing.MonitoredGeofenceStates.removed;
 
    // Specify how long the user must be in the geofence 
    // for the enter event to fire.
        var dwellTimeSpan = new Number(parseTimeSpan(dwellTimeField, defaultDwellTimeSeconds));
 
    // Specify how long the geofence is to be active.
        var durationTimeSpan = null;
        if (durationField.value.length) {
            durationTimeSpan = new Number(parseTimeSpan(durationField, 0));
        } else {
            durationTimeSpan = new Number(0); // Duration must be set because start time is set below.
        }
 
    // Specify the start time of the geofence.
        var startDateTime = null;
        if (startTimeField.value.length) {
            startDateTime = new Date(startTimeField.value);
        } else {
     // If you don't set start time in JavaScript, it defaults to 1/1/1601.
            startDateTime = new Date(); 
        }
 
        geofence = new Windows.Devices.Geolocation.Geofencing.Geofence(fenceKey, geocircle, mask, singleUse, dwellTimeSpan, startDateTime, durationTimeSpan);
    } catch (ex) {
        WinJS.log && WinJS.log(ex.toString(), "sample", "error");
    }
 
    return geofence;
}

 

Handling the geofencing notifications

Your app can receive notifications from your geofence either when it’s running in the foreground or by setting up a background task that sends an alert when the event occurs. Although you could technically set up both, we do not recommend it because there is no way to guarantee which handler would be called first. Generally, a background task is the best way to get the notifications. Even if your app is running in the foreground, you will still receive the event and you can take the appropriate action. This section shows how to use a background task. For more info about handling geofence events in the foreground, see Handling geofence notifications in the foreground.

To set up your background task, you need to register the background task in your app and also prompt the user to take some action. Here’s how:

  1. Declare the background task in your app’s manifest. Add a declaration of type Background Tasks, set the task type to Location, and then set the entry point in your app that is to be called when the event is triggered.

geofencing-2
Declaring Location Background Task type

  1. Register the background task in your app. We’ll discuss the code for this in just a minute.
  2. When your app first registers the background task, Windows displays a prompt to the user to add your app to the lock screen. After the user makes a choice, Windows remembers that choice for your app so the prompt does not appear the next time. The user can always change that choice by going to PC Settings and changing the list of lock-screen apps.

geofencing-3
Lock screen prompt

  1. For your background task to access geofencing, your app must have location permissions. Otherwise, your background task will not be triggered. It’s important to do this while your app is still running in the foreground, because background tasks cannot display prompts.

Here’s example code that registers the geofencing background task. The OnCompleted method will be called when the event is triggered.

C#

async private void RegisterBackgroundTask(object sender, RoutedEventArgs e)
{
    // Get permission for a background task from the user. 
    // If the user has already answered once, this does nothing and 
    // the user must manually update their preference via PC Settings.
    BackgroundAccessStatus backgroundAccessStatus = await BackgroundExecutionManager.RequestAccessAsync();
 
    // Regardless of the answer, register the background task. 
    // If the user later adds this app to the lock screen, 
    // the background task will be ready to run.
    // Create a new background task builder.
    BackgroundTaskBuilder geofenceTaskBuilder = new BackgroundTaskBuilder();
 
    geofenceTaskBuilder.Name = SampleBackgroundTaskName;
    geofenceTaskBuilder.TaskEntryPoint = SampleBackgroundTaskEntryPoint;
 
    // Create a new location trigger.
    var trigger = new LocationTrigger(LocationTriggerType.Geofence);
 
    // Associate the location trigger with the background task builder.
    geofenceTaskBuilder.SetTrigger(trigger);
 
    // If it is important that the user be present and/or
    // that there be an Internet connection when OnCompleted is called,
    // the following could be called before calling Register():
    // SystemCondition condition = new. SystemCondition(SystemConditionType.UserPresent | SystemConditionType.InternetAvailable);
    // geofenceTaskBuilder.AddCondition(condition);
 
    // Register the background task.
    geofenceTask = geofenceTaskBuilder.Register();
 
    // Associate an event handler with the new background task.
    geofenceTask.Completed += new BackgroundTaskCompletedEventHandler(OnCompleted);
 
    BackgroundTaskState.RegisterBackgroundTask(BackgroundTaskState.LocationTriggerBackgroundTaskName);
 
    switch (backgroundAccessStatus)
    {
    case BackgroundAccessStatus.Unspecified:
    case BackgroundAccessStatus.Denied:
        rootPage.NotifyUser("This app must be added to the lock screen before the background task will run.", NotifyType.ErrorMessage);
        break;
 
    }
}

JavaScript

function registerBackgroundTask() {
 
        var geofenceTask;
 
    // Get permission for a background task from the user. 
    // If the user has already answered once, this does nothing and 
    // the user must manually update their preference via PC Settings.
        Background.BackgroundExecutionManager.requestAccessAsync().done(
            function (backgroundAccessStatus) {
                var builder =  new Windows.ApplicationModel.Background.BackgroundTaskBuilder();
 
       // Regardless of the answer, register the background task. 
       // If the user later adds this app to the lock screen, 
       // the background task will be ready to run.
       // Create a new background task builder.
                builder.name = sampleBackgroundTaskName;
                builder.taskEntryPoint = sampleBackgroundTaskEntryPoint;
                builder.setTrigger(new Windows.ApplicationModel.Background.LocationTrigger(Windows.ApplicationModel.Background.LocationTriggerType.geofence));
 
      // If it is important that there the user be present and/or
      // that there be an Internet connection when OnCompleted is called,
      // the following could be called before Register().
      // var condition = new SystemCondition(SystemConditionType.userPresent | SystemConditionType.internetAvailable);
      // builder.addCondition(condition);
 
       // Register the background task.
                geofenceTask = builder.register();
 
       // Associate an event handler with the new background task.
                geofenceTask.addEventListener("completed", onCompleted);
 
                LocationTriggerBackgroundTask.updateButtonStates(/*registered:*/ true);
 
                switch (backgroundAccessStatus) {
                    case Background.BackgroundAccessStatus.unspecified:
                    case Background.BackgroundAccessStatus.denied:
                        WinJS.log && WinJS.log("This app must be added to the lock screen before the background task will run.", "sample", "status");
                        break;
 
                    default:
          // Finish by getting an initial position. This causes the location-consent dialog
          // to be displayed if it's the first attempt by this app to access location.
                        getGeopositionAsync();
                        break;
                }
            },
            function (e) {
                // Did you forget to delcare the background task in the package manifest?
                WinJS.log && WinJS.log(e.toString(), "sample", "error");
            }
        );
    }

The next step is to add the code to handle the background event. Remember to check the time stamp and current location in the OnCompleted method, because the event may have occurred in the past and the user may have changed location since the event occurred. This condition might be due to the device having been in connected standby or to the user doing other work on the device.

Here’s example code that contains the framework for your OnCompleted method. The actions you take will depend on the functionality of your app. Examples include popping a toast to alert the user or performing a web call such as a check-in. In some way, you will notify the user that a geofencing event has occurred, or you will take some action based on the geofencing event.

C#

async private void OnCompleted(IBackgroundTaskRegistration sender, BackgroundTaskCompletedEventArgs e)
{
    if (sender != null)
    {
        // Update the UI with progress reported by the background task.
        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            try
            {
                // If the background task threw an exception, display
                // the exception in the error text box.
                e.CheckResult();
 
                // Update the UI with the completion status of the 
                // background task.
                // The Run method of the background task sets the 
                // LocalSettings. 
                var settings = ApplicationData.Current.LocalSettings;
 
                // Get status.
                if (settings.Values.ContainsKey("Status"))
                {
                    rootPage.NotifyUser(settings.Values["Status"].ToString(), NotifyType.StatusMessage);
                }
 
                // Do your app’s work here.
 
            }
            catch (Exception ex)
            {
                // The background task had an error.
                rootPage.NotifyUser(ex.ToString(), NotifyType.ErrorMessage);
            }
        });
    }
}

JavaScript

// Handle background-task completion.
function onCompleted() {
    try {
 // Update the UI with the completion status of the background task.
 // The Run method of the background task sets the LocalSettings. 
        var settings = Windows.Storage.ApplicationData.current.localSettings;
 
        // Get the status.
        if (settings.values.hasKey("Status")) {
            WinJS.log && WinJS.log(settings.values["Status"].toString(), "sample", "status");
        }
 
 // Do your app’s work here.
 
    } catch (ex) {
    // The background task had an error.
        WinJS.log && WinJS.log(ex.toString(), "sample", "error");
    }
}

Wrapping up

Geofencing is a powerful way to make your app location-aware. It’s battery-efficient, too: instead of each app using power individually to wake up and poll for location info, the system can handle this for all apps on the device and wake up only the right app at the right time. Geofencing optimizations made in the operating system benefit all apps that use the geofencing API.

With geofencing you can create smart, contextual apps that are aware of where users are and where they’re going. There are many possibilities; let your imagination go wild and take advantage of this fun and engaging new feature.

To learn more about geofencing, see:

And for help with testing your geofencing app:

-- Ross Heise, Senior Content Developer, Windows Developer Content

Special thanks to Jon Kay, Janet Schneider, Frank Gorgenyi, Lisa Ong, Mark Inderhees, Andrew Dye, and Kevin Paulson for their help and contributions to this post.

1 Comments
You must be logged in to comment. Sign in or Join Now
  • jschroedl
    24 Posts

    Can you provide an example of Desktop code to do the same?