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.
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:
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).
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:
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.
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:
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:
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:
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:
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.
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 states YOUR-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.
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:
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:
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:
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:
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:
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:
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.