- Xamarin Blueprints
- Michael Williams
- 7200字
- 2021-07-08 11:48:26
Handling location updates
Our next step is to build the MapPageViewModel
; this view model will contain the IGeolocator
we just built. We will also be listening for location updates from the observable sequence and processing latitude and longitude values to gather address details.
Let's begin with the constructor:
public MapPageViewModel (INavigationService navigation, IGeolocator geolocator, Func<Action, ICommand> commandFactory, IGeocodingWebServiceController geocodingWebServiceController) : base (navigation) { _geolocator = geolocator; _geocodingWebServiceController = geocodingWebServiceController; _nearestAddressCommand = commandFactory(() => FindNearestSite()); _geolocationCommand = commandFactory(() => { if (_geolocationUpdating) { geolocator.Stop(); } else { geolocator.Start(); } GeolocationButtonTitle = _geolocationUpdating ? "Start" : "Stop"; _geolocationUpdating = !_geolocationUpdating; }); _positions = new List<IPosition> (); LocationUpdates = new Subject<IPosition> (); ClosestUpdates = new Subject<IPosition> (); }
Our constructor will retrieve the navigation service and the geolocator. Notice how we assign the geolocator
class:
_geolocator = geolocator;
The constructor will also be responsible for creating the commands for the two buttons on our map page. Any view models that require objects from the IoC container are usually assigned as read-only properties because they will never change. We want the property name to be the exact same as the item in the constructor argument:
private readonly IGeolocator _geolocator;
Now let's create our private properties:
#region Private Properties private IDisposable _subscriptions; private readonly IGeolocator _geolocator; private string _address; #endregion
We have a new object, the IDisposable
interface, which is used to take control of unmanaged resources, meaning we can release objects that have no control over memory disposal. In our case, we are going to be setting up a subscription to the events received via the observable sequence (Subject
).
Let's look at this technique more closely:
public void OnAppear() { _subscriptions = _geolocator.Positions.Subscribe (x => { _currentPosition = x; LocationUpdates.OnNext(x); }); } public void OnDisppear() { geolocator.Stop (); if (subscriptions != null) { subscriptions.Dispose (); } }
We are going to use these functions to be called when the MapPage
appears and disappears. The OnAppear
function will create a subscription to the Subject
, so whenever a new position is pushed onto the observable sequence, we will receive an item on the other side where we subscribed. In this case, we will be calling the OnNext
function on a different subject, meaning we are passing the item of the observable sequence into another observable sequence.
What a pointless function. We will show you why soon.
We are also assigning the subscription to our IDisposable
. A subscription is an unmanaged resource, meaning that without the use of an IDisposable
, we can't control the release of the subscription.
Why do we need to worry about disposing of the subscription?
Sometimes our observable streams may be propagating events to a user interface on the main UI thread. If we change pages, and the previous page's view model is still receiving events to update the previous page's interface, this means the events will be changing the user interface on a different thread from the main UI thread, which will break the application. This is just one example, but cleaning up subscriptions when we aren't using them is a good practice to control unwanted application processing.
Now for the public
properties:
#region Public Properties public string Address { get { return address; } set { if (value.Equals(address)) { return; } address = value; OnPropertyChanged("Address"); } } #endregion
All we need is a string that will be bound to MapPageLabel
under the map item. It will be used to display the address of the current location. Now we must create a label on MapPage
:
<Label x:Name="AddressLabel" Text="{Binding Address}" Grid.Row="1" Grid.Column="0"/>
Our next step is to make use of the latitude and longitude values that we receive from CLLocationManager
. We are going to use the Geocoder
class to get address information from our positions. A Geocoder
class is used to convert positions (latitudes and longitudes) into address information. We could actually do this conversion on the native side, but the idea of this exercise is to show you what is available in Xamarin.Forms
to share between the different platforms.
Now let's get back to answering the questions about passing events between two observable sequences.
Let's start building the MapPage.xaml.cs
sheet:
private MapPageViewModel viewModel; private IDisposable locationUpdateSubscriptions; private IDisposable closestSubscriptions; private Geocoder geocoder; public MapPage () { InitializeComponent (); } public MapPage (MapPageViewModel model) { viewModel = model; BindingContext = model; InitializeComponent (); Appearing += handleAppearing; Disappearing += handleDisappearing; geocoder = new Geocoder (); }
Here we create another two IDisposables
for handling the events from the view-model. We will also be subscribing to and disposing on the page's appearing and disappearing events, so now add the HandleAppearing
and HandleDisappearing
functions:
private void HandleDisappearing (object sender, EventArgs e) { viewModel.OnDisppear (); if (locationUpdateSubscriptions != null) { locationUpdateSubscriptions.Dispose (); } if (closestSubscriptions != null) { closestSubscriptions.Dispose (); } } private void HandleAppearing (object sender, EventArgs e) { viewModel.OnAppear (); locationUpdateSubscriptions = viewModel.LocationUpdates.Subscribe (LocationChanged); }
We also create a new Geocoder
, so every time we receive an event from the observable sequence in the view model, we use this position to retrieve the address information from Geocoder
via the following function:
private void LocationChanged (IPosition position) { try { var formsPosition = new Xamarin.Forms.Maps.Position(position.Latitude, position.Longitude); geocoder.GetAddressesForPositionAsync(formsPosition) .ContinueWith(_ => { var mostRecent = _.Result.FirstOrDefault(); if (mostRecent != null) { viewModel.Address = mostRecent; } }) .ConfigureAwait(false); } catch (Exception e) { System.Diagnostics.Debug.WriteLine ("MapPage: Error with moving map region - " + e); } }
That is everything we need to retrieve our latitude and longitude positions, as well as update the current address. The last step of our iOS version is to update the position on the map; we want the map view to zoom in to our current position and place the blue marker on the map. Next, we add the following to the end of LocationChanged
function:
MapView.MoveToRegion (MapSpan.FromCenterAndRadius (formsPosition, Distance.FromMiles (0.3)));
The MoveToRegion
function requires a MapSpan
; a MapSpan
is created from the latitude, longitude point and the radius from the position point. A circle will be drawn from the point to give the view radius to be shown on the map; in our case the radius is 0.3 miles around the latitude and longitude position.
The ContinueWith
function is used to execute some extra work as soon as the task finishes. As soon as we have retrieved all the possible address names, we wake the first on the list and assign it to the Address
property of the variable.
Our final step is to complete the rest of the project; we must first create an iOS module for registering the geolocator class:
public class IOSModule : IModule { public void Register(ContainerBuilder builer) { builer.RegisterType<GeolocatorIOS>().As<IGeolocator>().SingleInstance(); } }
Then finally we add the extras to the AppDelegate.cs
file (exactly the same as the previous example iOS project):
[Register ("AppDelegate")] public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate { public override bool FinishedLaunching (UIApplication app, NSDictionary options) { global::Xamarin.Forms.Forms.Init (this, bundle); global::Xamarin.FormsMaps.Init (this, bundle); initIoC (); LoadApplication (new App ()); return base.FinishedLaunching (app, options); } private void initIoC() { IoC.CreateContainer (); IoC.RegisterModule (new IOSModule()); IoC.RegisterModule (new XamFormsModule()); IoC.RegisterModule (new PortableModule()); IoC.StartContainer (); } }
Excellent! Let's run the project and click on the Find Location button. Watch the map update with the address shown in the preceding label.
Let's move on to the Android project and implement the same features.
Android and the LocationManager
The Android LocationManager
works like the CLLocationManager
, but we will use an observable sequence to handle location updates. When a location update is received, a new Position object is instantiated with the latitude and longitude values from the location update. Then the resulting Position is pushed on to the Geolocator's Subject.
First we create the Geolocator
implementation. It must also inherit the ILocationListener
interface:
public class GeolocatorDroid : IGeolocator, ILocationListener { private string provider = string.Empty; public Subject<IPosition> Positions { get; set; } #region ILocationListener implementation public void OnLocationChanged (Location location) { Positions.OnNext (new Position () { Latitude = location.Latitude, Longitude = location.Longitude }); } public void OnProviderDisabled (string provider) { Console.WriteLine (provider + " disabled by user"); } public void OnProviderEnabled (string provider) { Console.WriteLine (provider + " disabled by user"); } public void OnStatusChanged (string provider, Availability status, Bundle extras) { Console.WriteLine (provider + " disabled by user"); } #endregion }
Tip
You may have noticed the #define
keywords. These are useful for separating different sections and for referencing locations in code sheets, making code more readable.
The only one we are concerned about is the OnLocationChanged
function; whenever a location update is received by the location manager, the listener function will be called with the latitude and longitude values, and we will then use these values to push into the observable sequence for the Geocoder
and MapSpan
.
We also have to implement the extra requirements for the ILocationListener
interface. Since this interface inherits the IJavaObject
interface, we are required to implement the Dispose
function and the IntPtr
object.
To save time, we can have the class inherit the Java.Lang.Object
class like this:
public class GeolocatorDroid : Object, IGeolocator, ILocationListener
Next, we add the constructor:
private LocationManager locationManager; public GeolocatorDroid() { Positions = new Subject<IPosition> (); locationManager = (LocationManager)Application.Context.GetSystemService(Context.LocationService); provider = LocationManager.NetworkProvider; }
In the constructor, we pull out the required system service using the GetSystemService
function for the location service. The line underneath simply retrieves the NetworkProvider
of the LocationManager
; we need to use this for starting the location updates. There are further configurations we can set for retrieving correct providers (mainly logging purposes), but in this example we aren't going to bother too much as we are only interested in retrieving location positions.
Now it's time to implement the other required functions of the IGeolocator
interface:
public void Start() { if (locationManager.IsProviderEnabled(provider)) { locationManager.RequestLocationUpdates (provider, 2000, 1, this); } else { Console.WriteLine(provider + " is not available. Does the device have location services enabled?"); } } public void Stop() { locationManager.RemoveUpdates (this); }
The Start
function will first check whether we have these services enabled, then by calling the RequestLocationUpdates
function, we pass in the provider, the minimum time between locations updates, the minimum location distance between updates, and the pending intent to be called on each location update; in our case, this is the geolocator (the same class that started the location updates) as we have implemented the ILocationListener
class.
The Stop
function simply removes the updates from the Geolocator
, which in turn will stop the location updates from the location manager. Our next step in implementing the Android Geolocator
is to create the Android IoC module, and register this implementation in the IoC container:
public void Register(ContainerBuilder builer) { builer.RegisterType<GeolocatorDroid>().As<IGeolocator>().SingleInstance(); }
Our final step is to set up the MainActivity
class, which is exactly the same as the previous project:
[Activity (Label = "Locator.Droid", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity { protected override void OnCreate (Bundle bundle) { base.OnCreate (bundle); global::Xamarin.Forms.Forms.Init (this, bundle); global::Xamarin.FormsMaps.Init (this, bundle); LoadApplication (new App ()); } private void initIoC() { IoC.CreateContainer (); IoC.RegisterModule (new DroidModule()); IoC.RegisterModule (new XamFormsModule()); IoC.RegisterModule (new PortableModule()); IoC.StartContainer (); } }
Tip
Take note of how much code we are starting to reuse from previous projects. Why reinvent the wheel when we can save a lot of time by pulling from similar problems that have already been solved in other projects?
The last step in the Android project is to apply some Android permissions to allow your app to use location services. Open up the Mainfest.xml
and add the following:
<application android:label="Locator"> <meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="YOUR-API-KEY" /> <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> </application> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Inside the <application>
tag, we have to place API_KEY
, which is generated from the Google APIs platform (we will be doing this later). We then have to add the ACCESS_FINE_LOCATION
, ACCESS_COARSE_LOCATION
, and ACCESS_NETWORK_STATE
permissions for LocationManager to work. We can switch these permissions on through the Application window:

Creating an exit point
You may have noticed the extra button added on the starting page for exiting the application. We will have to go ahead and create an abstracted object for exiting the application. Start by creating a new folder called Extras
, then create a new file for the IMethods
interface:
public interface IMethods { void Exit(); }
Tip
Before moving on with the tutorial, have a go at implementing the native side for each project on your own.
Let's begin with the iOS version:
public class IOSMethods { public void Exit() { UIApplication.SharedApplication.PerformSelector(new ObjCRuntime.Selector("terminateWithSuccess"), null, 0f); } }
For the iOS version, we must dig into the SharedApplication
object and perform a selector method terminateWithSuccess
. We must then register this new object in our iOS module:
public void Register(ContainerBuilder builer) { builer.RegisterType<GeolocatorIOS>().As<IGeolocator>().SingleInstance(); builer.RegisterType<IOSMethods>().As<IMethods>().SingleInstance(); }
Now the Android implementation:
public class DroidMethods { public void Exit() { Android.OS.Process.KillProcess(Android.OS.Process.MyPid()); } }
Using the Android operating system namespace, we use the static item Process
to call the function KillProcess
on the main process. Again, we also register this within the IoC container:
public void Register(ContainerBuilder builer) { builer.RegisterType<GeolocatorDroid>().As<IGeolocator>().SingleInstance(); builer.RegisterType<DroidMethods>().As<IMethods>().SingleInstance(); }
Finally, we use the IMethods
interface in our MainPageViewModel
to call the exit function:
public MainPageViewModel (INavigationService navigation, Func<Action, ICommand> commandFactory, IMethods methods) : base (navigation) { exitCommand = commandFactory (() => methods.Exit()); locationCommand = commandFactory (() => Navigation.Navigate(PageNames.MapPage)); }
Looking at this more closely, we are using the command factory to initialize the exit command to a new Xamarin.Forms Command
, and when this command is executed, it will call the Exit
method from the IMethods
interface.
Our last step is to create an API key using the Google APIs for our Android version.
Creating an API key for Android
In order for us to create an API key, we will have to access the Google API portal. Android requires this extra step when configuring Google Maps:
Tip
You will need a Google Developer account to complete this section.
- Visit the following link to create a new project in the API portal: https://console.developers.google.com/iam-admin/projects.
- Select Create Project from the top menu and call the project
Locator
:Tip
For more information on setting up an API key, visit this link: https://developers.google.com/maps/documentation/javascript/get-api-key#get-an-api-key.
- Once we have our new project, visit the API Manager and select the Google Maps Android API:
- Select the Enable button, then click Credentials from the left-hand menu. We want to create a new API key from the drop-down list:
- Make sure we select an Android key:
- We are going to leave the name as
Android key 1
. Now click the Create button: - Finally, let's select our Android key and place it in the
AndroidManifest.xml
file where it statesYOUR-API-KEY
:
Congratulations, we have now integrated the iOS and Android location services with Google Maps.
Now let's move on to the Windows Phone version.
Creating our Windows project
Moving on to Visual Studio once again, let start by creating a new c-shape universal Windows project and calling it Locator.WinRT
:

We can remove the Windows store and shared projects. Before you remove the shared projects, move the app.xaml
files into the Windows Phone project.
Tip
The Map
object from Xamarin.Forms.Maps
is not usable in Windows Phone 8.1. We have to use the universal platform instead.
For our Windows Phone version, we need the following:
- A Windows Phone module for registering the geolocator and methods interfaces
- To implement the geolocator interface
- To implement the methods interface
Note
Have a think about that for a second...
That's all we have to do to replicate the application for Windows Phone? Think how much extra work would be involved if we were to rebuild this app from scratch entirely on the Windows platform.
Next, add the three folders, Modules
, Location
, and Extras
, and create a new .cs
file for each folder and name them accordingly: WinPhoneModule.cs
, GeolocatorWinPhone.cs
, and WinPhoneMethods.cs
.
Firstly, we have to change the targets of the PCL projects to be compatible with the Windows Phone frameworks. Select the Windows Phone 8.1 target for both PCL projects, then the Windows project can reference the two PCL projects:

We must also import the Xamarin.Forms
, Xamarin.Forms.Maps
, and Autofacnuget
packages.
Core Location Services with Windows Phone
Now for the exciting part. Let's integrate the core location services. First, we must turn on certain permissions. Open up the package.appmanifest
file, select the Capabilities tab, and select the Location checkbox:

Secondly, open the GeolocatorWinPhone.cs
file, and let's start building the Windows Phone locator class.
Let's start by creating the constructor:
public class GeolocatorWinPhone : IGeolocator { public Subject<IPosition> Positions { get; set; } Geolocator _geolocator; public GeolocatorWinPhone() { Positions = new Subject<IPosition>(); geolocator = new Geolocator(); _geolocator.DesiredAccuracyInMeters = 50; } }
We are implementing a native Geolocator
from the interface IGeolocator
, meaning we need to create an observable sequence for the positions. We also need a Geolocator
object to receive location updates, which we will use to push events into the sequence. With all native locators, we can set accuracy for location points, which is what we are doing with the following line:
geolocator.DesiredAccuracyInMeters = 50;
Our next step is to implement the Start
and Stop
functions:
public async void Start() { try { var geoposition = await _geolocator.GetGeopositionAsync( maximumAge: TimeSpan.FromMinutes(5), timeout: TimeSpan.FromSeconds(10) ); _geolocator.PositionChanged += geolocatorPositionChanged; // push a new position into the sequence Positions.OnNext(new Position() { Latitude = geoposition.Coordinate.Latitude, Longitude = geoposition.Coordinate.Longitude }); } catch (Exception ex) { Console.WriteLine("Error retrieving geoposition - " + ex); } }
The Start
function uses Geolocator
to retrieve the positions with the asynchronous function GetGeopositionAsync
. The function will take the maximum age of a location, meaning once the time period is passed, the location will update again. The request for this location will cancel when the timeout value is reached during a location update. We also listen on the event handler PositionChanged
via the following function:
private void GeolocatorPositionChanged(Geolocator sender, PositionChangedEventArgs args) { // push a new position into the sequence Positions.OnNext(new Position () { Latitude = args.Position.Coordinate.Latitude, Longitude = args.Position.geoposition.Coordinate.Longitude }); }
We actually have two places, which will push a new geoposition's latitude and longitude into the observable sequence.
Now we add the Stop
function:
public void Stop() { // remove event handler _geolocator.PositionChanged -= GeolocatorPositionChanged; }
All this does is remove the event handler function that we assigned in the Start
function.
Note
You should be noticing the development patterns with this project, how we implement abstracted interfaces, generate modules, register types, and so on. The processes are all the same, no matter what platform.
That's all for the Geolocator
class; we can now get on to the WinPhoneModule
:
public class WinPhoneModule : IModule { public void Register(ContainerBuilder builer) { builer.RegisterType<GeolocatorWinPhone>().As<IGeolocator>().SingleInstance(); builer.RegisterType<WinPhoneMethods>().As< IMethods>().SingleInstance(); } }
Now let's get to the WinPhoneMethods
class. We only need to implement the one function, Exit
.
The Application class
The static class Application
plays a similar role to the iOS UIApplication
class. We simply reference the current application, and terminate:
public class WinPhoneMethods : IMethods { public void Exit() { Application.Current.Terminate(); } }
Now we simply build the remaining elements with the MainPage.xaml
page:
<forms:WindowsPhonePage x:Class="Locator.WinPhone.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Locator.WinPhone" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:forms="using:Xamarin.Forms.Platform.WinRT" mc:Ignorable="d" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> </forms:WindowsPhonePage>
And we do it for the MainPage.xaml.cs
file:
public MainPage() { InitializeComponent(); InitIoC(); NavigationCacheMode = NavigationCacheMode.Required; LoadApplication(new Locator.App()); } private void InitIoC() { IoC.CreateContainer(); IoC.RegisterModule(new WinPhoneModule()); IoC.RegisterModule(new SharedModule(true)); IoC.RegisterModule(new XamFormsModule()); IoC.RegisterModule(new PortableModule()); IoC.StartContainer(); }
Exactly the same as the previous chapter, we are starting the IoC
container, adding our modules, and loading the Xamarin.Forms.App
object. The only difference is the SharedModule
, as we pass in true so the NativeMessageHandler
is used.
Finally, we have one more issue to address. Since Xamarin.Forms
1.5, only Windows Phone Silverlight is supported for using Google Maps. We have to add an additional library to use maps in Windows Phone 8.1.
Note
Personal thanks to Peter Foot for addressing this issue.
Luckily, an open source library is available to address this issue. We must install the nuget package InTheHand.Forms.Maps
.
Tip
This library is only available up to Xamarin.Forms
2.1.0.6529, meaning this entire example must stick to this version of Xamarin.Forms
.
Then, inside App.xaml.cs
, we need to initialize Xamarin.Forms
and Xamarin.Forms.Maps
. The Xamarin.Forms.Maps
framework is initialized through the library InTheHand.Forms.Maps
like this:
if (rootFrame == null) { rootFrame = new Frame(); rootFrame.CacheSize = 1; if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) { } Xamarin.Forms.Forms.Init(e); InTheHand.FormsMaps.Init("YOUR-API-KEY"); Window.Current.Content = rootFrame; }
Just like that, we now have the application on Windows Phone. Now that we have core location services running with Google Maps, let's take things one step further with the Google API platforms.
Web services and data contracts
We are now going to look at creating a web service controller to access web services provided by Google. These are useful implementations for downloading JSON data, deserializing it, and feeding this data in observable sequences for processing. With a web service controller, we get to use more of the IObservable
interface. These sequences will be used to take in deserialized JSON objects from a web source, and feed these into our view models.
Our web service controller will be kept inside the Locator.Portable project. Remember, we can share this work across the different platforms as all use some form of HTTP client to connect to a web URL.
What about data contracts?
Your data contract is a JSON object that is used to absorb the elements of the deserialized objects, so whenever we pull down raw JSON data, your contract will be the deserialized object or objects.
So the next question is, what data are we pulling to our application?
We are going to use the Google Geocoder
API to turn address information into latitude and longitude positions. We are going to pull down a list of addresses, calculate their latitude and longitude positions, calculate the closest address to our current position, and place a pin on the map.
Our first step is to create a new folder called WebServices
in Locator.Portable
. Inside this folder, we want to create another folder called GeocodingWebServiceController
, and another folder inside this called Contracts
. Let's first implement our contracts. A nice quick easy way to implement your JSON objects is to use an online application like this one: http://json2csharp.com/.
When we are pulling down JSON data, it takes time to look through the text and find all the properties required for your JSON object. This provides a nice way is to call the web service URL, retrieve some sample JSON data, and paste this JSON data into the box here:

Note
Personal thanks to Jonathan Keith for saving us time.
This application creates c-sharp JSON objects based on the JSON data you entered. Now let's get our sample JSON data to paste in the box, but before we can do this we have to access the Google API.
Creating another API key for geocoding
Log back in to the Google Developer console, and our first step is to enable to the Geocoding API from the API manager:

We then select the project Locator
we created earlier, and this time we are going to create a browser key to access the Geocoding API via HTTP requests:

Call the key Geocoding Key
and click Create. We are now going to use this key for every HTTP request passed to the Geocoding API:

Creating GeocodingWebServiceController
Our first step creating GeocodingWebServiceController
is to hit the web URL using your API key to pull down some sample JSON data; here is a test link: https://maps.googleapis.com/maps/api/geocode/json?address=1600+Amphitheatre+Parkway,+Mountain+View,+CA&key=YOUR_API_KEY
.
Where it says YOUR_API_KEY
, replace this text with your newly created API key, and then paste this link into the browser. You should get JSON results like this:
{ "results" : [ { "address_components" : [ { "long_name" : "1600", "short_name" : "1600", "types" : [ "street_number" ] }, { "long_name" : "Amphitheatre Parkway", "short_name" : "Amphitheatre Pkwy", "types" : [ "route" ] }, { "long_name" : "Mountain View", "short_name" : "Mountain View", "types" : [ "locality", "political" ] }, { "long_name" : "Santa Clara County", "short_name" : "Santa Clara County", "types" : [ "administrative_area_level_2", "political" ] },
We are going to copy and paste the entire resulting JSON into Json2Sharp to create our c-sharp objects:

There are quite a few JSON objects, so in the Contracts
folder, create the following files:
- AddressComponentContract.cs
- GeocodingContract.cs
- GeocodingResultContract.cs
- GeometryContract.cs
- LocationContract.cs
- NortheastContract.cs
- SouthwestContract.cs
- ViewportContract.cs
Let's begin with AddressComponentContract.cs
:
public sealed class AddressComponentContract { #region Public Properties public string long_name { get; set; } public string short_name { get; set; } public List<string> types { get; set; } #endregion }
Make sure we keep all these contracts in the namespace Locator.Portable.GeocodingWebServiceController.Contracts
.
Note
Namespaces should be named according to the folder hierarchy.
Now for the GeocodingContract
:
public sealed class GeocodingContract { #region Public Properties public List<GeocodingResultContract> results { get; set; } public string status { get; set; } #endregion }
The rest of the files are exactly the same; we simply copy the c-sharp objects created by Json2Sharp. Now it's time to complete the others:
public sealed class GeocodingResultContract { #region Public Properties public List<AddressComponentContract> address_components { get; set; } public string formatted_address { get; set; } public GeometryContract geometry { get; set; } public string place_id { get; set; } public List<string> types { get; set; } #endregion }
Make sure you double-check the property names are exactly the same as the JSON properties, otherwise the values inside the JSON string will not be deserialized correctly.
Note
We are not going to paste in every contract, as this should be enough direction for you to build the others.
Now that we have our geocoding contracts, let's create the interface for the GeocodingWebServiceController
:
public interface IGeocodingWebServiceController { #region Methods and Operators IObservable<GeocodingContract> GetGeocodeFromAddressAsync (string address, string city, string state); #endregion }
This is only a small interface; we only have one function, GetGeocodeFromAddressAsync
. The function requires three arguments to build the parameters in the web URL.
Now let's implement this interface.
Tip
A good practice with object-oriented and abstract coding is to declare interfaces before implementing the class which coincides; it will help you build the class quicker.
Newtonsoft.Json and Microsoft HTTP client libraries
As we are going to be deserializing JSON, we will need to import a JSON framework library. Newtonsoft is one of the most commonly used frameworks, so let's import this library into our Locator.Portable project:

We will also need to import the HTTP client libraries for our web service controller to access online web services:

Now that we have all the extra libraries for our Locator.Portable project, before we implement the IGeocodingWebServiceController
, we have to make some additions to the project structure:

Right-click on the Locator and create a new shared project called Locator.Shared:

ModernHttpClient and client message handlers
In this project, we will be creating a shared module to register a HttpClientHandler
class in the IoC container. HttpClientHandler
is a message handler class that receives a HTTP request and returns a HTTP response. Message handlers are used on both the client and server side for handling/delegating requests between different end points.
In our example, we are interested in the client side, as we are calling the server; our client handler will be used to handle our HTTP messages sent from the HTTP client.
Let's begin by adding the ModernHttpClient
library to our Locator (we will refer to this project as the Xamarin.Forms
project) and all native projects:

We also want to add the Microsoft Client Libraries package to all native projects.
In our shared project, remember we can't import libraries; these projects are only used to share code sheets. In this project, we want to create a folder called Modules
. In the Modules
folder, create a new file called SharedModule.cs
and implement the following:
public sealed class SharedModule : IModule { #region Fields private bool isWindows; #endregion #region Constructors and Destructors public SharedModule(bool isWindows) { isWindows = isWindows; } #endregion #region Public Methods and Operators public void Register(ContainerBuilder builder) { HttpClientHandler clientHandler = isWindows ? new HttpClientHandler() : new NativeMessageHandler(); clientHandler.UseCookies = false; clientHandler.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; builder.Register(cb => clientHandler).As<HttpClientHandler>().SingleInstance(); } #endregion }
One thing to notice is the minor change we have to make between the iOS and Android projects, and the Windows Phone project. Windows must use NativeMessageHandler
for the HttpClientHandler
in the IoC container. In iOS and Android, we can use a default HttpClientHandler
.
We tell the client handler that we not going to be using cookies, and we allow for automatic decompression on the data being pulled through the client handler (GZIP is a common form of JSON data compression).
Now let's focus our attention on the constructor. We simply pass in a bool
to determine whether we are using Windows to register the correct type of message handler for the current platform.
Now let's add this module to the registration in the AppDelegate
and MainActivity
file; it must be called before the LoadApplication
function:
private void InitIoC() { IoC.CreateContainer (); IoC.RegisterModule (new IOSModule()); IoC.RegisterModule (new SharedModule(false)); IoC.RegisterModule (new XamFormsModule()); IoC.RegisterModule (new PortableModule()); IoC.StartContainer (); }
Excellent! We now have access to our HTTP client handler in the IoC container, so let's start building the GeocodingWebServiceController
class:
public sealed class GeocodingWebServiceController : IGeocodingWebServiceController { #region Fields /// <summary> /// The client handler. /// </summary> private readonly HttpClientHandler clientHandler; #endregion #region Constructors and Destructors public GeocodingWebServiceController (HttpClientHandler clientHandler) { clientHandler = clientHandler; } #endregion }
Feeding JSON data into the IObservable framework
As we are going to be registering this web service controller in the IoC container, we can pull out the client handler we just created and registered in the SharedModule
class. Now we must implement the function we defined in the interface:
#region Public Methods public IObservable<GeocodingContract> GetGeocodeFromAddressAsync(string address, string city, string state) { var authClient = new HttpClient(_clientHandler); var message = new HttpRequestMessage(HttpMethod.Get, new Uri(string.Format(ApiConfig.GoogleMapsUrl, address, city, state))); return Observable.FromAsync(() => authClient.SendAsync(message, new CancellationToken(false))) .SelectMany(async response => { if (response.StatusCode != HttpStatusCode.OK) { throw new Exception("Respone error"); } return await response.Content.ReadAsStringAsync(); }) .Select(json => JsonConvert.DeserializeObject<GeocodingContract>(json)); } #endregion
It may look a bit daunting at first, but let's break it down. Our web service controller is going to pull down data, deserialize the data into our main JSON object GeocodingContract
, and create contracts in an observable sequence.
When we instantiate a new HttpClient
, we must pass in our registered client handler to delegate the request messages being sent from the HTTP client. We then create a new Http.Get
message; this will be sent from the HttpClient
and delegated through the message handler (HttpClientHandler
), which in turn will receive a JSON response.
This is where it gets tricky. Look at the Observable.FromAsync
function; this method takes an asynchronous function, will run and await the function, and will return data as an observable sequence. The asynchronous function must return an IObservable
.
The function we are passing is the SendAsync
function of the HttpClient
; we then use the RX function SelectMany
to take all the response objects. If each response object incurs a HTTP status code 200
(OK
), we return the response content as a string. Notice the async
keyword in front of the expression; we have to use an asynchronous function to await the ReadAsAsync
function and return the response content as a JSON string.
Finally, we use the RX function Select
to take each response string and return the deserialized GeocodingContract
. This contract will be fed into the observable sequence and returned to the original caller Observable.FromAsync
, which in turn will be the data returned from the function.
More Reactive Extensions
Before we move on, let's talk more about the RX functions we just used. The Select
function is used for iterating over any List
, Enumerable
, or IObservable
, and taking the value of each item to create a new observable sequence.
Say we have a list of objects with a string property Name
, and we do the following:
var newObservable = list.Select (x => x);
We are simply returning the same sequence of items, but then we do something like this:
var newObservable = list.Select (x => x.Name);
Our new sequence would be a stream of just the Name
property for each object. These functions are very useful for filtering streams and lists.
Resource (RESX) files
Notice in our GetGeocodeFromAddressAsync
function we are referencing a static class, ApiConfig
:
ApiConfig.GoogleMapsUrl
This is a technique for containing your application's resources, such as strings, URLs, constant variables, settings properties, and so on. It is also used for languages in which we have different constant variable values, based on language settings. This is normally how you would make your app multilingual.
Let's create a new folder called Resources
inside the Locator.Portable project:

In the ApiConfig.Designer.cs
file, we must have the namespace set according to the folder hierarchy. In this example, it is Locator.Portable | Resources.
Tip
Locator.Portable is the name assigned to our assembly. We must know the assembly name to reference where the folders will be stored when the app is built. To find out the name of your assembly, visit the properties page, shown in the next screenshot.

Now that we have our ApiConfig.resx
file, let's add a variable for the GoogleMapsUrl
property; paste the following in the ApiConfig.resx
file:
<!-- url --> <data name="GoogleMapsUrl" xml:space="preserve"> <value>https://maps.googleapis.com/maps/api/geocode/json?address={0},+{1},+{2}&key={YOUR-BROSWER-API-KEY}</value> </data>
Note
When you save this file, you will notice the ApiConfig.Designer.resx
file is automatically generated, meaning the namespace may change to incorrect folder paths. Sometimes we have to manually change the folder path every time this file regenerates.
Using GeocodingWebServiceController
Now that we have set up our web service controller, let's integrate it with our MapPageViewModel
. Our first step is to register the web service controller inside the IoC container; open up PortableModule.cs
and add the following to the Register
function:
builer.RegisterType<GeocodingWebServiceController> ().As<IGeocodingWebServiceController>().SingleInstance();
Now we update the constructor inside MapPageViewModel
to use GeocodingWebServiceController
from the IoC container:
#region Constructors public MapPageViewModel (INavigationService navigation, IGeolocator geolocator, IGeocodingWebServiceController geocodingWebServiceController) : base (navigation) { _geolocator = geolocator; _geocodingWebServiceController= geocodingWebServiceController; LocationUpdates = new Subject<IPosition> (); } #endregion
Our next step is to add an array of static addresses as a dictionary:
#region Constants private IDictionary<int, string[]> addresses = new Dictionary<int, string[]>() { {0, new string[] { "120 Rosamond Rd", "Melbourne", "Victoria" }}, {1, new string[] { "367 George Street", "Sydney", "New South Wales" }}, {2, new string[] { "790 Hay St", "Perth", "Western Australi" }}, {3, new string[] { "77-90 Rundle Mall", "Adelaide", "South Australia" }}, {4, new string[] { "233 Queen Street", "Brisbane", "Queensland" }}, }; #endregion
We are going to use the geocoder API to determine latitude and longitude positions of all these address locations, and from your current location, determine which one is closer.
OnNavigatedTo and OnShow
Before we go any further with the Geocoding API, we need to make some additions to the navigation setup. Let's begin by implementing the OnNavigatedTo
function for all content pages. Create a new file called INavigableXamFormsPage.cs
and paste in the following:
internal interface INavigableXamarinFormsPage { void OnNavigatedTo(IDictionary<string, object> navigationParameters); }
Note
Notice the internal
keyword; this is because this class will never leave the Xamarin.Forms
project.
Now we want every page to inherit this interface and create the OnNavigatedTo
function:
public partial class MainPage : ContentPage, INavigableXamarinFormsPage { public void OnNavigatedTo(IDictionary<string, object> navigationParameters) { } } public partial class MapPage : ContentPage, INavigableXamarinFormsPage { public void OnNavigatedTo(IDictionary<string, object> navigationParameters) { } }
Now we want to call the OnNavigatedTo
function every time a page is navigated to. First, let's update our interface for the NavigationService
:
public interface INavigationService { Task Navigate (PageNames pageName, IDictionary<string, object> navigationParameters); }
Now open up the NavigationService
class and update the Navigate
function:
#region INavigationService implementation public async Task Navigate (PageNames pageName, IDictionary<string, object> navigationParameters) { var page = getPage (pageName); if (page != null) { var navigablePage = page as INavigableXamarinFormsPage; if (navigablePage != null) { await IoC.Resolve<NavigationPage> ().PushAsync (page); navigablePage.OnNavigatedTo (); } } } #endregion
After the page is pushed, we then call the OnNavigatedTo
function.
Now we want to do a similar thing with page view models. In your ViewModelBase
class, add the following:
public void OnShow(IDictionary<string, object> parameters) { LoadAsync(parameters).ToObservable().Subscribe( result => { // we can add things to do after we load the view model }, ex => { // we can handle any areas from the load async function }); } protected virtual async Task LoadAsync(IDictionary<string, object> parameters) { }
The OnShow
function will take in the navigation parameters from the coinciding page's OnNavigatedTo
function.
Notice that the RX approach with handling asynchronous functions when the LoadAsync
has finished?
We have options to handle results and errors from the LoadAsync
function. You may have also noticed the short expressions used with arrows. This type of syntax is known as lambda expressions, a very common c-sharp syntax for abbreviating functions, arguments, and delegates. Our LoadAsync
is also virtual, which means any page view model that implements this interface can override this function.
Now let's make some extra additions to the Xamarin.Forms
project (Locator
). Create a new file in the UI
folder and call it XamarinNavigationExtensions.cs
. Now for the implementation:
public static class XamarinNavigationExtensions { #region Public Methods and Operators // for ContentPage public static void Show(this ContentPage page, IDictionary<string, object> parameters) { var target = page.BindingContext as ViewModelBase; if (target != null) { target.OnShow(parameters); } } #endregion }
Looking at this more closely, we are actually making extension functions for all ContentPage
types. The OnShow
function for a ContentPage
will extract the binding context as a ViewModelBase
and call the OnShow
function of the view model, which in turn will call LoadAsync
. Finally, we make the changes to MapPage.xaml.cs
and MainPage.xaml.cs
:
public void OnNavigatedTo(IDictionary<string, object> navigationParameters) { this.Show (navigationParameters); }
Well done! What we just implemented is a Windows Phone principle. We know that when the OnNavigatedTo
function is called, our layout for the XAML
sheet is already sized accordingly. The advantage of having this is we can now retrieve x, y, height, and width figures from the page inside this function.
Pythagoras equirectangular projection
Now back to the Geocoding API. We are going to implement the math behind calculating the closest address to a latitude and longitude (current position).
For our first step, we need to add some properties for MapPageViewModel
:
#region Private Properties private IList<IPosition> _positions; private Position _currentPosition; private string _closestAddress; private int _geocodesComplete = 0; #endregion
Now for the extra public
property, which will hold the string address of the closest position:
public string ClosestAddress { get { return _closestAddress; } set { if (value.Equals(_closestAddress)) { return; } _closestAddress = value; OnPropertyChanged("ClosestAddress"); } }
Now we have to add another Subject
sequence for when the closet position changes:
#region Subjects public Subject<IPosition> ClosestUpdates { get; set; } #endregion
This must be initialized in the constructor:
ClosestUpdates = new Subject<IPosition> ();
Now for the fun part.
How are we going to calculate the closest position?
Let's start with the first private function, which will get the positions from the address:
public async Task GetGeocodeFromAddress(string address, string city, string state) { var geoContract = await _geocodingWebServiceController.GetGeocodeFromAddressAsync(address, city, state); if (geoContract != null && geoContract.results != null && geoContract.results.Count > 0) { var result = geoContract.results.FirstOrDefault(); if (result != null && result.geometry != null && result.geometry.location != null) { _geocodesComplete++; _positions.Add(new Position() { Latitude = result.geometry.location.lat, Longitude = result.geometry.location.lng, Address = string.Format("{0}, {1}, {2}", address, city, state) }); // once all geocodes are found, find the closest if ((_geocodesComplete == _positions.Count) && _currentPosition != null) { FindNearestSite(); } } } }
In this function, we finally get to use our GeocodingWebServiceController
.
See how we pass in the variables that will make up the web service URL?
For each address, we must ping this API call to get the latitude and longitudes required to calculate the closest position. Then we do a bunch of checks on the values in the data contracts to make sure they aren't null, until we get the GeometryContract
values; we will then use these to create a new position and add it to the list.
Now let's make a small change to the Position
class and interface:
public class Position : IPosition { public string Address {get; set;} } public interface IPosition { double Latitude {get; set;} double Longitude {get; set;} public string Address {get; set;} }
Add the Address
property so we can record the address string for the closest property. We need to record this in the position because as we fire off so many requests to the API, they will not necessarily finish in order so we can't expect to use index referencing to obtain the position index in the list, to be the coinciding address in the array.
Now let's add the mathematical functions for calculating distances using the PythagorasEquirectangular
projection. It uses angular projection to calculate the distance between two coordinates on a map plane. We also need a DegreesToRadians
conversion for the PythagorasEquirectangular
function:
private double DegreesToRadians(double deg) { return deg * Math.PI / 180; } private double PythagorasEquirectangular (double lat1, double lon1, double lat2, double lon2) { lat1 = DegreesToRadians(lat1); lat2 = DegreesToRadians(lat2); lon1 = DegreesToRadians(lon1); lon2 = DegreesToRadians(lon2); // within a 10km radius var radius = 10; var x = (lon2 - lon1) * Math.Cos((lat1 + lat2) / 2); var y = (lat2 - lat1); var distance = Math.Sqrt(x * x + y * y) * radius; return distance; }
If the distance falls outside the radius value, it will not be used.
Tip
Try playing around with this setting to see the results you get.
Now for the FindNearestSite
function:
private void FindNearestSite() { if (_geolocationUpdating) { _geolocationUpdating = false; _geolocator.Stop(); GeolocationButtonTitle = "Start"; } double mindif = 99999; IPosition closest = null; var closestIndex = 0; var index = 0; if (_currentPosition != null) { foreach (var position in _positions) { var difference = PythagorasEquirectangular(_currentPosition.Latitude, _currentPosition.Longitude, position.Latitude, position.Longitude); if (difference < mindif) { closest = position; closestIndex = index; mindif = difference; } index++; } if (closest != null) { var array = _addresses[closestIndex]; Address = string.Format("{0}, {1}, {2}", array[0], array[1], array[2]); ClosestUpdates.OnNext(closest); } } }
We will call this when all the geocodes for the address have been obtained and added to the positions list. We then go through all the positions and compare each to our current position, determine which coordinate difference is the smallest, and use this as our closest position. Then we push a new position onto the ClosestUpdates
observable sequence, which we will subscribe to on the MapPage
.
Our last step on the MapPageViewModel
is to override the LoadAsync
function:
protected override async Task LoadAsync (IDictionary<string, object> parameters) { var index = 0; for (int i = 0; i < 5; i++) { var array = _addresses [index]; index++; GetGeocodeFromAddress(array[0], array[1], array[2]).ConfigureAwait(false); } }
This is where everything will kick off; when the page loads, it will iterate through every address and download the geocode, then once we count the entire count of the address list, we find the nearest positions and push onto the ClosestUpdates
sequence. We also want to run the GetGeocodeFromAddress
function in parallel for each address; this is why we have ConfigureAwait
set to false.
Now let's make changes to the MapPage
. We are going to use two IDisposables
now for the MapPage
, one for each subject in the view model:
private IDisposable _locationUpdateSubscriptions; private IDisposable _closestSubscriptions;
Now we update the OnAppear
and OnDisappear
functions to handle the subscribing to and disposing of the Subjects
:
private void HandleDisappearing (object sender, EventArgs e) { _viewModel.OnDisppear (); if (_locationUpdateSubscriptions != null) { _locationUpdateSubscriptions.Dispose (); } if (_closestSubscriptions != null) { _closestSubscriptions.Dispose (); } } private void HandleAppearing (object sender, EventArgs e) { _viewModel.OnAppear (); _locationUpdateSubscriptions = _viewModel.LocationUpdates.Subscribe (LocationChanged); _closestSubscriptions = _viewModel.ClosestUpdates.Subscribe (ClosestChanged); }
And our final touch is to add the function that is called every time for the ClosetUpdates
observable sequence:
private void ClosestChanged (IPosition position) { try { var pin = new Pin() { Type = PinType.Place, Position = new Xamarin.Forms.Maps.Position (position.Latitude, position.Longitude), Label = "Closest Location", Address = position.Address }; MapView.Pins.Add(pin); MapView.MoveToRegion(MapSpan.FromCenterAndRadius(new Xamarin.Forms.Maps.Position(position.Latitude, position.Longitude) , Distance.FromMiles(0.3))); } catch (Exception e) { System.Diagnostics.Debug.WriteLine ("MapPage: Error with moving pin - " + e); } }
We are creating a pin to place on the map. This pin will also show the address information when we click on the pin. We then move to the region on the map to show this pin, using the MoveToRegion
function.
That is everything; we have now integrated with Google Maps and Geocoding.