Chapter 4. Adding Location-Based Features within Your App

In our previous chapter, we looked at how we can apply what we already know about the MVVM design pattern, and how we can navigate between our ViewModels, by creating a navigation service C# class that acts as the navigation service for our app, using the Xamarin.FormsDependencyService class.

In this chapter, you'll learn how to go about incorporating platform-specific features within the TrackMyWalks app, depending on the mobile platform. You'll learn how to create a C# class, which will act as the Location Service for our app, as well as creating a IWalkLocationService interface class file, which will include a number of class methods that both our iOS and Android platforms will inherit, and, in turn, update the content pages to bind with the ViewModels to allow location-based information between these Views to happen.

We will also be covering how to properly perform location updates while the application is either in the foreground or background, and we will also be touching on some key background concepts, which include registering an app as a background-necessary application.

This chapter will cover the following topics:

  • Creating a location-based class that utilizes the native platform capabilities that come as part of the iOS and Android platforms
  • Enabling background location updates as well as getting the user's current location
  • Updating the TrackMyWalks application to use the Location Service
  • Updating the WalkEntryViewModel to use the Location Service Interface
  • Updating the DistanceTravelledViewModel to use the Location Service Interface

Creating and using platform-specific services

As mentioned in the introduction to this chapter, we created a customized navigation service, which provided an IWalkNavService Interface class for which our WalkBaseViewModel contained a property of that interface type, so that any implementations of the IWalkNavService can be provided to each of the ViewModels, as required.

The benefit of using an Interface to define platform-specific services is that it can be used within the ViewModels and the implementations of the service can be provided via dependency injection, using the DependencyService, with those implementations being actual services, or even mocked-up services for unit testing the ViewModels, which we will be covering in Chapter 9 , Unit Testing Your Xamarin.Forms Apps Using the NUnit and UITest Frameworks.

In addition to the navigation service, we can use a couple of other platform-specific feature services within our TrackMyWalks app to enrich its data and user experience. In this section, we will be taking a look at how to create a Location Service class that allows us to get the specific geolocation coordinates from the actual device for both our iOS and Android platforms.

Creating the Location Service Interface for the TrackMyWalks app

Before we can begin allowing our TrackMyWalks app to take advantage of the device's geolocation capabilities for both our iOS and Android platforms, we will need to create an Interface within the TrackMyWalks Portable Class Library, which can then be used by the ViewModels for each platform.

We will need to define the interface for our location service, as this will contain method implementations, as well as a data structure that will be used to represent our latitude and longitude coordinates.

Let's take a look at how we can achieve this through the following steps:

  1. Launch the Xamarin Studio application, and ensure that the TrackMyWalks solution is loaded within the Xamarin Studio IDE.
  2. Next, create a new empty interface within the TrackMyWalks PCL project solution, under the Services folder.
  3. Then, choose the Empty Interface option located within the General section and enter IWalkLocationService for the name of the new interface file to be created, as shown in the following screenshot:

    Creating the Location Service Interface for the TrackMyWalks app

  4. Next, click on the New button to allow the wizard to proceed and create the new Empty Interface class file, as shown in the preceding screenshot.
  5. Our wizard has created our IWalkLocationService class file, which will be used by our ViewModels and content page Views to display geolocation coordinates. As we start to build the Location Service Interface class, you will see that it contains a couple of class members that will allow us to get the user's location as well as determining the distance that the user has travelled from point A to point B.
  6. It also contains a data structure IWalkLocationCoords that will be used to hold our latitude and longitude geolocation coordinates. To proceed with creating the base IWalkLocationService Interface, perform the following steps:
  7. Ensure that the IWalkLocationService.cs file is displayed within the code editor and enter the following code snippet:
            // 
            //  IWalkLocationService.cs 
            //  TrackMyWalks Location Service Interface 
            // 
            //  Created by Steven F. Daniel on 16/09/2016. 
            //  Copyright © 2016 GENIESOFT STUDIOS. All rights reserved. 
            // 
            using System; 
     
            namespace TrackMyWalks.Services 
            { 
                // Define our Walk Location Service Interface 
                public interface IWalkLocationService 
                { 
                    // Define our Location Service Instance Methods 
                    void GetMyLocation(); 
     
                    double GetDistanceTravelled(double lat, double lon); 
                    event EventHandler<IWalkLocationCoords> MyLocation; 
                } 
     
                // Walk Location Coordinates Obtained 
                public interface IWalkLocationCoords 
                { 
                    double latitude { get; set; } 
                    double longitude { get; set; } 
                } 
            }  
    

In the preceding code snippet, we start by defining the implementation for our IWalkLocationService, which will provide our TrackMyWalks app with the ability to get the user's current location, calculating the distance travelled from the user's current location to the trail goal. We also define an EventHandlermyLocation, which will be called whenever the platform obtains a new location.

The IWalkLocationCoords interface defines a class that contains two properties that will be used by our EventHandler to return the latitude and longitude values.

Note

An Interface contains only the methods, properties, and events signature definitions. Any class that implements the interface must implement all members of the interface that are specified in the interface definition.

Now that we have defined the property and method implementations that will be used by our IWalkLocationService, our next step will be to create the required Location Service class implementations for each of our platforms, as they are defined quite differently.

Creating the Location Service class for the Android platform

In this section, we will begin by setting up the basic structure for our TrackMyWalks.Droid solution to include the folder that will be used to represent our Services. Let's take a look at how we can achieve this through the following steps:

  1. Launch the Xamarin Studio application, and ensure that the TrackMyWalks solution is loaded within the Xamarin Studio IDE.
  2. Next, create a new folder within the TrackMyWalks.Droid project, called Services, as shown in the following screenshot:

    Creating the Location Service class for the Android platform

  3. Next, create an empty class within the Services folder. If you can't remember how to do this, you can refer to the section entitled Creating the Navigation Service Interface for the TrackMyWalks app, within Chapter 3 , Navigating within the MVVM Model - The Xamarin.Forms Way.
  4. Then, choose the Empty Class option located within the General section and enter WalkLocationService for the name of the new class file to be created, as shown in the following screenshot:

    Creating the Location Service class for the Android platform

  5. Next, click on the New button to allow the wizard to proceed and create the new empty class file, as shown in the preceding screenshot.

    Up until this point, all we have done is create our WalkLocationService class file. This class will be used and will act as the base Location Service class that will contain the functionality required by our ViewModels.

    As we start to build our Location Class, you will see that it contains a number of method members that will be used to help us get the user's current geolocation coordinates from their device, so that we can display this within each of our ViewModels, and it will implement the IWalkLocationService Interface.

To proceed with creating and implementing the base WalkLocationService class, perform the following steps:

  1. Ensure that the WalkLocationService.cs file is displayed within the code editor, and enter the highlighted code sections shown in the following code snippet:
            // 
            //  WalkLocationService.cs 
            //  TrackMyWalks Location Service Class (Android) 
            // 
            //  Created by Steven F. Daniel on 16/09/2016. 
            //  Copyright © 2016 GENIESOFT STUDIOS. All rights reserved. 
            // 
            using System; 
            using Android.Content; 
     
            using Android.Locations;
    
            using TrackMyWalks.Droid;
    
            using TrackMyWalks.Services; 
     
            using Xamarin.Forms; 
    
  2. First, we initialize our WalkLocationService class, which is to be marked as a dependency, by adding the Dependency metadata attribute just as we did for our navigation service. This is so that it can be resolved by the Xamarin.FormsDependencyService to allow it to find and use our method implementations as defined within our Interface. We also need to implement the IWalkCoordinates interface using the LocationEventArgs class that contains our latitude and longitude properties, which will be populated whenever a new location is obtained:
            [assembly: Xamarin.Forms.Dependency(
              typeof(WalkLocationService))] 
            namespace TrackMyWalks.Droid 
            { 
                // Event arguments containing latitude and longitude 
                public class Coordinates : EventArgs, IWalkCoordinates 
                { 
                    public double latitude { get; set; } 
                    public double longitude { get; set; } 
                } 
  3. Next, we need to modify our WalkLocationService class constructor signature, so that it inherits from the IWalkLocationService Interface class, as well as implementing an ILocationListener interface class, which will be used to indicate whenever the user's location changes, by implementing four methods-OnLocationChanged, OnProviderDisabled, OnProviderEnabled, and OnStatusChanged:
            public class WalkLocationService : Java.Lang.Object,
              IWalkLocationService, ILocationListener {
                  LocationManager locationManager;
                  Location newLocation; 
     
                  // Create the four methods for our LocationListener  
                  // interface. 
                  public void OnProviderDisabled(string provider) { } 
                  public void OnProviderEnabled(string provider) { } 
                  public void OnStatusChanged(string provider,
                  Availability status, Android.OS.Bundle extras) { } 
    

    Note

    We need to ensure that our WalkLocationService class inherits from the Android-specific Java.Lang.Object class, so that we can provide access to the system location services, in order to obtain periodic updates on the device's geographical location.

    Whenever your classes inherit from the ILocationListener API, the ILocationListener Interface supports several different method types, which are explained in the following table:

    Method name

    Description

    OnProviderDisabled

    This method is fired up whenever the location service provider has been disabled by the user.

    OnProviderEnabled

    This method is fired up whenever the location service provider has been enabled by the user.

    OnStatusChanged

    This method is fired up whenever the location service provider status has been changed, that is, the location services have been disabled by the user.

    OnLocationChanged

    This method is fired up whenever a change in location has been detected.

  4. Then, we need to set up an EventHandler delegate object that will be called whenever the location has been obtained or changed:
            // Set up our EventHandler delegate that is called  
            // whenever a location has been obtained 
            public event EventHandler<IWalkCoordinates> MyLocation; 
     
    
  5. Next, we create the OnLocationChanged method that will be fired up whenever the user's location has been changed since the last time. This method accepts the user's current location, and we need to add a check to ensure that our location is not empty prior to creating an instance of our Coordinates class data structure, and then assigning the new location details for our latitude and longitude, before finally passing a copy of the Coordinates to the MyLocationEventHandler:
            // Fired whenever there is a change in location 
            public void OnLocationChanged(Location location) 
            { 
                if (location != null) 
     
                { 
                    // Create an instance of our Coordinates 
                    var coords = new Coordinates(); 
                     
                    // Assign our user's Latitude and Longitude   
                    // values 
                    coords.latitude = location.Latitude; 
                    coords.longitude = location.Longitude; 
     
                    // Update our new location to store the  
                    // new details. 
                    newLocation = new Location("Point A"); 
                    newLocation.Latitude = coords.latitude; 
                    newLocation.Longitude = coords.longitude; 
     
                    // Pass the new location details to our  
                    // Location Service EventHandler. 
                    MyLocation(this, coords); 
                }; 
            }  
    
  6. Then, we create the GetMyLocation method that will be used to start getting the user's location. We then set up our locationManager to request location updates. This is because, when dealing with Android, these services require a Context object in order for them to work. Xamarin.Forms comes with the Forms.Context object, and we use the NetworkProvider method to obtain the location using the cellular network and Wi-Fi. Consider the following code:
            // Method to call to start getting location 
            public void GetMyLocation() 
            { 
               locationManager = (LocationManager) 
     
               long minTime = 0;      // Time in milliseconds 
               float minDistance = 0; // Distance in metres 
               Forms.Context.GetSystemService(Context.LocationService); 
               locationManager.RequestLocationUpdates( 
                        LocationManager.NetworkProvider, 
                        minTime, 
                        minDistance, 
                        this); 
            } 
    
  7. Next, create the GetDistanceTravelled method, which accepts two parameters containing our latitude and longitude values. We create a new location, and set the Latitude and Longitude values that contain the ending coordinates for our trail. We then declare a variable distance, which calls the DistanceTo method on our newLocation object, to determine our current distance from the end goal. We divide the distance by 1000 to convert the distance travelled to meters:
            // Calculates the distance between two points 
            public double GetDistanceTravelled(double lat, double lon) 
           { 
               Location locationB = new Location("Trail Finish"); 
               locationB.Latitude = lat; 
               locationB.Longitude = lon; 
     
               float distance = newLocation.DistanceTo(locationB) / 1000; 
               return distance; 
            }  
    
  8. Finally, create the WalkLocationService class finalizer; this will be used to stop all update listener events when our class has been set to null.
            // Stop the location update when the object is set to null 
             ~WalkLocationService()  
             { 
                locationManager.RemoveUpdates(this); 
             } 
            } 
           } 
    

Now that we have created the WalkLocationService class for the Android portion of our TrackMyWalks app, our next step is to create the same class for the iOS portion, which will be covered in the next section.

Creating the Location Service class for the iOS platform

In the previous section, we created the class for our WalkLocationService. We also defined a number of different methods that will be used to provide location-based information within our MVVM ViewModel.

In this section, we will build the iOS portion for our WalkLocationService, just like we did for our Android portion. You will notice that the implementations for both of these classes are quite similar; however, these implement different methods, as you will see once we start implementing them.

Let's take a look at how we can achieve this through the following steps:

  1. Create an empty class within the Services folder for our TrackMyWalks.iOS project, and enter WalkLocationService for the name of the new class file to create.
  2. Once you have created the WalkLocationService class file, ensure that the WalkLocationService.cs file is displayed within the code editor and enter the highlighted code sections shown in the following code snippet:
            // 
            //  WalkLocationService.cs 
            //  TrackMyWalks Location Service Class (iOS) 
            // 
            //  Created by Steven F. Daniel on 16/09/2016. 
            //  Copyright © 2016 GENIESOFT STUDIOS. All rights reserved. 
            // 
            using System; 
            using CoreLocation;
    
            using TrackMyWalks.iOS;
    
            using TrackMyWalks.Services;
    
            using UIKit;
    
  3. Next, we initialize our WalkLocationService class, which is to be marked as a dependency, by adding the Dependency metadata attribute just as we did for our navigation service. This is so that it can be resolved by the Xamarin.FormsDependencyService to allow it to find and use our method implementations as defined within our Interface. We also need to implement the IWalkCoordinates interface using the Coordinates class, which contains the latitude and longitude properties that will be populated whenever a new location is obtained:
            [assembly: Xamarin.Forms.Dependency(typeof(WalkLocationService))] 
            namespace TrackMyWalks.iOS 
            { 
                // Event arguments containing latitude and longitude 
                public class Coordinates : EventArgs, IWalkCoordinates 
                { 
                    public double latitude { get; set; } 
                    public double longitude { get; set; } 
                }  
    
  4. Then, we need to modify our WalkLocationService class constructor signature, so that it inherits from the IWalkLocationService Interface class. We also need to declare our locationManager object, which will be used to obtain the user's location. We also create a newLocation object of type CLLocation, which will be used to convert the latitude and longitude coordinates from the locationManager object into a CLLocation object:
            // Walk Location Service class that inherits from our  
            // IWalkLocationService interface 
            public class WalkLocationService : IWalkLocationService 
            { 
                // Declare our Location Manager 
                CLLocationManager locationManager; 
                CLLocation newLocation; 
    
  5. Next, we need to set up an EventHandler delegate object that will be called whenever the location has been obtained or changed:
            // Set up our EventHandler delegate that is called  
            // whenever a location has been obtained 
            public event EventHandler<IWalkCoordinates> MyLocation; 
  6. Then, we create the GetMyLocation method that will be used to start getting the user's location. Next, we set up our locationManager using the iOS CLLocationManager class to allow our class to request location updates. We then perform a check, using the LocationServicesEnabled property of the CLLocationManager class, to ensure that location services have been enabled on the user's device.
  7. This is a good check to enforce prior to requesting the getting of the user's location, and if our CLLocationManager class determines that location services have been disabled, we display a message to the user, using the UIAlertView class:
            // Method to call to start getting location 
            public void GetMyLocation() 
            { 
                locationManager = new CLLocationManager(); 
     
                // Check to see if we have location services  
                // enabled 
                if (CLLocationManager.LocationServicesEnabled) 
                { 
                    // Set the desired accuracy, in meters 
                    locationManager.DesiredAccuracy = 1;   
     
                    // CLLocationManagerDelegate Methods 
    
  8. Next, we set up an event handler, LocationsUpdated, that will start firing up whenever there is a change in the user's current location, and we call the locationUpdated instance method, passing in the location geo-coordinates:
            // Fired whenever there is a change in  
            // location 
            locationManager.LocationsUpdated += (object sender,
            CLLocationsUpdatedEventArgs e) => 
            { 
                locationUpdated(e); 
            }; 
    
  9. Then, we set up an event handler, AuthorizationChanged, which will be called whenever it detects a change made to the authorization of location-based services. For example, this will be called if, for some reason, the user decides to turn off location-based services:
            // This event gets fired whenever it  
            // detects a change, i.e., if the user  
            // has turned off or disabled location  
            // based services. 
              locationManager.AuthorizationChanged += (object
              sender, CLAuthorizationChangedEventArgs e) => 
              { 
                  didAuthorizationChange(e); 
     
                  // Perform location changes within the  
                  // foreground. 
                  locationManager.RequestWhenInUseAuthorization(); 
              }; 
             } 
            }  
    
  10. Next, we create the locationUpdated method that will be fired up whenever the user's location has been changed since the last time. This method accepts the user's current location, which is defined by the CLLocationsUpdatedEventArgs in a variable called e. Next, we create an instance of our Coordinates class data structure, and then assign the new location details for the latitude and longitude, before finally passing a copy of the Coordinates to the MyLocationEventHandler:
            // Method is called whenever there is a change in  
            // location 
            public void locationUpdated(CLLocationsUpdatedEventArgs e) 
            { 
                // Create our Location Coordinates      
                var coords = new Coordinates(); 
     
                // Get a list of our locations found 
                var locations = e.Locations; 
     
                // Extract our Latitude and Longitude values  
                // from our locations array. 
                coords.latitude = locations[locations.Length - 1].
                  Coordinate.Latitude; 
                coords.longitude = locations[locations.Length - 1].
                  Coordinate.Longitude; 
     
                // Then, convert both our Latitude and Longitude  
                // values to a CLLocation object. 
                newLocation = new CLLocation(coords.latitude, 
                  coords.longitude); 
                MyLocation(this, coords); 
             }  
  11. Then, we create the didAuthorizationChange method, which will be called whenever the CLLocationManager delegate detects a change in the authorization status; you will be notified about those changes. To handle any changes in the authorization status while your app is running, and to prevent your application from crashing unexpectedly, you will need to ensure that the proper authorization is handled accordingly.
  12. If we detect that the user has restricted or denied access to location services on the device, we will need to alert the user to this, and display an alert dialog popup:
            public void didAuthorizationChange( 
                    CLAuthorizationChangedEventArgs authStatus) 
            { 
                switch (authStatus.Status) { 
                    case CLAuthorizationStatus.AuthorizedAlways: 
                        locationManager.RequestAlwaysAuthorization(); 
                        break; 
                    case CLAuthorizationStatus.AuthorizedWhenInUse: 
                        locationManager.StartUpdatingLocation(); 
                        break; 
                    case CLAuthorizationStatus.Denied: 
                        UIAlertView alert = new UIAlertView(); 
     
                        alert.Title = "Location Services Disabled"; 
                        alert.AddButton("OK"); 
                        alert.AddButton("Cancel"); 
                        alert.Message = "Enable locations for this app  
                        via
    the Settings app on your iPhone"; 
                        alert.AlertViewStyle = UIAlertViewStyle.Default; 
     
                        alert.Show(); 
     
                        alert.Clicked += (object s,  
                                          UIButtonEventArgs ev) => 
                        { 
                            var Button = ev.ButtonIndex; 
                        }; 
                        break; 
                    default: 
                        break; 
                } 
            }  
  13. The didAuthorizationChange method contains a number of authorization status codes, and these are explained, along with their descriptions, in the following table:

    Authorization status

    Description

    .AuthorizedAlways or .AuthorizedWhenInUse

    Either of these cases can occur whenever the user has granted access for your app to use location services. These statuses are both mutually exclusive, as you can only receive one type of authorization at a time.

    .NotDetermined

    This generally happens whenever the user hasn't made a choice regarding whether your iOS app can begin accepting location updates, and can be caused if the user has installed your app for the first time and has not run it yet.

    .Restricted or .Denied

    You will generally receive this type of authorization status state whenever the user has explicitly denied access to your app for the use of location services, or when location services are currently unavailable.

    Note

    If you are interested in finding out more information on the CLLocationManager class, please refer to the Xamarin developer documentation located at https://developer.xamarin.com/api/type/CoreLocation.CLLocationManager/ .

  14. Next, create the GetDistanceTravelled method, which accepts two parameters containing our lat and lon values, and declares a variable distance, which calls the DistanceFrom method on our newLocation object, to determine our current distance from the end goal. We divide the distance by 1000 to convert the distance travelled to meters:
            // Calculates the distance between two points 
            public double GetDistanceTravelled(double lat, double lon) 
            { 
                // Get the distance travelled from  
                // current location to the previous location. 
                var distance = newLocation.DistanceFrom(new  
                               CLLocation(lat, lon)) / 1000; 
                return distance; 
            }  
  15. Finally, create the WalkLocationService class finalizer, which will be used to stop all update listener events and free the memory used when our class has been set to null:
            // Stops performing location updates when the  
            // object has been set to null. 
              ~WalkLocationService() 
              { 
                  locationManager.StopUpdatingLocation(); 
              } 
             } 
            } 
    

Now that we have created the WalkLocationService class for the iOS portion of our TrackMyWalks app, our next step is to learn how to provide our iOS app with the functionality to perform continuous location updates in the background.

Enabling background updates and getting the user's current location

This relates to working with background location updates to continuously monitor changes to the user location in the background.

Let's take a look at how we can achieve this through the following steps:

  1. Double-click on the Info.plist file, which is contained within the TrackMyWalks.iOS project, and ensure that the Application tab is showing.
  2. Next, scroll down to the bottom of the page and select Enable Background Modes from under the Background Modes section to enable background updates.
  3. Then, ensure that the Location Updates option has been selected, so that Xcode can provision your app to monitor location-based updates in the background:

    Enabling background updates and getting the user's current location

  4. Now that we have modified our TrackMyWalks.iOS project to monitor location updates in the background, we need to do one more thing and tell Xcode to handle Location updates. So let's do that now.
  5. Ensure that the Info.plist file is displayed within the Xamarin IDE, and that the Source tab is showing.
  6. Next, create the keys NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription by clicking within the Add new entry section of the Info.plist.

    Enabling background updates and getting the user's current location

  7. Then, add Track My Walks would like to obtain your location as the string description for the Value field, as shown in the preceding screenshot.

Next, we need to provide our app with the ability to monitor location updates in the background for our TrackMyWalks.iOS project. Let's take a look at how we can achieve this through the following steps:

  1. Ensure that the WalkLocationService.cs file is displayed within the code editor.
  2. Next, locate the GetMyLocation method and enter the following code snippet:
            // Method to call to start getting location 
            public void GetMyLocation() 
            { 
                locationManager = new CLLocationManager(); 
     
                // Check to see if we have location services  
                // enabled 
                if (CLLocationManager.LocationServicesEnabled) 
                { 
                    // Set the desired accuracy, in meters 
                    locationManager.DesiredAccuracy = 1; 
     
                    // iOS 8 has additional permission  
                    // requirements 
                    if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0)) 
                    { 
                     // Perform location changes within the  
                     // background 
                        locationManager.RequestAlwaysAuthorization(); 
                    } 
     
                    // iOS 9, comes with a new method that  
                    // allows us to receive location updates  
                    // within the back, when the app has  
                    // suspended. 
                    if (UIDevice.CurrentDevice.CheckSystemVersion(9, 0)) 
                    { 
                        locationManager.AllowsBackgroundLocationUpdates = true; 
                    } 
     
                    // CLLocationManagerDelegate Methods 
                    // Fired whenever there is a change in  
                    // location 
                    locationManager.LocationsUpdated += (object sender,  
                    CLLocationsUpdatedEventArgs e) => 
                    { 
                        locationUpdated(e); 
                    }; 
     
                    // This event gets fired whenever it  
                    // detects a change, i.e., if the user has  
                    // turned off or disabled Location Based  
                    // Services. 
                    locationManager.AuthorizationChanged += (object  
                    sender, CLAuthorizationChangedEventArgs e) => 
                    { 
                        didAuthorizationChange(e); 
     
                        // Perform location changes within  
                    // the foreground. 
                        locationManager.RequestWhenInUseAuthorization(); 
                    }; 
                } 
            }  
    

In the preceding code snippet, we check the iOS version currently running on the user's device, and use the RequestAlwaysAuthorization method call on the locationManager class to request the user's permission to obtain their current location. In iOS 9, Apple decided to add a new method called AllowsBackgroundLocationUpdates, which allows the handling of background location updates. Next, we also need to configure our Android portion of our TrackMyWalks.Droid project by modifying the AndroidManifest.xml file.

Let's take a look at how we can achieve this through the following steps:

  1. Double-click on the AndroidManifest.xml file, which is contained within the TrackMyWalks.Droid project, and ensure that the Source tab is selected, as shown in the following screenshot:

    Enabling background updates and getting the user's current location

  2. Ensure that the AndroidManifest.xml file is displayed within the code editor, and enter the following highlighted code sections:
           <?xml version="1.0" encoding="utf-8"?> 
            <manifest xmlns:android="http://schemas.
              android.com/apk/res/android"
              android:versionCode="1" android:versionName="1.0" 
              package="com.geniesoftstudios.trackmywalks"> 
            <uses-sdk android:minSdkVersion="15" /> 
            <uses-permission android:name="android.
              permission.ACCESS_FINE_LOCATION" />
    
            <uses-permission android:name="android.
              permission.ACCESS_COARSE_LOCATION" />
    
            <uses-permission android:name="android.
              permission.INTERNET" /> 
            <application android:label="TrackMyWalks"> 
            </application> 
            </manifest>  
    

In the preceding code snippet, we begin by adding permissions that will allow our TrackMyWalks Android app to access location information for location updates, as well as the Internet. Google is pretty strict about which permissions are allowed, and these must be approved prior to your app being accepted into the Google Play Store.

Updating the WalkEntryViewModel to use the location service

Now that we have created our WalkLocationService for both our Android and iOS implementations, we need to begin modifying our ViewModel, which will be used by our WalkEntry page, to take advantage of our Location Service.

Let's take a look at how we can achieve this through the following steps:

  1. Ensure that the WalkEntryViewModel.cs file is displayed within the code editor, and enter in the following highlighted code sections:
            // 
            //  WalkEntryViewModel.cs 
            //  TrackMyWalks ViewModels 
            // 
            //  Created by Steven F. Daniel on 22/08/2016. 
            //  Copyright © 2016 GENIESOFT STUDIOS. All rights reserved. 
            // 
                using System; 
                using System.Diagnostics.Contracts; 
                using System.Threading.Tasks; 
                using TrackMyWalks.Models; 
                using TrackMyWalks.Services; 
                using TrackMyWalks.ViewModels; 
                using Xamarin.Forms; 
     
                namespace TrackMyWalks.ViewModels 
                { 
                    public class WalkEntryViewModel : WalkBaseViewModel 
                    { 
    
  2. Next, we declare a locationService variable that will be used to provide a reference to our IWalkLocationService and provide our class with a reference to the EventHandler, which is contained within our IWalkLocationService interface class; this will contain our location coordinate information whenever the location changes. To proceed, enter the following highlighted code sections:
            IWalkLocationService locationService; 
     
            string _title; 
            public string Title 
            { 
                get { return _title; } 
                set 
                { 
                    _title = value; 
                    OnPropertyChanged(); 
                    SaveCommand.ChangeCanExecute(); 
                } 
            } 
     
            string _notes; 
            public string Notes 
            { 
                get { return _notes; } 
                set 
                { 
                    _notes = value; 
                    OnPropertyChanged(); 
                } 
             } 
     
             double _latitude; 
             public double Latitude 
             { 
                 get { return _latitude; } 
                 set 
                 { 
                     _latitude = value; 
                     OnPropertyChanged(); 
                 } 
             } 
     
             double _longitude; 
             public double Longitude 
             { 
                 get { return _longitude; } 
                 set 
                 { 
                     _longitude = value; 
                     OnPropertyChanged(); 
                 } 
             } 
     
             double _kilometers; 
             public double Kilometers 
             { 
                 get { return _kilometers; } 
                 set 
                 { 
                     _kilometers = value; 
                     OnPropertyChanged(); 
                 } 
             }  
      
             string _difficulty; 
             public string Difficulty 
             { 
                 get { return _difficulty; } 
                 set 
                 { 
                     _difficulty = value; 
                     OnPropertyChanged(); 
                 } 
             } 
     
             double _distance; 
             public double Distance 
             { 
                 get { return _distance; } 
                 set 
                 { 
                     _distance = value; 
                     OnPropertyChanged(); 
                 } 
             } 
     
             string _imageUrl; 
             public string ImageUrl 
             { 
                 get { return _imageUrl; } 
                 set 
                 { 
                     _imageUrl = value; 
                     OnPropertyChanged(); 
                 } 
             } 
    
  3. In our next step, we will need to modify the contents of our WalksEntryViewModel class constructor to declare and initialize our locationService variable, which will include our IWalkLocationService constructor that is retrieved from the Xamarin.FormsDependencyService class. We then proceed to call the MyLocation method on our EventHandler, which is defined within the IWalkLocationService interface; this will return the geographical location coordinates defined by their Latitude and Longitude values.
  4. Locate the WalksEntryViewModel class constructor and enter the following highlighted code sections:
            public WalkEntryViewModel(IWalkNavService navService) :
              base(navService) 
            { 
                Title = "New Walk"; 
                Difficulty = "Easy"; 
                Distance = 1.0; 
     
                // Get our Location Service
               locationService
    = DependencyService.Get<IWalkLocationService>();
    
                // Check to ensure that we have a value
     
                // for our object
    
                if (locationService != null)
    
                {
    
                    locationService.MyLocation += (object sender,
                      IWalkCoordinates e) =>
    
                    {
                        // Obtain our Latitude and Longitude 
                        // coordinates
                        Latitude = e.latitude;
                        Longitude = e.longitude;
                    };
                }
    
                // Call our Service to get our GPS location
    
                locationService.GetMyLocation();
    
            } 
            Command _saveCommand; 
            public Command SaveCommand 
            { 
                get 
                { 
                    return _saveCommand ?? (
                     _saveCommand = new Command(async () => 
                    await ExecuteSaveCommand(), ValidateFormDetails)); 
                } 
            } 
     
            async Task ExecuteSaveCommand() 
            { 
                var newWalkItem = new WalkEntries 
                { 
                    Title = this.Title, 
                    Notes = this.Notes, 
                    Latitude = this.Latitude, 
                    Longitude = this.Longitude, 
                    Kilometers = this.Kilometers, 
                    Difficulty = this.Difficulty, 
                    Distance = this.Distance, 
                    ImageUrl = this.ImageUrl 
                }; 
    
  5. Then, we locate and modify the ExecuteSaveCommand instance method to free the memory used by our locationService variable when the Save button is pressed. This is achieved by setting this to null, which in turn will call the ~GetMyLocation() within the iOS and Android class de-constructor. Proceed to enter the following highlighted code sections:
                // Upon exiting our New Walk Entry Page, 
                // we need to stop checking for location 
                // updates
                 locationService = null;
    
                // Here, we will save the details entered  
                // in a later chapter. 
                await NavService.PreviousPage(); 
             } 
     
             // method to check for any form errors 
             bool ValidateFormDetails() 
             { 
                 return !string.IsNullOrWhiteSpace(Title); 
             } 
     
              public override async Task Init() 
              { 
                  await Task.Factory.StartNew(() => 
                  { 
                      Title = "New Walk"; 
                      Difficulty = "Easy"; 
                      Distance = 1.0; 
                  }); 
              } 
             } 
            }  
    

In this section, we looked at the steps involved in modifying our WalkEntryViewModel so that it can take advantage of our WalkLocationService.

We then declared a locationService variable that will be used to provide a reference to our IWalkLocationService and provide our class with a reference to the EventHandler, which is contained within our IWalkLocationService interface class; this will contain our location coordinate information whenever the location changes.

We also modified our WalkEntryViewModel class constructor to initialize our locationService variable, to point to the IWalkLocationService constructor that is retrieved from the Xamarin.FormsDependencyService class, which needs to be done prior to calling the MyLocation method on our EventHandler, so that it can return the geographical location coordinates, defined by their Latitude and Longitude values.

Finally, we set our locationService object to null to stop checking for location updates.

Updating the DistanceTravelledViewModel to use the location service

Now that we have modified our MVVM ViewModel for our WalkEntryViewModel, our next step is to begin modifying our DistTravelledViewModel to take advantage of our WalkLocationService class, that will be used to calculate the distance travelled, and save this information back to our DistTravelledViewModel.

Let's take a look at how we can achieve this through the following steps:

  1. Ensure that the DistTravelledViewModel.cs file is displayed within the code editor.
            // 
            //  DistTravelledViewModel.cs 
            //  TrackMyWalks ViewModels 
            // 
            //  Created by Steven F. Daniel on 22/08/2016. 
            //  Copyright © 2016 GENIESOFT STUDIOS. All rights reserved. 
            // 
            using System; 
            using System.Threading.Tasks; 
            using TrackMyWalks.Models; 
            using TrackMyWalks.Services; 
            using TrackMyWalks.ViewModels; 
            using Xamarin.Forms; 
     
            namespace TrackMyWalks.ViewModels 
            { 
                public class DistTravelledViewModel :  
                  WalkBaseViewModel<WalkEntries> 
                { 
                    WalkEntries _walkEntry; 
    
  2. Next, we declared a locationService variable that will be used to provide a reference to our IWalkLocationService and provide our class with a reference to the EventHandler, which is contained within our IWalkLocationService interface class; this will contain our location coordinate information whenever the location changes. To proceed, enter the following highlighted code sections:
            IWalkLocationService locationService;  
     
            public WalkEntries WalkEntry 
            { 
                get { return _walkEntry; } 
                set 
                { 
                    _walkEntry = value; 
                    OnPropertyChanged(); 
                } 
            } 
     
            double _travelled; 
            public double Travelled 
            { 
                get { return _travelled; } 
                set 
                { 
                    _travelled = value; 
                    OnPropertyChanged(); 
                } 
     
            } 
     
            double _hours; 
            public double Hours 
            { 
                get { return _hours; } 
                set 
                { 
                    _hours = value; 
                    OnPropertyChanged(); 
                } 
            } 
     
            double _minutes; 
            public double Minutes 
            { 
                get { return _minutes; } 
                set 
                { 
                    _minutes = value; 
                    OnPropertyChanged(); 
                } 
            } 
     
            double _seconds; 
            public double Seconds 
            { 
                get { return _seconds; } 
                set 
                { 
                    _seconds = value; 
                    OnPropertyChanged(); 
                } 
            } 
     
            public string TimeTaken 
            { 
                get 
                { 
                    return string.Format("{0:00}:{1:00}:{2:00}",  
                    this.Hours, this.Minutes, this.Seconds); 
                } 
            } 
    
  3. Next, we need to modify the DistTravelledViewModel class constructor to declare and initialize our locationService variable; this will include our IWalkLocationService constructor, which is retrieved from the Xamarin.FormsDependencyService class. We then proceed to call the MyLocation method on our EventHandler, which is defined within the IWalkLocationService interface; this will return the geographical location coordinates, defined by their Latitude and Longitude values.
  4. Locate the DistTravelledViewModel class constructor and enter the following highlighted code:
            public DistTravelledViewModel(IWalkNavService navService) :  
            base(navService) 
            { 
                this.Hours = 0; 
                this.Minutes = 0; 
                this.Seconds = 0; 
                this.Travelled = 100; 
     
                locationService = DependencyService.Get
                  <IWalkLocationService>();
    
                locationService.MyLocation += (object sender,
    
                  IWalkCoordinates e) =>
    
                {
    
                    // Determine Distance Travelled
    
                    if (_walkEntry != null)
    
                    {
    
                        var distance = locationService.GetDistanceTravelled(
    
                          _walkEntry.Latitude, _walkEntry.Longitude);
    
                        this.Travelled = distance;
    
                    }
    
                };
    
                locationService.GetMyLocation();  
             } 
    
  5. Then, we create the Init method within our DistTravelledViewModel, which will be used to initialize the DistanceTravelled when it is called. We need to specify and use the Task.Factory.StartNew method to give the ViewModel enough time to display the page on screen, prior to initializing the ContentPage contents and using the passed-in walkDetails for our model:
            public override async Task Init(WalkEntries walkDetails) 
            { 
                await Task.Factory.StartNew(() => 
                { 
                    WalkEntry = walkDetails; 
                }); 
            } 
    
  6. Next, we need to create the BackToMainPage command property that will be used to bind to the End This Trail button, which will run an action upon being pressed. This action will execute a class instance method to determine whether the command can be executed.
  7. If the command can be executed, a call will be made to the BackToMainPage method on the NavService navigation service class to take the user back to the TrackMyWalks main page; this is done by removing all existing ViewModels within the NavigationStack, except the first page:
            Command _mainPage; 
            public Command BackToMainPage 
            { 
                get 
                { 
                  return _mainPage ?? (_mainPage = new Command(
                    async () => await  
                  NavService.BackToMainPage())); 
                } 
             } 
            } 
           }  
    

In this section, we looked at the steps involved in modifying our DistanceTravelledViewModel so that it can take advantage of our WalkLocationService. We then declared a locationService variable that will be used to provide a reference to our IWalkLocationService and provide our class with a reference to the EventHandler, which is contained within our IWalkLocationService interface class; this will contain our location coordinate information whenever the location changes.

We also modified our DistTravelledViewModel class constructor to initialize our locationService variable to point to the IWalkLocationService constructor that is retrieved from the Xamarin.Forms DependencyService class; this needs to be done prior to calling the MyLocation method on our EventHandler, so that it can return the geographical location coordinates, defined by their Latitude and Longitude values.

Updating the SplashPage to register our ViewModels

In this section, we need to update our SplashPage to register our ViewModels for our Android platform; this will involve creating a new instance of the navigation service, and registering the application ContentPage and ViewModel mappings:

  1. Ensure that the SplashPage.cs file is displayed within the code editor, and enter in the following highlighted code sections:
            // 
            //  SplashPage.cs 
            //  TrackMyWalks 
            // 
            //  Created by Steven F. Daniel on 04/08/2016. 
            //  Copyright © 2016 GENIESOFT STUDIOS. All rights reserved. 
            // 
            using System; 
            using System.Threading.Tasks; 
            using TrackMyWalks.Services;
    
            using TrackMyWalks.ViewModels; 
            using Xamarin.Forms; 
     
            namespace TrackMyWalks 
            { 
                public class SplashPage : ContentPage 
                { 
                    public SplashPage() 
                    { 
                        AbsoluteLayout splashLayout = new AbsoluteLayout 
                        { 
                            HeightRequest = 600 
                        }; 
     
                        var image = new Image() 
                        { 
                            Source = ImageSource.FromFile("icon.png"), 
                            Aspect = Aspect.AspectFill, 
                        }; 
                        AbsoluteLayout.SetLayoutFlags(image,
                          AbsoluteLayoutFlags.All); 
                        AbsoluteLayout.SetLayoutBounds(image,
                          new Rectangle(0f, 0f, 1f, 1f)); 
     
                        splashLayout.Children.Add(image); 
     
                        Content = new StackLayout() 
                        { 
                            Children = { splashLayout } 
                        }; 
                    } 
    
  2. Next, locate the OnAppearing method and enter the following highlighted code sections:
            protected override async void OnAppearing() 
            { 
                base.OnAppearing(); 
     
                // Delay for a few seconds on the splash screen 
                await Task.Delay(3000); 
     
                // Instantiate a NavigationPage with the  
                // MainPage 
                var navPage = new NavigationPage(new WalksPage() 
                { 
                    Title = "Track My Walks - Android" 
                }); 
     
                 navPage.BarBackgroundColor = Color.FromHex("#4C5678");
    
                 navPage.BarTextColor = Color.White;
    
                 // Declare our DependencyService Interface
    
                 var navService = DependencyService.Get<IWalkNavService>()
    
                   as WalkNavService;
    
                 navService.navigation = navPage.Navigation;
    
                 // Register our View Model Mappings between
       
                 // our ViewModels and Views (Pages).
    navService.
                    RegisterViewMapping(typeof(WalksPageViewModel),
    
                       typeof(WalksPage));
    
                      navService.RegisterViewMapping(
                      typeof(WalkEntryViewModel),
    typeof(WalkEntryPage));
    
                      navService.RegisterViewMapping(
                      typeof(WalksTrailViewModel), 
    typeof(WalkTrailPage));
    
                      navService.RegisterViewMapping(
                        typeof(DistTravelledViewModel)
    , 
                          typeof(DistanceTravelledPage));
    
                      // Set the MainPage to be our Walks Navigation Page
    
                      Application.Current.MainPage = navPage; 
                  } 
               } 
             }  

In the preceding code snippet, we begin by customizing our NavigationBar, by setting the Background and TextColor attributes, and then declaring a variable navService that points to an instance of our navigation service as defined by our assembly attribute for our Xamarin.FormsDependencyService, which is declared in our WalkNavService class.

In our next step, we set the navService.navigation property to point to an instance of the NavigationPage class that our walksPage.navigation property currently points to, and this will be used as the main root page.

Finally, we call the RegisterViewMapping instance method for each of our ViewModels and specify the associated ContentPage for each.

Updating the MainActivity class to use Xamarin.Forms.Maps

In this section, we need to update our MainActivity Class to integrate with the Xamarin.Forms.Maps package for our Android platform, so that our ViewModels can use this to display mapping capabilities:

  1. Open the MainActivity.cs file and ensure that it is displayed within the code editor.
  2. Next, locate the OnCreate method and enter the following highlighted code sections:
            // 
            //  MainActivity.cs 
            //  TrackMyWalks 
            // 
            //  Created by Steven F. Daniel on 04/08/2016. 
            //  Copyright © 2016 GENIESOFT STUDIOS. All rights reserved. 
            // 
            using Android.App; 
            using Android.Content.PM; 
            using Android.OS; 
     
            namespace TrackMyWalks.Droid 
            { 
                [Activity(Label = "TrackMyWalks.Droid",
                Icon = "@drawable/icon", Theme = "@style/MyTheme",
                MainLauncher = true, ConfigurationChanges = 
                  ConfigChanges.ScreenSize |
                   ConfigChanges.Orientation)] 
                public class MainActivity :
                 global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity 
                { 
                    protected override void OnCreate(Bundle savedInstanceState) 
                    { 
                        TabLayoutResource = Resource.Layout.Tabbar; 
                        ToolbarResource = Resource.Layout.Toolbar; 
     
                        base.OnCreate(savedInstanceState); 
     
                        global::Xamarin.Forms.Forms.Init(this,
                          savedInstanceState); 
     
                        // Integrate Xamarin Forms Maps
    
                        Xamarin.FormsMaps.Init(this, savedInstanceState); 
                        LoadApplication(new App()); 
                    } 
                } 
            }  
    

In the preceding code snippet, we begin by initializing our MainActivity class to use the Xamarin.Forms.Maps library, so that our TrackMyWalks solution can use the maps. If this is omitted from the class, the DistanceTravelledPage content page will not display the map, and therefore will not work as expected.

Updating the Xamarin.Forms App class to use platform specifics

In this section, we need to update our Xamarin.Forms.App class by modifying the constructor in the main App class to set the MainPage instance, depending on the TargetPlatform that our device is running. This is extremely easy when using Xamarin.Forms.

Let's take a look at how we can achieve this by following these steps:

  1. Open the TrackMyWalks.cs file and ensure that it is displayed within the code editor.
  2. Next, locate the App method and enter the following highlighted code sections:
            // 
            //  TrackMyWalks.cs 
            //  TrackMyWalks 
            // 
            //  Created by Steven F. Daniel on 04/08/2016. 
            //  Copyright © 2016 GENIESOFT STUDIOS. All rights reserved. 
            // 
            using TrackMyWalks.Services;
    
            using TrackMyWalks.ViewModels; 
            using Xamarin.Forms; 
     
            namespace TrackMyWalks 
            { 
                public class App : Application 
                { 
                    public App() 
                    { 
                        // Check the Device Target OS Platform 
                        if (Device.OS == TargetPlatform.Android) 
                        { 
                            // The root page of your application  
                            MainPage = new SplashPage(); 
                        } 
                        else if (Device.OS == TargetPlatform.iOS)  
                        { 
                            // The root page of your application
                             var walksPage = new NavigationPage(new WalksPage() 
                            { 
                                Title = "Track My Walks - iOS" 
                            }); 
     
                            // Set the NavigationBar TextColor and  
                            // Background Color  
                             walksPage.BarBackgroundColor = 
                               Color.FromHex("#440099");
    
                             walksPage.BarTextColor = Color.White;  
                            // Declare our DependencyService Interface  
                            var navService = DependencyService. 
                              Get<IWalkNavService>() as WalkNavService; 
                            navService.navigation = walksPage.Navigation; 
     
                            // Register our View Model Mappings  
                            // between our ViewModels and Views (Pages)  
                            navService.RegisterViewMapping( 
                            typeof(WalksPageViewModel), typeof(WalksPage)); 
                            navService.RegisterViewMapping( 
                            typeof(WalkEntryViewModel), typeof(WalkEntryPage)); 
                            navService.RegisterViewMapping( 
                            typeof(WalksTrailViewModel), 
                              typeof(WalkTrailPage)); 
                            navService.RegisterViewMapping(typeof(
                            DistTravelledViewModel),
                              typeof(DistanceTravelledPage)); 
     
                            // Set the MainPage to be our  
                            // Walks Navigation Page  
                            MainPage = walksPage; 
                        } 
                    } 
                    protected override void OnStart() 
                    { 
                        // Handle when your app starts 
                    } 
     
                    protected override void OnSleep() 
                    { 
                        // Handle when your app sleeps 
                    } 
     
                    protected override void OnResume() 
                    { 
                        // Handle when your app resumes 
                    } 
                } 
            }  
    

In the preceding code snippet, we use the TargetPlatform class that comes as part of the Xamarin.Forms.Core library, and we check this against the Device.OS class and handle it accordingly.

The TargetPlatform method contains a number platform codes, which are explained along with their descriptions in the following table:

Platform name

Description

Android

This indicates that the Xamarin.Forms platform is running on a device that is running the Android operating system.

iOS

This indicates that the Xamarin.Forms platform is running on a device that is running the Apple iOS operating system.

Windows

This indicates that the Xamarin.Forms platform is running on a device that is running the Windows platform.

WinPhone

This indicates that the Xamarin.Forms platform is running on a device that is running the Microsoft WinPhone OS.

Note

For more information on the Device class, refer to the Xamarin documentation at https://developer.xamarin.com/guides/xamarin-forms/platform-features/device/ .

Now that we have updated the necessary MVVM ViewModels to take advantage of our WalkLocationService, our next step is to finally build and run the TrackMyWalks application within the iOS simulator. When compilation completes, the iOS simulator will appear automatically and the TrackMyWalks application will be displayed, as shown in the following screenshot:

Updating the Xamarin.Forms App class to use platform specifics

As you can see from the preceding screenshot, this displays our current list of walk trail entries, which are displayed within our ListView. When the user clicks on the Add Walk button link, this will display the New Walk Entry content page, and will display the current user's geolocation coordinates for the Latitude and Longitude  EntryCell properties contained within our WalkEntryViewModel. The preceding screenshot, this shows the distance travelled page along with the placeholder pin marker showing the trail location within the map View. You will notice that the Distance Travelled section has been updated and shows the distance travelled by the user that is calculated by the GetDistanceTravelled method contained within our IWalkLocationService interface.

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

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