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:
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:
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:
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:
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
:
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
{
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);
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)
{
}
public void OnProviderDisabled(string provider)
{
}
public void OnProviderEnabled(string provider)
{
}
}
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:
="1.0"="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:
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:
Before testing the app make sure that you have the following permissions within the AndroidManifest.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:
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.