Chapter 3. Building a GPS Locator Application

In this chapter, we will delve deeper into code sharing. We will build a Xamarin.Forms application that integrates native GPS location services and Google Maps APIs. We will cover more content on IoC containers, the Xamarin.Forms.Maps library, and techniques for c-sharp async and background tasks.

Expected knowledge:

  • Web services
  • JSON
  • Google Maps
  • Google Geocoding APIs (it helps to have a Google Developer account)

In this chapter, you will learn the following:

  • Core location and GPS
  • Navigation with Xamarin.Forms
  • Google Maps integration
  • Integrating Google Maps with Xamarin.Forms.Maps
  • Reactive extensions
  • Core location with iOS and the CLLocationManager Library
  • Android and the LocationManager
  • Creating our Windows project
  • Core location services with Windows Phone
  • The Application class
  • Web services and data contracts
  • Integrating with a Google APIs
  • Creating the Geocoding web service controller
  • Newtonsoft.Json and Microsoft HTTP client libraries
  • ModernHttpClient and client message handlers
  • Feeding JSON data into the IObservable framework more reactive extensions
  • Resource (RESX) files
  • Using the Geocoding web server controller
  • OnNavigatedTo and OnShow
  • Pythagoras equirectangular projection

Core location and GPS

All mobile phone platforms have access to core location services. These services are background tasks that run in the background and update the latitude and longitude values at certain intervals indefinitely until the service is stopped. 99% of smart phones come with a built-in GPS tracker, allowing you to integrate these latitude and longitude values with your application.

Project setup

Let's jump straight into project setup and create a new Xamarin.Forms application. We are going to start by setting up an IoC container with Autofac, exactly the same as the previous project, import Autofac into all three projects (PCL, Android, and iOS). We can reuse a lot of the PCL code from the IoC container implementation in the previous project.

Note

The more apps you build, the more problems you solve; why reinvent the wheel over and over? Eventually, when you have built multiple applications, future apps will be built mostly from piecing parts of different projects together.

Copy in the IoC, Pages, and ViewModels folders, and let's start building our MainPage:

<?xml version="1.0" encoding="UTF-8"?> 
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"  
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"  
    x:Class="Locator.Pages.MainPage" 
    BackgroundColor="White" 
    Title="Welcome"> 
 
    <ContentPage.Content> 
 
    <Grid x:Name="Grid" RowSpacing="10" Padding="10, 10, 10, 10" VerticalOptions="Center"> 
        <Grid.RowDefinitions> 
            <RowDefinition Height="*"/> 
            <RowDefinition Height="Auto"/> 
            <RowDefinition Height="Auto"/> 
            <RowDefinition Height="Auto"/> 
        </Grid.RowDefinitions> 
 
        <Grid.ColumnDefinitions> 
            <ColumnDefinition Width="*"/> 
        </Grid.ColumnDefinitions> 
 
        <Image x:Name="Image" Source="map.png" HeightRequest="120" WidthRequest="120"  
                Grid.Row="0" Grid.Column="0"/> 
 
        <Label x:Name="DesciptionLabel" Text="{Binding DescriptionMessage}" HorizontalOptions="Center" Font="Arial, 20" Grid.Row="1" Grid.Column="0"> 
            <Label.TextColor> 
                <OnPlatform x:TypeArguments="Color" 
                    Android="Black" 
                    WinPhone="Black" 
                    iOS="Black"> 
                </OnPlatform> 
            </Label.TextColor> 
        </Label> 
 
        <Button x:Name="LocationButton" Text="{Binding LocationTitle}" Command="{Binding LocationCommand}" BackgroundColor="Silver" Grid.Row="2" Grid.Column="0"> 
            <Button.TextColor> 
                <OnPlatform x:TypeArguments="Color" 
                    Android="Navy" 
                    WinPhone="Blue" 
                    iOS="Black"> 
                </OnPlatform> 
            </Button.TextColor> 
        </Button> 
 
        <Button x:Name="ExitButton" Text="{Binding ExitTitle}" Command="{Binding ExitCommand}" BackgroundColor="Silver" Grid.Row="3" Grid.Column="0"> 
            <Button.TextColor> 
                <OnPlatform x:TypeArguments="Color" 
                    Android="Navy" 
                    WinPhone="Blue" 
                    iOS="Black"> 
                </OnPlatform> 
            </Button.TextColor> 
        </Button>  
    </Grid> 
 
    </ContentPage.Content> 
 
</ContentPage> 

This is very much the same as the previous MainPage, but this time we are adding two Buttons, a Label, and an Image.

Note

Before reading any further, have a look at the properties bounded to each element. See if you can build the properties for the view model.

Navigation with Xamarin.Forms

Before we start building any view models we are going to build our navigation system. Xamarin.Forms comes complete with navigation control for all platforms, so you won't have to worry about it. But as we always like to do things the hard way, we are going to show you a technique to separate our cross-platform structure a little more, in order to keep things more modular. Using one PCL project to contain both view models and views is great, but what if we could separate our views from view models into two PCL projects?

Why would we do this?

One small issue we have with the current PCL is that it relies completely on Xamarin.Forms. Only our XAML sheets and user interfaces rely on Xamarin.Forms; our view models do not. Then let's move the view models from the Xamarin.Forms PCL into an even lower-level PCL project that only relies on c-sharp libraries.

This is a good technique to keep the PCL projects completely separated. Developing a modular system is advantageous when it comes to code sharing. For example, we are building a new app that requires a login screen, a list view screen, and other similar screens most apps include. As we already have the view models that handle all the web services, JSON processing, and property bindings, do we really need to change much? Now that we have a low-level project that simply has the view models, let's just extract the ones we need, design our user interfaces for the view models, and bind them together. Not only can we reuse these view models for other apps, but if we wanted to develop an entirely separated application (for example, a WPF application), we can just compare the required screens, take the related view models, create new user interfaces, and bind them together. Keeping everything completely separated allows for complete plug-and-play capability, which will dramatically decrease the development time required to build similar applications.

Let's approach this pattern by creating a new PCL project and copying in the view models; call it Locator.Portable:

Why would we do this?

We also want to copy over the IoC folder as well.

Building the navigation control

Our first step is to create a folder called enum, add the PageNames.cs file, and copy in the following:

public enum PageNames 
{ 
    MainPage, 
 
    MapPage 
} 

Now let's add a new folder called UI and create a new file called INavigationService.cs:

public interface INavigationService 
{ 
    Task Navigate(PageNames pageName); 
} 

Then we create a new folder in the Xamarin.Forms PCL (Locator) project called UI, and create a new file called NavigationService.cs. The NavigationService class will inherit the INavigationService interface:

    public class NavigationService : INavigationService 
    { 
        #region INavigationService implementation 
 
        public async Task Navigate (PageNames pageName) 
        { 
        } 
 
        #endregion 
    } 

Simple, right? Navigate will be used whenever we want the stack to navigate to a page. In making an abstracted interface, as we have done for navigation, this allows us to control navigation way down in the lower-level PCL. Now, fill in the rest:

        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 (navigationParameters); 
                } 
            } 
        } 
 
        private Page GetPage(PageNames page) 
        { 
            switch(page) 
            { 
                case PageNames.MainPage: 
                    return IoC.Resolve<MainPage> (); 
                case PageNames.MapPage: 
                    return IoC.Resolve<MapPage> (); 
                default: 
                    return null; 
            } 
        } 

Firstly, look more closely at the private function GetPage; this will be called every time the Navigate function is called to retrieve the correct ContentPage object (which is registered in the IoC container) based upon the PageName enum passed to it, and if we have found the correct page, push it onto the navigation stack.

Finally, let's build our new XamFormsModule for registering the pages and navigation service:

public void Register(ContainerBuilder builer) 
        { 
            builer.RegisterType<MainPage> ().SingleInstance(); 
            builer.RegisterType<MapPage> ().SingleInstance(); 
 
            builer.Register (x => new NavigationPage(x.Resolve<MainPage>())).AsSelf().SingleInstance(); 
 
            builer.RegisterType<NavigationService> ().As<INavigationService>().SingleInstance(); 
        } 

We are registering one navigation page throughout the entire life of the application, and we set the starting page to the one main page item we registered before.

Now open up the App.cs file and update it accordingly:

public App () 
        { 
            MainPage = IoC.Resolve<NavigationPage> (); 
        } 

Making sense now?

IoC is a very powerful pattern for cross-platform applications.

View model navigation

Now let's get back to our MainPageViewModel and update and modify the previous chapter's MainPageViewModel with the properties required for the data-bindings on MainPage.xaml shown previously. Firstly, let's implement the private properties:

public class MainPageViewModel : ViewModelBase 
    { 
                #region Private Properties 
     
        private readonly IMethods _methods; 
 
        private string _descriptionMessage = "Find your location"; 
 
        private string _locationTitle = "Find Location"; 
 
        private string _exitTitle = "Exit"; 
 
        private ICommand _locationCommand; 
 
        private ICommand _exitCommand; 
 
        #endregion 
 
} 

Now for the Public properties:

#region Public Properties 
 
        public string DescriptionMessage 
        { 
            get 
            { 
                return _descriptionMessage; 
            } 
 
            set 
            { 
                if (value.Equals(_descriptionMessage)) 
                { 
                    return; 
                } 
 
                _descriptionMessage = value; 
                OnPropertyChanged("DescriptionMessage"); 
            } 
        } 
 
        public string LocationTitle 
        { 
            get 
            { 
                return _locationTitle; 
            } 
 
            set 
            { 
                if (value.Equals(_locationTitle)) 
                { 
                    return; 
                } 
 
                _locationTitle = value; 
                OnPropertyChanged("LocationTitle"); 
            } 
        } 
 
        public string ExitTitle 
        { 
            get 
            { 
                return _exitTitle; 
            } 
 
            set 
            { 
                if (value.Equals(_exitTitle)) 
                { 
                    return; 
                } 
 
                _exitTitle = value; 
                OnPropertyChanged("ExitTitle"); 
            } 
        } 
 
        public ICommand LocationCommand 
        { 
            get 
            { 
                return _locationCommand; 
            } 
 
            set 
            { 
                if (value.Equals(_locationCommand)) 
                { 
                    return; 
                } 
 
                _locationCommand = value; 
                OnPropertyChanged("LocationCommand"); 
            } 
        } 
 
        public ICommand ExitCommand 
        { 
            get 
            { 
                return _exitCommand; 
            } 
 
            set 
            { 
                if (value.Equals(_exitCommand)) 
                { 
                    return; 
                } 
 
                _exitCommand = value; 
                OnPropertyChanged("ExitCommand"); 
            } 
        } 
 
        #endregion 

Are we starting to see the same pattern here?

Now add the constructor, which is going to use the navigation service interface that we abstracted earlier through the IoC container:

        #region Constructors 
 
        public MainPageViewModel (INavigationService navigation) : base (navigation) 
        { 
 
        } 
 
        #endregion 

Now it's time to show you another trick using the IoC container. In our constructor, we need to be able to create a new Command object from the Xamarin.Forms library. We are lucky here, because since commands from Xamarin.Forms inherit the ICommand interface from System.Windows.Input, we are able to register this object in the IoC container. Open up XamFormsModule.cs and update the Register function to include the following:

builer.RegisterType<Xamarin.Forms.Command> ().As<ICommand>().InstancePerDependency(); 

Tip

Take note that we are registering this type as an InstancePerDependency because we want an independent instance every time we create a command in the view model constructors.

Now let's create a new command through the constructor of MainPageViewModel; update the constructor like this:

        #region Constructors 
 
        public MainPageViewModel (INavigationService navigation, Func<Action, ICommand> commandFactory) : base (navigation) 
        { 
            _locationCommand = commandFactory (() => Navigation.Navigate(PageNames.MapPage)); 
        } 
 
        #endregion 

In the constructor, we are pulling a Func out of the IoC container, which takes an Action and returns an ICommand object, because we have registered this interface to a Xamarin.FormsCommand object, we will be left with a new Command with the action passed in the constructor as follows:

  locationCommand = commandFactory (() => Navigation.Navigate(PageNames.MapPage)); 

This is exactly the same as doing this if we were using the Xamarin.Forms library:

  locationCommand = new Command (() => Navigation.Navigate(PageNames.MapPage)); 

Now we have a new Command set with and Action to push a new MapPage onto the stack when the button is pressed:

    public class PortableModule : IModule 
    { 
        public void Register(ContainerBuilder builer) 
        { 
            builer.RegisterType<MainPageViewModel> ().SingleInstance(); 
           
        } 
    } 

Now to register our new view model with the IoC container. Create a new folder called Modules for the portable IoC module. Create a new file called PortableModule.cs and paste in the preceding code into it.

Integrating Google Maps using Xamarin.Forms.Maps

Our next step is to implement the MapPage; this page will show a panel that will display Google Maps. Underneath this panel, we will also display the location information (latitude, longitude, address, and so on) retrieved from our native platform core location services. To access these native services, we need to import Xamarin.Forms.Maps:

Integrating Google Maps using Xamarin.Forms.Maps

Now that we have imported the Xamarin.Forms.Maps library, we can access the native Google Maps services. We can now create the Map user interface element via MapPage.xaml:

<?xml version="1.0" encoding="UTF-8"?> 
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"  
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"  
      xmlns:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps" 
    x:Class="Locator.Pages.MapPage" 
    BackgroundColor="White" 
    Title="Map"> 
 
    <ContentPage.Content> 
 
        <Grid x:Name="Grid" RowSpacing="10" Padding="10, 10, 10, 10"> 
            <Grid.RowDefinitions> 
                <RowDefinition Height="*"/> 
                <RowDefinition Height="80"/> 
                <RowDefinition Height="60"/> 
                <RowDefinition Height="60"/> 
            </Grid.RowDefinitions> 
 
            <Grid.ColumnDefinitions> 
                <ColumnDefinition Width="*"/> 
            </Grid.ColumnDefinitions> 
 
            <maps:Map x:Name="MapView" IsShowingUser="true" Grid.Row="0" Grid.Column="0"/> 
 
            <Label x:Name="AddressLabel" Text="{Binding Address}" TextColor="Black" Grid.Row="1" Grid.Column="0"/> 
 
            <Button x:Name="GeolocationButton" Text="{Binding GeolocationButtonTitle}"  
                Command="{Binding GeolocationCommand}" Grid.Row="2" Grid.Column="0"/> 
 
            <Button x:Name="NearestAddressButton" Text="Find Nearest Address"  
                Command="{Binding NearestAddressCommand}" Grid.Row="3" Grid.Column="0"/> 
        </Grid> 
 
    </ContentPage.Content> 
 
</ContentPage> 

See at the top how we imported the Xamarin.Forms.Maps library?

We have created four rows in the Grid, one for the Map (this will cover most of the screen), one for a label that will display the address, and two buttons for starting/stopping location updates and finding the closest location out of a list of addresses.

So where does the address come from?

We now need to implement the core location service; this is a background service that will send position information based upon your location. The information returned is very detailed; we can depict exact longitude and latitude values, as well as addresses.

Note

Core location services can drain device battery life, so when using core location, we must manage the usage and turn it on and off when required. As this is a background service, when the app is placed in the background, the location service will still be running.

To begin our core location implementation, we are going to create an abstracted geolocation interface called IGeolocator, but first we are going to add another library for processing our location updates.

Reactive Extensions

If you haven't heard of the RX framework before, you are about to enter a never-ending rabbit hole of asynchrony. RX gives developers the ability to use LINQ-style query operations for processing objects in observable sequences. It allows for full control over event-based operations between different elements of an application.

In our project, we are going to use a Subject for handling location events received on the native side. In cross-platform development, because we work in both PCL and native-level projects, it involves passing data and events up and down the project structure.

We could use the event framework, which is standard in c-sharp, but instead we are going to use a Subject to push events into an observable sequence, while we subscribe to the subject at a lower level to receive and handle these events.

Let's start by importing the Reactive Extensions interface in our native and PCL projects:

Reactive Extensions

Now let's create our IGeolocator class:

    public interface IGeolocator 
    { 
        Subject<IPosition> Positions { get; set; }  
 
        void Start(); 
 
        void Stop(); 
    } 

Notice the interface IPosition? We must also create a new interface, which is going to store all the location information:

public interface IPosition 
    { 
        double Latitude {get; set;} 
 
        double Longitude {get; set;} 
    } 

The interface is designed to return these variables to be used for the Xamarin.Forms geolocator, so we can pull down address information. This information is returned by CLLocationManager with every position update.

Why do we need to create an interface for the position information?

As this information comes from different native services, we want to create our own object to contain the information we need in the lower-level projects.

Core location with iOS and the CLLocationManager library

CLLocationManager is used for the delivery of location and heading events; we must use this object in our Geolocator implementation, so let's begin:

    public class GeolocatorIOS : IGeolocator 
    { 
        public Subject<IPosition> Positions { get; set; }  
    } 

From our interface, we must include the Subject. Now let's instantiate CLLocationManager. First, we must import the CoreLocation library:

    using CoreLocation; 

Now we instantiate CLLocationManager in the constructor when this is created through the IoC container. According to iOS standards, since changes to iOS 9 and iOS 8, we must implement a few separate calls to allow the location manager to begin sending location events:

public GeolocatorIOS() 
        { 
            Positions = new Subject<IPosition> (); 
         
            locationManager = new CLLocationManager(); 
            locationManager.PausesLocationUpdatesAutomatically = false;  
 
            // iOS 8 has additional permissions requirements 
            if (UIDevice.CurrentDevice.CheckSystemVersion (8, 0))  
            { 
                locationManager.RequestWhenInUseAuthorization (); 
            } 
 
            if (UIDevice.CurrentDevice.CheckSystemVersion (9, 0))  
            { 
                locationManager.AllowsBackgroundLocationUpdates = true; 
            } 
        } 

This is nothing major; in iOS 8 we must request the authorization before using the location manager. For iOS 9, we can also set some conditional settings. For our example, we have used this:

AllowsBackgroundLocationUpdates = true 

This allows the location manager to keep sending events, even when the app is in the background. We can also do this:

if (UIDevice.CurrentDevice.CheckSystemVersion (8, 0))  
            { 
                locationManager.RequestWhenInUseAuthorization (); 
            }  

This will only allow events from CLLocationManager when the app is in the foreground. There are multiple settings that can be changed, between controlling location events in the foreground and background when using location services. We want to know whether our app is going to keep updates running in the background/foreground. Most of the time, we want location updates when the app is in the foreground to reduce battery consumption, but there are scenarios where updates should continue in the background.

Now for the rest of the class; let's begin handling the location events:

        private void handleLocationsUpdated (object sender, CLLocationsUpdatedEventArgs e) 
        { 
            var location = e.Locations.LastOrDefault (); 
            if (location != null) 
            { 
                Console.WriteLine ("Location updated, position: " + location.Coordinate.Latitude + "-" + location.Coordinate.Longitude); 
 
                // fire our custom Location Updated event 
                Positions.OnNext(new Position() 
                    { 
                        Latitude = location.Coordinate.Latitude, 
                        Longitude = location.Coordinate.Longitude, 
                    }); 
            } 
        } 

The previous function is called every time we receive a location update from CLLocationManager. From the event argument CLLocationsUpdatedEventArgs, we pull out a list of locations; as sometimes the CLLocationManager receives multiple updates at one time, we always want to take the very last location. Then once we create a new Position, assign the latitude and longitude values, and by calling the OnNext function, we push a new event into the observable sequence.

Our next step is to add some small additions to the info.plist file.

Let's add the following keys:

  <key>NSLocationAlwaysUsageDescription</key> 
  <string>Can we use your location</string> 
  key>NSLocationWhenInUseUsageDescription</key> 
  <string>We are using your location</string> 

Note

The preceding code is from the source of the info.plist file.

The NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription keys will be displayed to the user in the alert that requests location data access. We must also add the background modes for the location in which we can set the iOS project properties:

Core location with iOS and the CLLocationManager library

Now we must implement the Start and Stop functions:

        public void Start() 
        { 
            if (CLLocationManager.LocationServicesEnabled)  
            { 
                locationManager.DesiredAccuracy = 1; 
                locationManager.LocationsUpdated += handleLocationsUpdated; 
                locationManager.StartUpdatingLocation(); 
            } 
        } 
 
        public void Stop() 
        { 
            locationManager.LocationsUpdated -= handleLocationsUpdated; 
            locationManager.StopUpdatingLocation(); 
        } 

The Start function will check whether location services have been enabled, assign the LocationsUpdated event, and start the location updates:

public void Register(ContainerBuilder builer) 
        { 
            builer.RegisterType<GeolocatorIOS>().As<IGeolocator>().SingleInstance(); 
        } 

The Stop function will do nothing more than stop the location updates and remove the event handler. That's all for the iOS geolocator. Next, we must register this interface through the IoC container.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.186.167