Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Xamarin

Xamarin.Android: Working With GPS And Locations

4.70/5 (20 votes)
26 Jul 2016CPOL5 min read 59.9K   2.1K  
In this article, you will learn 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.

Introduction

In the past months, 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 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 Android. 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: Getting Started With Android Wearable Using Xamarin and Visual Studio

Let's Get Started!

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

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:

Image 1

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:

Image 2

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. 
Components The Components directory houses ready-made features from the Xamarin Components.
Assets Contains the files the application needs to run including fonts, local data files and text files. 
Properties Contains the AndroidManifest.xml file that describes all the requirements for our Xamarin.Android application, including name, version number and permissions. 
Resources Contains application resources such as strings, images and layouts. We can access these resources in code through the generated Resource class. 

Creating the Latitute and Longitude Class

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

C#
namespace MyFirstGPSApp  
{  
    public class LatLng  
    {  
        public double Latitude { get; set; }  
        public double Longitude { get; set; }  
        public LatLng(double lat, double lng)  
        {  
            this.Latitude = lat;  
            this.Longitude = lng;  
        }  
    }  
}  

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:

C#
using System;  
  
namespace MyFirstGPSApp  
{  
    static class Utils  
    {  
        public enum DistanceUnit { Miles, Kilometers };  
        public static double ToRadian(this double value)  
        {  
            return (Math.PI / 180) * value;  
        }  
        public static double HaversineDistance(LatLng coord1, LatLng coord2, DistanceUnit unit)  
        {  
            double R = (unit == DistanceUnit.Miles) ? 3960 : 6371;  
            var lat = (coord2.Latitude - coord1.Latitude).ToRadian();  
            var lng = (coord2.Longitude - coord1.Longitude).ToRadian();  
  
            var h1 = Math.Sin(lat / 2) * Math.Sin(lat / 2) +  
                     Math.Cos(coord1.Latitude.ToRadian()) * Math.Cos(coord2.Latitude.ToRadian()) *  
                     Math.Sin(lng / 2) * Math.Sin(lng / 2);  
  
            var h2 = 2 * Math.Asin(Math.Min(1, Math.Sqrt(h1)));  
  
            return R * h2;  
        }  
    }  
} 

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. I've referred the code from this post.

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:

C#
using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
  
using Android.App;  
using Android.Content;  
using Android.OS;  
using Android.Locations;  
  
namespace MyFirstGPSApp  
{  
  
    [Service]  
    public class GPSService : Service, ILocationListener  
    {  
        private const string _sourceAddress = "TGU Tower, Cebu IT Park, Jose Maria del Mar St,Lahug, Cebu City, 6000 Cebu";  
        private string _location = string.Empty;  
        private string _address = string.Empty;  
        private string _remarks = string.Empty;  
  
        public const string LOCATION_UPDATE_ACTION = "LOCATION_UPDATED";  
        private Location _currentLocation;  
        IBinder _binder;  
        protected LocationManager _locationManager = (LocationManager)Android.App.Application.Context.GetSystemService(LocationService);  
        public override IBinder OnBind(Intent intent)  
        {  
            _binder = new GPSServiceBinder(this);  
            return _binder;  
        }  
  
        public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)  
        {  
            return StartCommandResult.Sticky;  
        }  
  
        public void StartLocationUpdates()  
        {  
            Criteria criteriaForGPSService = new Criteria  
            {  
                //A constant indicating an approximate accuracy  
                Accuracy = Accuracy.Coarse,  
                PowerRequirement = Power.Medium  
            };  
  
            var locationProvider = _locationManager.GetBestProvider(criteriaForGPSService, true);  
            _locationManager.RequestLocationUpdates(locationProvider, 0, 0, this);  
  
        }  
  
        public event EventHandler<LocationChangedEventArgs> LocationChanged = delegate { };  
        public void OnLocationChanged(Location location)  
        {  
            try  
            {  
                _currentLocation = location;  
  
                if (_currentLocation == null)  
                    _location = "Unable to determine your location.";  
                else  
                {  
                    _location = String.Format("{0},{1}", _currentLocation.Latitude, _currentLocation.Longitude);  
  
                    Geocoder geocoder = new Geocoder(this);  
  
                    //The Geocoder class retrieves a list of address from Google over the internet  
                    IList<Address> addressList = geocoder.GetFromLocation(_currentLocation.Latitude, _currentLocation.Longitude, 10);  
  
                    Address addressCurrent = addressList.FirstOrDefault();  
  
                    if (addressCurrent != null)  
                    {  
                        StringBuilder deviceAddress = new StringBuilder();  
  
                        for (int i = 0; i < addressCurrent.MaxAddressLineIndex; i++)  
                            deviceAddress.Append(addressCurrent.GetAddressLine(i))  
                                .AppendLine(",");  
  
                        _address = deviceAddress.ToString();  
                    }  
                    else  
                        _address = "Unable to determine the address.";  
  
                    IList<Address> source = geocoder.GetFromLocationName(_sourceAddress, 1);  
                    Address addressOrigin = source.FirstOrDefault();  
  
                    var coord1 = new LatLng(addressOrigin.Latitude, addressOrigin.Longitude);  
                    var coord2 = new LatLng(addressCurrent.Latitude, addressCurrent.Longitude);  
  
                    var distanceInRadius = Utils.HaversineDistance(coord1, coord2, Utils.DistanceUnit.Miles);  
  
                    _remarks = string.Format("Your are {0} miles away from your original location.", distanceInRadius);  
  
                    Intent intent = new Intent(this, typeof(MainActivity.GPSServiceReciever));  
                    intent.SetAction(MainActivity.GPSServiceReciever.LOCATION_UPDATED);  
                    intent.AddCategory(Intent.CategoryDefault);  
                    intent.PutExtra("Location", _location);  
                    intent.PutExtra("Address", _address);  
                    intent.PutExtra("Remarks", _remarks);  
                    SendBroadcast(intent);  
                }  
            }  
            catch (Exception ex)  
            {  
                _address = "Unable to determine the address.";  
            }  
  
        }  
  
        public void OnStatusChanged(string provider, Availability status, Bundle extras)  
        {  
            //TO DO:  
        }  
  
        public void OnProviderDisabled(string provider)  
        {  
            //TO DO:  
        }  
  
        public void OnProviderEnabled(string provider)  
        {  
            //TO DO:  
        }  
    }  
    public class GPSServiceBinder : Binder  
    {  
        public GPSService Service { get { return this.LocService; } }  
        protected GPSService LocService;  
        public bool IsBound { get; set; }  
        public GPSServiceBinder(GPSService service) { this.LocService = service; }  
    }  
    public class GPSServiceConnection : Java.Lang.Object, IServiceConnection  
    {  
  
        GPSServiceBinder _binder;  
  
        public event Action Connected;  
        public GPSServiceConnection(GPSServiceBinder binder)  
        {  
            if (binder != null)  
                this._binder = binder;  
        }  
  
        public void OnServiceConnected(ComponentName name, IBinder service)  
        {  
            GPSServiceBinder serviceBinder = (GPSServiceBinder)service;  
  
            if (serviceBinder != null)  
            {  
                this._binder = serviceBinder;  
                this._binder.IsBound = true;  
                serviceBinder.Service.StartLocationUpdates();  
                if (Connected != null)  
                    Connected.Invoke();  
            }  
        }  
        public void OnServiceDisconnected(ComponentName name) { this._binder.IsBound = false; }  
    }  
}  

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:

XML
<?xml version="1.0" encoding="utf-8"?>  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:orientation="vertical"  
    android:layout_width="fill_parent"  
    android:layout_height="fill_parent">  
    <TextView  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:id="@+id/txtLocation"  
        android:width="200dp"  
        android:layout_marginRight="0dp"  
        android:layout_gravity="right"  
        android:gravity="left"  
        android:layout_alignParentRight="true" />  
    <TextView  
        android:text="Location :"  
        android:layout_width="60.2dp"  
        android:layout_height="wrap_content"  
        android:id="@+id/textView1"  
        android:layout_toLeftOf="@id/txtLocation"  
        android:layout_alignTop="@id/txtLocation"  
        android:width="100dp"  
        android:layout_marginTop="0dp"  
        android:layout_alignParentLeft="true" />  
    <TextView  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_below="@id/txtLocation"  
        android:id="@+id/txtAddress"  
        android:width="200dp"  
        android:layout_alignParentRight="true" />  
    <TextView  
        android:text="Address :"  
        android:layout_width="60.2dp"  
        android:layout_height="wrap_content"  
        android:id="@+id/textView2"  
        android:layout_toLeftOf="@id/txtAddress"  
        android:layout_below="@id/txtLocation"  
        android:width="100dp"  
        android:layout_marginTop="0dp"  
        android:layout_alignParentLeft="true" />  
    <TextView  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_below="@id/txtAddress"  
        android:id="@+id/txtRemarks"  
        android:width="200dp"  
        android:layout_alignParentRight="true" />  
    <TextView  
        android:text="Remarks :"  
        android:layout_width="60.2dp"  
        android:layout_height="wrap_content"  
        android:id="@+id/textView3"  
        android:layout_toLeftOf="@id/txtRemarks"  
        android:layout_below="@id/txtAddress"  
        android:width="100dp"  
        android:layout_marginTop="0dp"  
        android:layout_alignParentLeft="true" />  
</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:

C#
using Android.App;  
using Android.Content;  
using Android.Widget;  
using Android.OS;  
  
namespace MyFirstGPSApp  
{  
    [Activity(Label = "MyFirstGPSApp", MainLauncher = true, Icon = "@drawable/icon")]  
    public class MainActivity : Activity  
    {  
        TextView _locationText;  
        TextView _addressText;  
        TextView _remarksText;  
  
        GPSServiceBinder _binder;  
        GPSServiceConnection _gpsServiceConnection;  
        Intent _gpsServiceIntent;  
        private GPSServiceReciever _receiver;  
  
        public static MainActivity Instance;  
        protected override void OnCreate(Bundle bundle)  
        {  
            base.OnCreate(bundle);  
  
            Instance = this;  
            SetContentView(Resource.Layout.Main);  
  
            _addressText = FindViewById<TextView>(Resource.Id.txtAddress);  
            _locationText = FindViewById<TextView>(Resource.Id.txtLocation);  
            _remarksText = FindViewById<TextView>(Resource.Id.txtRemarks);  
  
            RegisterService();  
        }  
  
        private void RegisterService()  
        {  
            _gpsServiceConnection = new GPSServiceConnection(_binder);  
            _gpsServiceIntent = new Intent(Android.App.Application.Context, typeof(GPSService));  
            BindService(_gpsServiceIntent, _gpsServiceConnection, Bind.AutoCreate);  
        }  
        private void RegisterBroadcastReceiver()  
        {  
            IntentFilter filter = new IntentFilter(GPSServiceReciever.LOCATION_UPDATED);  
            filter.AddCategory(Intent.CategoryDefault);  
            _receiver = new GPSServiceReciever();  
            RegisterReceiver(_receiver, filter);  
        }  
  
        private void UnRegisterBroadcastReceiver()  
        {  
            UnregisterReceiver(_receiver);  
        }  
        public void UpdateUI(Intent intent)  
        {  
            _locationText.Text = intent.GetStringExtra("Location");  
            _addressText.Text = intent.GetStringExtra("Address");  
            _remarksText.Text = intent.GetStringExtra("Remarks");  
        }  
  
        protected override void OnResume()  
        {  
            base.OnResume();  
            RegisterBroadcastReceiver();  
        }  
  
        protected override void OnPause()  
        {  
            base.OnPause();  
            UnRegisterBroadcastReceiver();  
        }  
  
        [BroadcastReceiver]  
        internal class GPSServiceReciever : BroadcastReceiver  
        {  
            public static readonly string LOCATION_UPDATED = "LOCATION_UPDATED";  
            public override void OnReceive(Context context, Intent intent)  
            {  
                if (intent.Action.Equals(LOCATION_UPDATED))  
                {  
                    MainActivity.Instance.UpdateUI(intent);  
                }  
  
            }  
        }  
    }  
}

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

For your reference, here’s the actual structure of the project:

Image 3

 

Before testing the app make sure that you have the following permissions within the AndroidManifest.xml:

XML
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />  
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />  
<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:

Image 4

That’s it! I hope you will find this article useful. 

Summary

In this article, we've learned how to create a simple app that utilizes Android Location services.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)