Tuesday, May 01, 2012

Windows Phone Location-aware applications: a tip to help you keep track of yourself

handling location events in your application is in no way rocket science, in fact it’s about as easy as it can be.

I’m using the same approach, in fact even the same code, In all my apps (the ones that are location-aware that is) and I thought I’d share it with you. It’s basically some members, event handlers and a background method for initializing positioning.

1) some members used in the viewmodel (or even better, a base viewmodel)..

        public virtual GeoCoordinate CurrentLocation
        {
            get
            {
                if (IsInDesignMode)
                {
                    return new GeoCoordinate(59.36106, 17.87462);
                }
                return GeoHelper.Watcher.Position.Location;
            }
        }

        private string progressText;
        public bool HasWiredPositioningEvents { get; set; }
        public bool PreventLoading { get; set; }
        public bool PositioningStatusOk { get; set; }
        private IDisposable locationSubscriber = null;

        public delegate void OnPositioningReady();
        public event OnPositioningReady PositioningReady;

2) a WireUpPositioning method that takes care of most of the location details..

        public virtual void WireUpPositioning()
        {
            if (PreventLoading)
                return;

            if (Settings.PositioningAllowed)
            {
                if (!HasWiredPositioningEvents)
                {
                    HasWiredPositioningEvents = true;
                    GeoHelper.Watcher.StatusChanged += new System.EventHandler<GeoPositionStatusChangedEventArgs>(watcher_StatusChanged);
                    GeoHelper.Watcher.PositionChanged += new System.EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>>(watcher_PositionChanged);

                    ProgressText = "Determining your position..";

                    // background work - don't want to halt the UI while waiting..
                    ThreadPool.QueueUserWorkItem((o) =>

                    locationSubscriber = Observable.Return(GeoHelper.Start())
                        .Subscribe(x =>
                        {
                            if (!x)
                            {
                                // Start() failed, usually caused by positioning turned off..
                            }
                            else
                            {
                                SmartDispatcher.BeginInvoke(() =>
                                    {
                                        PositioningStatusOk = true;
                                        if (PositioningReady != null)
                                        {
                                            PositioningReady();
                                        }
                                    });
                            }
                        }
                    , ex =>
                    {
                        // exception caught..
                        ProgressText = null;
                    }
                    , (() => ProgressText = null)));
                }
            }
            else
            {
                HasWiredPositioningEvents = false;
                GeoHelper.Watcher.StatusChanged -= watcher_StatusChanged;
                GeoHelper.Watcher.PositionChanged -= watcher_PositionChanged;
                GeoHelper.Stop();
            }
        }

3) simple event handlers to travel the notification all the way up..

        void watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
        {
            SmartDispatcher.BeginInvoke(() =>
            {
                this.RaisePropertyChanged("CurrentLocation");
            });
        }

        void watcher_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
        {
            if (e.Status == GeoPositionStatus.Ready)
            {
                ProgressText = null;
                PositioningStatusOk = true;
            }
            else
            {
                PositioningStatusOk = false;
                if (e.Status == GeoPositionStatus.Initializing)
                {
                    ProgressText = "Initializing positioning..";
                }
                if (e.Status == GeoPositionStatus.Disabled)
                {
                    // handle positioning turned off / disabled if you need to..
                    ProgressText = null;
                }
                if (e.Status == GeoPositionStatus.NoData)
                {
                    ProgressText = "Positioning unavailable..";
                }
            }
            SmartDispatcher.BeginInvoke(() => 
                {
                    RaisePropertyChanged("PositioningStatusOk");
                    RaisePropertyChanged("CurrentLocation");
                }
                );
        }

4) the initializer I use is from a simple static helper class called GeoHelper from my Extensions library that holds a GeoCoordinateWatcher and methods to start/stop it.

    //
    // Copyright (c) 2012 Peter Drougge
    //
    // Licensed under the Apache License, Version 2.0 (the "License");
    // you may not use this file except in compliance with the License.
    // You may obtain a copy of the License at
    //
    //    http://www.apache.org/licenses/LICENSE-2.0
    //
    // Unless required by applicable law or agreed to in writing, software
    // distributed under the License is distributed on an "AS IS" BASIS,
    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    // See the License for the specific language governing permissions and
    // limitations under the License.
    //
    public static class GeoHelper
        {
            private static GeoCoordinateWatcher watcher;
            public static GeoCoordinateWatcher Watcher 
            { 
                get
                {
                    if (watcher == null)
                    {
                        watcher = new GeoCoordinateWatcher( GeoPositionAccuracy.High);
                    }
                    return watcher;
                }
            }


            public static bool IsCoordinatesValid(double latitude, double longitude)
            {
                if (latitude!=null && longitude!=null)
                {
                    try
                    {
                        GeoCoordinate gc = new GeoCoordinate(latitude, longitude);
                        return true;
                    }
                    catch (Exception)
                    {
                        return false;
                    }
                }
                return false;
            }

            public static bool Start()
            {
                if (watcher.Status!=GeoPositionStatus.Initializing && watcher.Status!=GeoPositionStatus.Ready)
                {
                    return watcher.TryStart(false, new TimeSpan(0,0,30));
                }
                return true;
            }
            public static void Stop()
            {
                watcher.Stop();
            }
        }

that’s it. All you now have to do is place a call to WireUpPositioning() somewhere appropriate in the start of your app.

No comments: