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:
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. 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:
- 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.
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:
- <?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:
- 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
Here’s the actual view 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 below:
That’s it! I hope you will find this article useful. Stay tuned for more.