Android/Xamarin GPS Location Tracking

This short post is about the easiest way to get GPS Location Tracking with your Android Phone,  Xamarin and C#.

Imagine you have a navigation/tracking application and you need to get the GPS position of the device with real-time accuracy (and not periodically, each 5-15 minutes, like Google Location History does).

In this case you may find useful the following class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Locations;

namespace LocationTrackerTest
{
    /// <summary>
    /// Class provides GPS location.
    /// Note: Requires permission ACCESS_FINE_LOCATION.
    /// Usage: 
    ///   1. Create the class instance. 
    ///   2. Call InitializeLocationManager(). 
    ///   3. Check "CurrentLocation" when you wish or 
    ///      subscribe to "LocationChanged" event to be notified.
    ///   4. When you do not need it any more - call Dispose().
    /// </summary>
    public class GPSLocationTracker : Java.Lang.Object, ILocationListener, IDisposable
    {
        /// <summary>
        /// Current location
        /// </summary>
        public Location CurrentLocation { get; set; }

        /// <summary>
        /// Current location (if available) or status message if not available
        /// </summary>
        public string CurrentLocationString { get; set; }

        public Availability CurrentGPSProviderStatus = Availability.Available;

        /// <summary>
        /// Tracker Status - true if ok, false if no GPS location
        /// </summary>
        public bool IsGettingLocation { get; set; }

        /// <summary>
        /// True if GPS provider is enabled in settings, otherwise false
        /// </summary>
        public bool IsGPSProviderEnabled { get; set; }

        // Event handlers
        public event EventHandler<Location> LocationChanged;
        public event EventHandler GPSProviderDisabled;
        public event EventHandler GPSProviderEnabled;
        public event EventHandler<Availability> GPSStatusChanged;

        public LocationManager _locationManager = null;
        Context _context;
        bool _isGiveToastsOnStatusChanges;
        bool _isFirstLocationReported = false;

        // The minimum distance to change Updates in meters
        long _minDistanceChangeForUpdatesMeters = 10; // 10 meters

        // The minimum time between updates in milliseconds
        long _minTimeBetweenUpdatesMs = 1000 * 30; // 30 seconds

        public GPSLocationTracker(Context context, bool giveToastsOnStatusChanges = true, long minDistanceChangeForUpdatesMeters = 10, long minTimeBetweenUpdatesMs = 30000)
        {
            this._context = context;
            this._isGiveToastsOnStatusChanges = giveToastsOnStatusChanges;
            _minDistanceChangeForUpdatesMeters = minDistanceChangeForUpdatesMeters;
            _minTimeBetweenUpdatesMs = minTimeBetweenUpdatesMs;
            IsGettingLocation = false;
            IsGPSProviderEnabled = false;
        }

        /// <summary>
        /// Initialize location services and request getting location updates and events.
        /// Run this methid after construction of the class instance.
        /// Run it in the UI thread if you wish the GPS settings alert to be automatically displayed.
        /// </summary>
        public void InitializeLocationManager(bool isShowGPSSettingsAlert = true)
        {
            // Get location manager
            _locationManager = (LocationManager)_context.GetSystemService(Context.LocationService);

            // Check if GPS is enabled, If not - display alert with shortcut to GPS settings
            IsGPSProviderEnabled = _locationManager.IsProviderEnabled(LocationManager.GpsProvider);
            if (IsGPSProviderEnabled)
                CurrentGPSProviderStatus = Availability.Available;
            else
                CurrentGPSProviderStatus = Availability.OutOfService;
            if (!IsGPSProviderEnabled && isShowGPSSettingsAlert)
                ShowGPSSettingsAlert();

            // Subscibe to getting location updates with the desired treshold
            _locationManager.RequestLocationUpdates(LocationManager.GpsProvider, _minTimeBetweenUpdatesMs, _minDistanceChangeForUpdatesMeters, this);
            CurrentLocation = _locationManager.GetLastKnownLocation(LocationManager.GpsProvider);
        }

        /// <summary>
        /// Display GPS disabled alert with shortcut to GPS settings
        /// </summary>
        public void ShowGPSSettingsAlert()
        {
            // See http://stacktips.com/tutorials/xamarin/alertdialog-and-dialogfragment-example-in-xamarin-android

            AlertDialog.Builder alertDialog = new AlertDialog.Builder(_context);

            // Setting Dialog Title
            alertDialog.SetTitle("GPS settings");

            // Setting Dialog Message
            alertDialog.SetMessage("GPS is not enabled. Do you want to go to settings menu?");

            // On pressing Settings button
            alertDialog.SetPositiveButton("Settings", (senderAlert, args) => {
                Intent intent = new Intent(Android.Provider.Settings.ActionLocationSourceSettings);
                _context.StartActivity(intent);
            });

            // On pressing cancel button
            alertDialog.SetNegativeButton("Cancel", (senderAlert, args) => { });

            // Showing Alert Message (note - do it only on UI thread, or use Activity.RunOnUiThread method)
            Dialog dialog = alertDialog.Create();
            dialog.Show();
        }


        /// <summary>
        /// LocationListener will call this method when updates come from LocationManager
        /// </summary>
        /// <param name="location"></param>
        public void OnLocationChanged(Location location)
        {
            if (location == null)
            {
                CurrentLocationString = "No location";
                _isFirstLocationReported = false;
                IsGettingLocation = false;
                if (_isFirstLocationReported && _isGiveToastsOnStatusChanges)
                {   // if after we had location, we lost it and got null
                    Toast.MakeText(_context, String.Format("Location update: null", CurrentLocation.Latitude, CurrentLocation.Longitude), ToastLength.Short).Show();
                }
            }
            else
            {
                CurrentLocation = location;
                IsGettingLocation = true;
                CurrentLocationString = String.Format("{0}:{1}", CurrentLocation.Latitude, CurrentLocation.Longitude);
                if (!_isFirstLocationReported && _isGiveToastsOnStatusChanges)
                {
                    Toast.MakeText(_context, String.Format("Location update: {0}:{1}", CurrentLocation.Latitude, CurrentLocation.Longitude), ToastLength.Short).Show();
                    _isFirstLocationReported = true;
                }
            }
            // Fire event
            if (LocationChanged != null)
                LocationChanged(this, location);
        }

        public void OnProviderDisabled(string provider)
        {
            if (provider == LocationManager.GpsProvider)
            {
                if (_isGiveToastsOnStatusChanges)
                    Toast.MakeText(_context, "GPS provider disabled.", ToastLength.Short).Show();
                CurrentLocationString = "GPS provider disabled.";
                IsGettingLocation = false;
                _isFirstLocationReported = false;
                IsGPSProviderEnabled = false;
                // Fire event
                if (GPSProviderDisabled != null)
                    GPSProviderDisabled(this, null);
            }
        }

        public void OnProviderEnabled(string provider)
        {
            if (provider == LocationManager.GpsProvider)
            {
                if (_isGiveToastsOnStatusChanges)
                    Toast.MakeText(_context, "GPS provider enabled", ToastLength.Short).Show();
                CurrentLocationString = "GPS provider enabled.";
                _isFirstLocationReported = false;
                IsGPSProviderEnabled = false;
                // Fire event
                if (GPSProviderEnabled != null)
                    GPSProviderEnabled(this, null);
            }
        }

        public void OnStatusChanged(string provider, Availability status, Bundle extras)
        {
            if (provider == LocationManager.GpsProvider)
            {
                if (CurrentGPSProviderStatus != status)
                {
                    if (_isGiveToastsOnStatusChanges)
                        Toast.MakeText(_context, "GPS Status changed: " + status.ToString(), ToastLength.Short).Show();
                    CurrentLocationString = "GPS Status " + status.ToString();
                    CurrentGPSProviderStatus = status;
                    if (status != Availability.Available)
                    {
                        IsGettingLocation = false;
                        _isFirstLocationReported = false;
                    }
                    // Fire event
                    if (GPSStatusChanged != null)
                        GPSStatusChanged(this, status);
                }
            }
        }


        /// <summary>
        /// Stop location services and unsubscribe from getting events
        /// </summary>
        public void StopLocationManager()
        {
            if (_locationManager != null)
            {
                _locationManager.RemoveUpdates(this);
            }
        }

        public void Dispose()
        {
            StopLocationManager();
        }
    }
}

This class initializes the LocationManager, Checks if the GPS data provider is enabled (if not, it optionally displays a message box with shortcut to GPS settings), and starts listening to location updates with given threshold in meters and timeout.

To illustrate the usage of this class, I’ve created a sample and simple application which displays the GPS position from GPSLocationTracker class on the screen:

using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;

namespace LocationTrackerTest
{
    [Activity(Label = "LocationTrackerTest", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        GPSLocationTracker gpsLocationTracker = null;

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);

            // Create and initialize the GPSLocationTracker,
            // subscribe to location changed event
            gpsLocationTracker = new GPSLocationTracker(this, true);
            gpsLocationTracker.InitializeLocationManager(true);
            gpsLocationTracker.LocationChanged += GpsLocationTracker_LocationChanged;
        }

        /// <summary>
        /// gpsLocationTracker.LocationChanged event handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void GpsLocationTracker_LocationChanged(object sender, Android.Locations.Location e)
        {
            // Just show current location in a text field
            TextView tv = FindViewById<TextView>(Resource.Id.txtLocation);
            tv.Text = gpsLocationTracker.CurrentLocationString;
        }
    }
}

There are just 3 lines of code required to initialize this class and subscribe to LocationChanged event. Then you need to create your LocationChanged handler and there perform all the actions you need – like update the position on the map, send the device location update to the server etc.

Note that you should add ACCESS_FINE_LOCATION permission to your application manifest:

androidmanifest

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s