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:
TrackMyWalks
application to use the Location Service
WalkEntryViewModel
to use the Location Service Interface
DistanceTravelledViewModel
to use the Location Service Interface
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.
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:
TrackMyWalks
solution is loaded within the Xamarin Studio IDE.TrackMyWalks
PCL project solution, under the Services
folder.IWalkLocationService
for the name of the new interface file to be created, as shown in the following screenshot:
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.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: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.
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.
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:
TrackMyWalks
solution is loaded within the Xamarin Studio IDE.TrackMyWalks.Droid
project, called Services
, as shown in the following screenshot:
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.WalkLocationService
for the name of the new class file to be created, as shown in the following 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:
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;
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; } }
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) { }
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 |
|
This method is fired up whenever the location service provider has been disabled by the user. |
|
This method is fired up whenever the location service provider has been enabled by the user. |
|
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. |
|
This method is fired up whenever a change in location has been detected. |
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;
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); }; }
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); }
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; }
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.
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:
Services
folder for our TrackMyWalks.iOS
project, and enter WalkLocationService
for the name of the new class file to create.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;
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; } }
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;
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;
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.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
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); };
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(); }; } }
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); }
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.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; } }
didAuthorizationChange
method contains a number of authorization status codes, and these are explained, along with their descriptions, in the following table:
Authorization status |
Description |
|
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. |
|
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. |
|
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. |
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/
.
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; }
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.
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:
Info.plist
file, which is contained within the TrackMyWalks.iOS
project, and ensure that the Application tab is showing.
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.Info.plist
file is displayed within the Xamarin IDE, and that the Source tab is showing.NSLocationAlwaysUsageDescription
and NSLocationWhenInUseUsageDescription
by clicking within the Add new entry section of the Info.plist
.
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:
WalkLocationService.cs
file is displayed within the code editor.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:
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:
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.
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:
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
{
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();
}
}
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.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 };
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.
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:
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;
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);
}
}
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.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(); }
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; }); }
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.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.
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:
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 } }; }
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.
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:
MainActivity.cs
file and ensure that it is displayed within the code editor.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.
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:
TrackMyWalks.cs
file and ensure that it is displayed within the code editor.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 |
iOS |
This indicates that the |
Windows |
This indicates that the |
WinPhone |
This indicates that the |
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:
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.
3.143.22.23