Xamarin.Android: Working With GPS And Locations

Couple of months ago I've started learning mobile and device development using Xamarin. First was creating a simple app that could sync data between wearable and handheld device and then working with another Android stuffs. So far it was fun but I struggled a bit because of the huge learning curve that I have to tackle.

I'm writing this article so anyone that might get interested in mobile development can also reference this if they need a simple working app that requires GPS and location features. In this particular example I'm going to demonstrate how to get the current location of the device and determine how many miles you are away based on the origin of the location provided.

Before you go any further make sure that you have the necessary requirements for your system and your development environment is properly configured. For setting up the development environment in Visual Studio please refer my previous article here:

For this example I'm going to use Visual Studio 2015 with the latest Xamarin version as of this writing.

Let's Get Started!

Now fire up Visual Studio 2015 and then create a new project by selecting File > New > Project. It should bring up the following dialog below:

blank app

From the dialog, under Templates select Visual C# > Android > Blank App (Android). Name your app to whatever you like but for the simplicity of this demo i just name it as "MyFirstGPSApp". Now click OK to generate the necessary files needed for our application. You should now be able to see the following screen:

MyFirstGPSApp

Before we proceed it's important to know what are the files generated and what their purpose are. So as a recap, here's the Anatomy of Xamarin.Android Application that is taken from the official documentation here.

Folder Purpose
References Contains the assemblies required to build and run the application. If we expand the References directory, we'll see references to .NET assemblies such as System, System.Core and System.Xml, as well as a reference to Xamarin's Mono.Android assembly.
Components The Components directory houses ready-made features from the Xamarin Components store, a public marketplace for Xamarin code. For more information on Xamarin Components, refer to the Xamarin Components walkthrough.
Assets Contains the files the application needs to run including fonts, local data files and text files. Files included here are accessible through the generated Assets class. For more information on Android Assets, see the Xamarin Using Android Assets guide.
Properties Contains the AndroidManifest.xml file that describes all the requirements for our Xamarin.Android application, including name, version number and permissions. The Properties folder also houses AssemblyInfo.cs, a .NET assembly metadata file. It is good practice to fill this file with some basic information about your application.
Resources Contains application resources such as strings, images and layouts. We can access these resources in code through the generated Resource class. The Android Resources guide provides more details about the Resources directory. The application template also includes a concise guide to Resources in the AboutResources.txt file.

Creating the Latitute and Longitude class

The very first thing to do is to create a class that houses the following properties:

  1. namespace MyFirstGPSApp  
  2. {  
  3.     public class LatLng  
  4.     {  
  5.         public double Latitude { getset; }  
  6.         public double Longitude { getset; }  
  7.         public LatLng(double lat, double lng)  
  8.         {  
  9.             this.Latitude = lat;  
  10.             this.Longitude = lng;  
  11.         }  
  12.     }  
  13. }  
The code above is pretty much simple and very straight forward. It's just a class that holds some simple properties without any logic on it.

Creating the Helper class

The next thing to do is to create a helper class that allow us to reuse common code. Here's how the helper class looks like:
  1. using System;  
  2.   
  3. namespace MyFirstGPSApp  
  4. {  
  5.     static class Utils  
  6.     {  
  7.         public enum DistanceUnit { Miles, Kilometers };  
  8.         public static double ToRadian(this double value)  
  9.         {  
  10.             return (Math.PI / 180) * value;  
  11.         }  
  12.         public static double HaversineDistance(LatLng coord1, LatLng coord2, DistanceUnit unit)  
  13.         {  
  14.             double R = (unit == DistanceUnit.Miles) ? 3960 : 6371;  
  15.             var lat = (coord2.Latitude - coord1.Latitude).ToRadian();  
  16.             var lng = (coord2.Longitude - coord1.Longitude).ToRadian();  
  17.   
  18.             var h1 = Math.Sin(lat / 2) * Math.Sin(lat / 2) +  
  19.                      Math.Cos(coord1.Latitude.ToRadian()) * Math.Cos(coord2.Latitude.ToRadian()) *  
  20.                      Math.Sin(lng / 2) * Math.Sin(lng / 2);  
  21.   
  22.             var h2 = 2 * Math.Asin(Math.Min(1, Math.Sqrt(h1)));  
  23.   
  24.             return R * h2;  
  25.         }  
  26.     }  
  27. }  
The ToRadian() method is an extension method that converts a double value to radian. The HaversineDistance() is a method that gets the distance in radius based on two given coordinate points.

Creating the Location Service

There are two possible options that I know of on implementing a Location service feature in your Android app. The simplest option is to implement the code directly in your Main Activity by implementing the ILocationListener. The other option is to create a Service that implements ILocationListener. I chose the service implementation option to make our code more flexible and reusable in case other app will need it.

Below is the code block for the Location Service:
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5.   
  6. using Android.App;  
  7. using Android.Content;  
  8. using Android.OS;  
  9. using Android.Locations;  
  10.   
  11. namespace MyFirstGPSApp  
  12. {  
  13.   
  14.     [Service]  
  15.     public class GPSService : Service, ILocationListener  
  16.     {  
  17.         private const string _sourceAddress = "TGU Tower, Cebu IT Park, Jose Maria del Mar St,Lahug, Cebu City, 6000 Cebu";  
  18.         private string _location = string.Empty;  
  19.         private string _address = string.Empty;  
  20.         private string _remarks = string.Empty;  
  21.   
  22.         public const string LOCATION_UPDATE_ACTION = "LOCATION_UPDATED";  
  23.         private Location _currentLocation;  
  24.         IBinder _binder;  
  25.         protected LocationManager _locationManager = (LocationManager)Android.App.Application.Context.GetSystemService(LocationService);  
  26.         public override IBinder OnBind(Intent intent)  
  27.         {  
  28.             _binder = new GPSServiceBinder(this);  
  29.             return _binder;  
  30.         }  
  31.   
  32.         public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)  
  33.         {  
  34.             return StartCommandResult.Sticky;  
  35.         }  
  36.   
  37.         public void StartLocationUpdates()  
  38.         {  
  39.             Criteria criteriaForGPSService = new Criteria  
  40.             {  
  41.                 //A constant indicating an approximate accuracy  
  42.                 Accuracy = Accuracy.Coarse,  
  43.                 PowerRequirement = Power.Medium  
  44.             };  
  45.   
  46.             var locationProvider = _locationManager.GetBestProvider(criteriaForGPSService, true);  
  47.             _locationManager.RequestLocationUpdates(locationProvider, 0, 0, this);  
  48.   
  49.         }  
  50.   
  51.         public event EventHandler<LocationChangedEventArgs> LocationChanged = delegate { };  
  52.         public void OnLocationChanged(Location location)  
  53.         {  
  54.             try  
  55.             {  
  56.                 _currentLocation = location;  
  57.   
  58.                 if (_currentLocation == null)  
  59.                     _location = "Unable to determine your location.";  
  60.                 else  
  61.                 {  
  62.                     _location = String.Format("{0},{1}", _currentLocation.Latitude, _currentLocation.Longitude);  
  63.   
  64.                     Geocoder geocoder = new Geocoder(this);  
  65.   
  66.                     //The Geocoder class retrieves a list of address from Google over the internet  
  67.                     IList<Address> addressList = geocoder.GetFromLocation(_currentLocation.Latitude, _currentLocation.Longitude, 10);  
  68.   
  69.                     Address addressCurrent = addressList.FirstOrDefault();  
  70.   
  71.                     if (addressCurrent != null)  
  72.                     {  
  73.                         StringBuilder deviceAddress = new StringBuilder();  
  74.   
  75.                         for (int i = 0; i < addressCurrent.MaxAddressLineIndex; i++)  
  76.                             deviceAddress.Append(addressCurrent.GetAddressLine(i))  
  77.                                 .AppendLine(",");  
  78.   
  79.                         _address = deviceAddress.ToString();  
  80.                     }  
  81.                     else  
  82.                         _address = "Unable to determine the address.";  
  83.   
  84.                     IList<Address> source = geocoder.GetFromLocationName(_sourceAddress, 1);  
  85.                     Address addressOrigin = source.FirstOrDefault();  
  86.   
  87.                     var coord1 = new LatLng(addressOrigin.Latitude, addressOrigin.Longitude);  
  88.                     var coord2 = new LatLng(addressCurrent.Latitude, addressCurrent.Longitude);  
  89.   
  90.                     var distanceInRadius = Utils.HaversineDistance(coord1, coord2, Utils.DistanceUnit.Miles);  
  91.   
  92.                     _remarks = string.Format("Your are {0} miles away from your original location.", distanceInRadius);  
  93.   
  94.                     Intent intent = new Intent(thistypeof(MainActivity.GPSServiceReciever));  
  95.                     intent.SetAction(MainActivity.GPSServiceReciever.LOCATION_UPDATED);  
  96.                     intent.AddCategory(Intent.CategoryDefault);  
  97.                     intent.PutExtra("Location", _location);  
  98.                     intent.PutExtra("Address", _address);  
  99.                     intent.PutExtra("Remarks", _remarks);  
  100.                     SendBroadcast(intent);  
  101.                 }  
  102.             }  
  103.             catch (Exception ex)  
  104.             {  
  105.                 _address = "Unable to determine the address.";  
  106.             }  
  107.   
  108.         }  
  109.   
  110.         public void OnStatusChanged(string provider, Availability status, Bundle extras)  
  111.         {  
  112.             //TO DO:  
  113.         }  
  114.   
  115.         public void OnProviderDisabled(string provider)  
  116.         {  
  117.             //TO DO:  
  118.         }  
  119.   
  120.         public void OnProviderEnabled(string provider)  
  121.         {  
  122.             //TO DO:  
  123.         }  
  124.     }  
  125.     public class GPSServiceBinder : Binder  
  126.     {  
  127.         public GPSService Service { get { return this.LocService; } }  
  128.         protected GPSService LocService;  
  129.         public bool IsBound { getset; }  
  130.         public GPSServiceBinder(GPSService service) { this.LocService = service; }  
  131.     }  
  132.     public class GPSServiceConnection : Java.Lang.Object, IServiceConnection  
  133.     {  
  134.   
  135.         GPSServiceBinder _binder;  
  136.   
  137.         public event Action Connected;  
  138.         public GPSServiceConnection(GPSServiceBinder binder)  
  139.         {  
  140.             if (binder != null)  
  141.                 this._binder = binder;  
  142.         }  
  143.   
  144.         public void OnServiceConnected(ComponentName name, IBinder service)  
  145.         {  
  146.             GPSServiceBinder serviceBinder = (GPSServiceBinder)service;  
  147.   
  148.             if (serviceBinder != null)  
  149.             {  
  150.                 this._binder = serviceBinder;  
  151.                 this._binder.IsBound = true;  
  152.                 serviceBinder.Service.StartLocationUpdates();  
  153.                 if (Connected != null)  
  154.                     Connected.Invoke();  
  155.             }  
  156.         }  
  157.         public void OnServiceDisconnected(ComponentName name) { this._binder.IsBound = false; }  
  158.     }  
  159. }  
The GPSService.cs file basically contains the following classes:

 

  • GPSService
  • GPSServiceBinder
  • GPSServiceConnection

The GPSService is a class that implements a Service and ILocationService. This is where we implement the code when the device location has been changed and perform some task based on the result. The OnLocationChanged event is triggered according the settings you supplied while registering location listener, the StartLocationUpdates() method handles that.

The OnLocationChanged event is where we put the logic to get the device address, location and the remarks. You may also notice that I used an Intent to pass some data using SendBroadcast() method. The values that are being passed can then be retrieved using a BroadcastReciever.
Android provides three options when communicating a service depending on where the service is running. For this example, I am using the Service Binding. The main reason is that our service (GPSService) is just part of our application. This way a client can communicate with the service directly by binding to it. A service that binds to client will override Bound Service lifecycle methods, and communicate with the client using a Binder (GPSServiceBinder) and a ServiceConnection (GPSServiceConnection).

Building the UI

Modify the Main.axml under Resources > Layout folder to make it look like this:

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:orientation="vertical"  
  4.     android:layout_width="fill_parent"  
  5.     android:layout_height="fill_parent">  
  6.     <TextView  
  7.         android:layout_width="wrap_content"  
  8.         android:layout_height="wrap_content"  
  9.         android:id="@+id/txtLocation"  
  10.         android:width="200dp"  
  11.         android:layout_marginRight="0dp"  
  12.         android:layout_gravity="right"  
  13.         android:gravity="left"  
  14.         android:layout_alignParentRight="true" />  
  15.     <TextView  
  16.         android:text="Location :"  
  17.         android:layout_width="60.2dp"  
  18.         android:layout_height="wrap_content"  
  19.         android:id="@+id/textView1"  
  20.         android:layout_toLeftOf="@id/txtLocation"  
  21.         android:layout_alignTop="@id/txtLocation"  
  22.         android:width="100dp"  
  23.         android:layout_marginTop="0dp"  
  24.         android:layout_alignParentLeft="true" />  
  25.     <TextView  
  26.         android:layout_width="wrap_content"  
  27.         android:layout_height="wrap_content"  
  28.         android:layout_below="@id/txtLocation"  
  29.         android:id="@+id/txtAddress"  
  30.         android:width="200dp"  
  31.         android:layout_alignParentRight="true" />  
  32.     <TextView  
  33.         android:text="Address :"  
  34.         android:layout_width="60.2dp"  
  35.         android:layout_height="wrap_content"  
  36.         android:id="@+id/textView2"  
  37.         android:layout_toLeftOf="@id/txtAddress"  
  38.         android:layout_below="@id/txtLocation"  
  39.         android:width="100dp"  
  40.         android:layout_marginTop="0dp"  
  41.         android:layout_alignParentLeft="true" />  
  42.     <TextView  
  43.         android:layout_width="wrap_content"  
  44.         android:layout_height="wrap_content"  
  45.         android:layout_below="@id/txtAddress"  
  46.         android:id="@+id/txtRemarks"  
  47.         android:width="200dp"  
  48.         android:layout_alignParentRight="true" />  
  49.     <TextView  
  50.         android:text="Remarks :"  
  51.         android:layout_width="60.2dp"  
  52.         android:layout_height="wrap_content"  
  53.         android:id="@+id/textView3"  
  54.         android:layout_toLeftOf="@id/txtRemarks"  
  55.         android:layout_below="@id/txtAddress"  
  56.         android:width="100dp"  
  57.         android:layout_marginTop="0dp"  
  58.         android:layout_alignParentLeft="true" />  
  59. </RelativeLayout>  
There’s really nothing fancy from the layout above. It just contain some TextViews to display the results from our service.

The Main Activity

Now update the MainActivity.cs file by adding the following code block below:
  1. using Android.App;  
  2. using Android.Content;  
  3. using Android.Widget;  
  4. using Android.OS;  
  5.   
  6. namespace MyFirstGPSApp  
  7. {  
  8.     [Activity(Label = "MyFirstGPSApp", MainLauncher = true, Icon = "@drawable/icon")]  
  9.     public class MainActivity : Activity  
  10.     {  
  11.         TextView _locationText;  
  12.         TextView _addressText;  
  13.         TextView _remarksText;  
  14.   
  15.         GPSServiceBinder _binder;  
  16.         GPSServiceConnection _gpsServiceConnection;  
  17.         Intent _gpsServiceIntent;  
  18.         private GPSServiceReciever _receiver;  
  19.   
  20.         public static MainActivity Instance;  
  21.         protected override void OnCreate(Bundle bundle)  
  22.         {  
  23.             base.OnCreate(bundle);  
  24.   
  25.             Instance = this;  
  26.             SetContentView(Resource.Layout.Main);  
  27.   
  28.             _addressText = FindViewById<TextView>(Resource.Id.txtAddress);  
  29.             _locationText = FindViewById<TextView>(Resource.Id.txtLocation);  
  30.             _remarksText = FindViewById<TextView>(Resource.Id.txtRemarks);  
  31.   
  32.             RegisterService();  
  33.         }  
  34.   
  35.         private void RegisterService()  
  36.         {  
  37.             _gpsServiceConnection = new GPSServiceConnection(_binder);  
  38.             _gpsServiceIntent = new Intent(Android.App.Application.Context, typeof(GPSService));  
  39.             BindService(_gpsServiceIntent, _gpsServiceConnection, Bind.AutoCreate);  
  40.         }  
  41.         private void RegisterBroadcastReceiver()  
  42.         {  
  43.             IntentFilter filter = new IntentFilter(GPSServiceReciever.LOCATION_UPDATED);  
  44.             filter.AddCategory(Intent.CategoryDefault);  
  45.             _receiver = new GPSServiceReciever();  
  46.             RegisterReceiver(_receiver, filter);  
  47.         }  
  48.   
  49.         private void UnRegisterBroadcastReceiver()  
  50.         {  
  51.             UnregisterReceiver(_receiver);  
  52.         }  
  53.         public void UpdateUI(Intent intent)  
  54.         {  
  55.             _locationText.Text = intent.GetStringExtra("Location");  
  56.             _addressText.Text = intent.GetStringExtra("Address");  
  57.             _remarksText.Text = intent.GetStringExtra("Remarks");  
  58.         }  
  59.   
  60.         protected override void OnResume()  
  61.         {  
  62.             base.OnResume();  
  63.             RegisterBroadcastReceiver();  
  64.         }  
  65.   
  66.         protected override void OnPause()  
  67.         {  
  68.             base.OnPause();  
  69.             UnRegisterBroadcastReceiver();  
  70.         }  
  71.   
  72.         [BroadcastReceiver]  
  73.         internal class GPSServiceReciever : BroadcastReceiver  
  74.         {  
  75.             public static readonly string LOCATION_UPDATED = "LOCATION_UPDATED";  
  76.             public override void OnReceive(Context context, Intent intent)  
  77.             {  
  78.                 if (intent.Action.Equals(LOCATION_UPDATED))  
  79.                 {  
  80.                     MainActivity.Instance.UpdateUI(intent);  
  81.                 }  
  82.   
  83.             }  
  84.         }  
  85.     }  
  86. }  
The Oncreate event is where we initialize the ContentViews and the TextViews. It is also where we register the service that we need for our app. The RegisterService() registers and binds the service needed. The RegisterBroadcastReciever() method registers the broadcast reciever so we can have access to the data from the broadcast. This method will be called at OnResume event override. The UnRegisterBroadcastReceiver() method unregisters the broadcast reciever. This method will be called at OnPause event override of the activity.

The GPSServiceReciever class is used to handle the message from a broadcast by implemeting BroadcastReciever. If you recall, under GPSService OnLocationChanged event we sent out an intent broadcast to pass values. These values will then be displayed in the UI for the client to see.

Wrapping Up

Here’s the actual view of the project:

view of the project

Before testing the app make sure that you have the following permissions within the AndroidManifest.xml:
  1. <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />  
  2. <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />  
  3. <uses-permission android:name="android.permission.INTERNET" />  
Running the App

Now try to deploy the app in an actual device. The output should look like this below:

Running the App

That’s it! I hope you will find this article useful. Stay tuned for more.

 

Next Recommended Readings