Visually Located

XAML and GIS

Using the Geolocator API in your Phone 8 AND Phone 7 apps!

If you’ve done any work with location in Windows Phone you probably know that the a new location API was added at Windows Phone 8. If you want to upgrade your app to take advantage of functionality in Window Phone 8 you have a few options.

  1. Continue to use the original GeoCoordinateWatcher API from Windows Phone 7 in your Phone 8 and Phone 7 app
  2. Upgrade your app to Windows Phone 8 and use the new API in your Phone 8 app, while the Phone 7 app uses the original API.
  3. Upgrade your app to Windows Phone 8 and use the new API in your Phone 8 app AND your Phone 7 app!

These options assume you want to continue to support Windows Phone 7. Please do not forget that there are a lot of people with a Phone 7.

If you want to do options one or two you can stop reading now. If you want to do option three then you are in the right place. I’m not a big fan of supporting two different APIs in my apps and I don’t like using “out dated” APIs either. What we will need to do is create an API that duplicates the Windows.Devices.Geolocation API but uses the old System.Device.Location API. This will allow a file that uses location to be used with a Windows Phone 7 app AND a Phone 8 app!

I won’t get into the differences between the APIs as this upgrade link does a pretty good job. Most of the old API maps pretty easily to the new API. The bulk of the work is in the Geolocator class itself.

We’ll start with just a stub of the class.

namespace Windows.Devices.Geolocation
{
    public class Geolocator
    {
        public event TypedEventHandler<Geolocator, PositionChangedEventArgs> PositionChanged;
 
        public event TypedEventHandler<Geolocator, StatusChangedEventArgs> StatusChanged;
 
        public PositionStatus LocationStatus { get; }
 
        public Task<Geoposition> GetGeopositionAsync() {        }
 
        public double DesiredAccuracyInMeters { get; set; }
 
        public double MovementThreshold { get; set; }
 
        public PositionAccuracy DesiredAccuracy { get; set; }
 
        /// <summary>
        /// Not implemented
        /// </summary>
        public int ReportInterval { get; set; }
    }
}

Notice that ReportInterval is not implemented. This is not something I wanted to implement, not nothing is stopping you from doing that!
Also notice that I did not even

Most of the properties are implemented in terms of the original API.

private static bool _disabled;
 
private PositionStatus _status = PositionStatus.NoData;
private GeoCoordinateWatcher _watcher;
 
public Geolocator()
{
    _watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.Default) { MovementThreshold = 10 };
}
 
public event TypedEventHandler<Geolocator, StatusChangedEventArgs> StatusChanged;
 
public PositionStatus LocationStatus
{
    get
    {
        if (_disabled) return PositionStatus.Disabled;
        return _status;
    }
}
 
public double DesiredAccuracyInMeters
{
    get { return _watcher.MovementThreshold; }
    set { _watcher.MovementThreshold = value; }
}

I did not implement DesiredAccuracy or MovementThreshold in this stub because it’s a little more involved to maintain functionality. I’ll get into that more later.

Notice the static disabled property. A nice thing about the Geolocator is that it always knows if the location service for the phone itself is turned off. The GeoCoordinateWatcher only knows about it being disabled after it has already started. So to make life a little easier, this Geolocator will attempt to do the same.

The biggest different between the two APIs is the removal of the Start and Stop methods from the GeoCoordinateWatcher class. Start and Stop were replaced by simply subscribing to the PositionChanged event. Because everything happens when (un)subscribing from the event, we cannot use standard event stubs and must implement the event ourselves.

private TypedEventHandler<Geolocator, PositionChangedEventArgs> _positionChangedDelegate;
private readonly object _padLock = new object();
 
public event TypedEventHandler<Geolocator, PositionChangedEventArgs> PositionChanged
{
    add
    {
        lock (_padLock)
        {
            if (_positionChangedDelegate == null)
            {
                // if this is the first subscription to the PositionChanged event
                // then subscribe to the GeoCoordinateWatcher events and start
                // tracking location.
                _watcher.PositionChanged += OnPositionChanged;
                _watcher.StatusChanged += OnStatusChanged;
                _watcher.Start();
            }
            _positionChangedDelegate += value;
        }
    }
    remove
    {
        lock (_padLock)
        {
            _positionChangedDelegate -= value;
            if (_positionChangedDelegate == null)
            {
                // Last person to unsubscribe from this event
                // Stop the GeoCoordinateWatcher and unsubscribe from the events.
                _watcher.Stop();
                _watcher.PositionChanged -= OnPositionChanged;
                _watcher.StatusChanged -= OnStatusChanged;
            }
        }
    }
}

When the first subscription to the event comes in, we’ll subscribe to the GeoCoordinateWatcher events and Start tracking location. When the last subscription is removed, we unsubscribe from the GeoCoordinateWatcher events and stop tracking location. When the position or status changes from the GeoCoordinateWatcher, we’ll fire off our own PositionChanged or StatusChanged events.

private void OnStatusChanged(object sender, GeoPositionStatusChangedEventArgs args)
{
    var handler = StatusChanged;
    if (handler != null)
    {
        _status = args.Status.ToPositionStatus();
        _disabled = _status == PositionStatus.Disabled;
        var changedEventArgs = new StatusChangedEventArgs(_status);
        handler(this, changedEventArgs);
    }
}
 
private void OnPositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> args)
{
    var handler = _positionChangedDelegate;
    if (handler != null)
    {
        var geoposition = new Geoposition(args.Position);
        handler(this, new PositionChangedEventArgs(geoposition));
    }
}

We have now replaced the Start and Stop functionality from the GeoCoordinateWatcher API with the PositionChanged event. The next biggie are the GeoPositionAsync methods. I choose to only implement the default method as the first parameter from the second method (maximumAge) does not lend itself well to Windows Phone 7. The maximumAge parameter specifies “the maximum acceptable age of cached location data.” While we could cache location data, this is not the same as what is done in Windows Phone 8. In Phone 8, the system itself will cache location data. The GeoPositionAsync method performs it’s asynchronous operations via the IAsyncOperation<T> interface. This interface is not available in Windows Phone 7, but thanks to the Microsoft.Bcl library we do have the Task class which allows us to have methods that can be awaited.

public Task<Geoposition> GetGeopositionAsync()
{
    var completion = new TaskCompletionSource<Geoposition>();
 
    var locator = new Geolocator()
        {
            DesiredAccuracyInMeters = DesiredAccuracyInMeters,
            MovementThreshold = MovementThreshold,
            ReportInterval = ReportInterval
        };
 
    TypedEventHandler<Geolocator, PositionChangedEventArgs> positionChangedHandler = null;
    TypedEventHandler<Geolocator, StatusChangedEventArgs> statusChangedHandler = null;
 
    positionChangedHandler = (s, e) =>
        {
            locator.PositionChanged -= positionChangedHandler;
            locator.StatusChanged -= statusChangedHandler;
            completion.SetResult(e.Position);
        };
 
    statusChangedHandler = (sender, args) =>
        {
            if (args.Status == PositionStatus.Disabled)
            {
                // unsubscribe as we will not get any data
                locator.PositionChanged -= positionChangedHandler;
                locator.StatusChanged -= statusChangedHandler;
                completion.SetResult(null);
            }
        };
 
    locator.PositionChanged += positionChangedHandler;
    locator.StatusChanged += statusChangedHandler;
 
    return completion.Task;
}

The GeoPositionAsync method is meant to be a one time call method. This method initializes a new Geolocator object so we are able to take advantage of the new API we just wrote! The method uses the TaskCompletionSource class which gives us the power to perform this method in an async way. When GetPositionAsync is called, we need to subscribe to both the PositionChanged and the StatusChanged events. If the StatusChanged event comes back with location being Disabled, the PositionChanged event will never fire with a null location.

I mentioned earlier that DesiredAccuracy and MovementThreashold were not stubbed out because they required a little more. In Windows Phone 8, you are not allowed to change these properties (along with ReportInterval) while you are getting location. To maintain this functionality, we must check to see if anyone has subscribed to the PositionChanged event. If they have, throw an exception. In windows Phone 8, it throws the following

System.Exception was unhandled by user code
  HResult=-2147467260
  Message=Operation aborted (Exception from HRESULT: 0x80004004 (E_ABORT))

It throws this exception because the Geolocator is native C++. We’re in .Net so we can throw a better exception.

public double MovementThreshold
{
    get { return _watcher.MovementThreshold; }
    set
    {
        if (_positionChangedDelegate != null)
            throw new NotSupportedException("Cannot change the MovementThreshold while getting location.");
 
        _watcher.MovementThreshold = value;
    }
}
 
public PositionAccuracy DesiredAccuracy
{
    get { return (PositionAccuracy)_watcher.DesiredAccuracy; }
    set
    {
        if (_positionChangedDelegate != null)
            throw new NotSupportedException("Cannot change the DesiredAccuracy while getting location.");
 
        double movementThreshold = MovementThreshold;
        // We cannot change the accuracy in a GeoCoodinateWatcher so we need to create a new one
        _watcher = new GeoCoordinateWatcher((GeoPositionAccuracy)value) 
            { MovementThreshold = movementThreshold };
    }
}

And we are done with the Geolocator class. The rest of the work is stubbing out the other classes. These are pretty much a one for one match from each API. To save you some time you can download the source from github.

If you are a fan of NuGet you can install the bridge:

PM> Install-Package WPLocationBridge

OH, and did I forget to mention that this API is the same used in Windows Store apps? This means that one code file can be used across ALL THREE platforms!

Avoid InvalidOperationException when navigating in your Windows Phone apps

I have no idea how I get this error, but in every app I’ve published I get error reports in which navigation fails and throws an InvalidOperationException. The entire stack trace is similar to

System.InvalidOperationException
Navigation is not allowed when the task is not in the foreground.
   at System.Windows.Navigation.NavigationService.Navigate(Uri source)
   at MyWPApp.MainPage.OnListBoxItemSelected(Object sender, SelectionChangedEventArgs e)
   at System.Windows.Controls.Primitives.Selector.OnSelectionChanged(SelectionChangedEventArgs e)
   at System.Windows.Controls.Primitives.Selector.InvokeSelectionChanged(List`1 unselectedItems, List`1 selectedItems)
   at System.Windows.Controls.Primitives.Selector.SelectionChanger.End()
   at System.Windows.Controls.Primitives.Selector.SelectionChanger.SelectJustThisItem(Int32 oldIndex, Int32 newIndex)
   at System.Windows.Controls.ListBox.MakeSingleSelection(Int32 index)
   at System.Windows.Controls.ListBox.HandleItemSelection(ListBoxItem item, Boolean isMouseSelection)
   at System.Windows.Controls.ListBox.OnListBoxItemClicked(ListBoxItem item)
   at System.Windows.Controls.ListBoxItem.OnManipulationCompleted(ManipulationCompletedEventArgs e)
   at System.Windows.Controls.Control.OnManipulationCompleted(Control ctrl, EventArgs e)

As you can see the error states “Navigation is not allowed when the task is not in the foreground” yet everything about this stack trace indicates that the event and call to Navigate IS in the foreground.

Instead of trying to figure out how or why this occurs, I decided to create a new NavigateToPage extension method that ignores this exception.

public static class Extensions
{
    public static bool NavigateToPage(this NavigationService source, string uri)
    {
        try
        {
            return source.Navigate(new Uri(uri, UriKind.Relative));
        }
        catch (InvalidOperationException)
        {
            return false;
        }
    }
}

I also took the liberty of not requiring a Uri as I hate having to add the UriKind every time. Now everywhere in my app I use the new NavigateToPage method.

NavigationService.NavigateToPage("/SecondPage.xaml");

Performing an async operation in the OnBackKeyPress method and still exit your Windows Phone apps

The OnBackKeyPress method allows you to perform special logic when the user presses the phone back button. You usually override this method when you do not want the user to actually go back a page. An example of this is if you show a popup, when the user presses the back button you should close the popup.

protected override void OnBackKeyPress(CancelEventArgs e)
{
    base.OnBackKeyPress(e);
 
    if (PopupIsOpen())
    {
        e.Cancel = true;
        ClosePopup();
    }
}

This workflow works great because everything is synchronous.

You may have seen some apps that prompt you with a MessageBox asking if you want to exit. If you say yes the app exits and if you say no, you do not exit the app.

protected override void OnBackKeyPress(CancelEventArgs e)
{
    base.OnBackKeyPress(e);
    var result = MessageBox.Show(
        "Are you sure you want to exit?", "Exit app?", MessageBoxButton.OKCancel);
    if(result == MessageBoxResult.Cancel)
    {
        e.Cancel = true;
    }
}

This also works because the built in MessageBox suspends code execution when the Show method is called. This does not work when you are need to perform an async operation. An example of this is when you create a ShowAsync method for the Windows Phone Toolkit CustomMessageBox. Another example is calling to a service to save some settings. You would want to let the user know if you were unable to save.

protected async override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
    base.OnBackKeyPress(e);
 
    Microsoft.Phone.Controls.CustomMessageBox msgBox = new CustomMessageBox();
    msgBox.RightButtonContent = "yes";
    msgBox.LeftButtonContent = "no";
    msgBox.Caption = "Are you sure you want to exit the app?";
    msgBox.Title = "Exit app?";
    var result = await msgBox.ShowAsync();
    
    // right button is cancel button
    if (result == CustomMessageBoxResult.RightButton)
    {
        e.Cancel = true;
    }
}

NOTE: I in no way indorse or support the use of message boxes when exiting the app. This is used merely as an example

When the CustomMessageBox is shown, the method is exited. This is the power of async/await. As soon as the async method happens, the method will exit, and as such, the app will exit. You will need to change your approach here. The first thing is to set e.Cancel to true before the async method call. This will stop the app from exiting, but as I mentioned you will be unable to set it back to true. You will have to manually exit the app. This is done in one of two ways, if you are using Windows Phone 7, you can reference the Microsoft.Xna.Framework.Game assembly and use the Exit method of the Game class.

new Microsoft.Xna.Framework.Game().Exit();

If you are using Windows Phone 8, you can use the new Terminate method  of the Application class.

Application.Current.Terminate();

Wrapping it all up your OnBackKeyPress method looks like

protected async override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
    base.OnBackKeyPress(e);
    e.Cancel = true;
 
    Microsoft.Phone.Controls.CustomMessageBox msgBox = new CustomMessageBox();
    msgBox.RightButtonContent = "yes";
    msgBox.LeftButtonContent = "no";
    msgBox.Caption = "Are you sure you want to exit the app?";
    msgBox.Title = "Exit app?";
    var result = await msgBox.ShowAsync();
    
    // left button is yes button
    if (result == CustomMessageBoxResult.LeftButton)
    {
        // if using WP7
        //new Microsoft.Xna.Framework.Game().Exit();
 
        //if using WP8
        Application.Current.Terminate();
    }
}

This approach works for any of your OnBackKeyPress methods, not just in the first page of your app. If you instead want to navigate backward, change that exits the app to:

NavigationService.GoBack();

Making the Windows Phone Toolkit CustomMessageBox async

The Windows Phone Toolkit has a nice CustomMessageBox control that allows you to customize the button text of a MessageBox. This message box is nice but you must subscribe to an event for when it is closed.

Microsoft.Phone.Controls.CustomMessageBox msgBox = new CustomMessageBox();
msgBox.RightButtonContent = "cancel";
msgBox.LeftButtonContent = "delete";
msgBox.Caption = "Are you sure you want to delete?";
msgBox.Title = "Delete message?";
msgBox.Dismissed += (o, eventArgs) =>
    {
        // Do some logic in here.
    };
msgBox.Show();

This requires that your logic for user input to be either in a separate method or above the Show method like in my example. If the CustomMessageBox had a way to show it asynchronously, you could have your logic in one place. Luckily this is very easy with an extension method.

public static class ToolkitExtensions
{
    public static Task<CustomMessageBoxResult> ShowAsync(this CustomMessageBox source)
    {
        var completion = new TaskCompletionSource<CustomMessageBoxResult>();
        
        // wire up the event that will be used as the result of this method
        EventHandler<DismissedEventArgs> dismissed = null;
        dismissed += (sender, args) =>
            {
                completion.SetResult(args.Result);
                
                // make sure we unsubscribe from this!
                source.Dismissed -= dismissed;
            };
 
        source.Show();
        return completion.Task;
    }
}

This new extension method allows your code to flow better.

Microsoft.Phone.Controls.CustomMessageBox msgBox = new CustomMessageBox();
msgBox.RightButtonContent = "cancel";
msgBox.LeftButtonContent = "delete";
msgBox.Caption = "Are you sure you want to delete?";
msgBox.Title = "Delete message?";
var result = await msgBox.ShowAsync();
// Do something with result

NOTE:  If you are developing a Windows Phone 7 application, you will need the async BCL from nuget