We already created a service in the preceding chapter to handle navigation. That custom navigation service specification was provided by an interface, INavService
, and there is a property of that interface type in the BaseViewModel
so that a concrete implementation of the service can be provided to the ViewModels as needed.
The benefit of using an interface to define platform-specific services is that it can be used in an agnostic way in the ViewModels and the concrete implementations can be provided via dependency injection. Those concrete implementations can be actual services, or even mocked services for unit testing the ViewModels, as we'll see in Chapter 8, Testing.
In addition to navigation, there are a couple of other platform-specific services our TripLog app could use to enrich its data and experience. In this section, we will create a location service that allows us to get specific geo-coordinates from the device. The actual platform-specific implementation of the location service is fairly trivial and there are tons of resources on how to do this in various ways. We will create a basic implementation without going too deep so that we can keep the focus on how we leverage it as a dependency in a Xamarin.Forms architecture.
Similar to the approach taken for the navigation service, we will first start out by creating an interface for the location service and then create the actual platform-specific implementations.
The first step to allowing our app to take advantage of the device's geo-location capabilities is to provide an interface in the core library that can be used by the ViewModels in a device and platform agnostic manner. When getting geo-location back from a device, each platform could potentially provide coordinates in a platform-specific data structure. However, each structure will ultimately provide two double values representing the coordinate's latitude and longitude values. There are a couple of ways to ensure the results are returned in a platform-agnostic manner, which we need since we are working in a portable class library.
One way is to pass the values back via a callback method. Another approach, which we will employ, is to use a custom object, which we will define in our Models
namespace, as shown in the following steps:
GeoCoords
in the Models
folder of the core TripLog project.double
properties to the GeoCoords
class named Latitude
and Longitude
:public class GeoCoords { public double Latitude { get; set; } public double Longitude { get; set; } }
ILocationService
in the Services
folder of the core TripLog project. The interface should have one async
method, which returns a GeoCoords
object:public interface ILocationService { Task<GeoCoords> GetGeoCoordinatesAsync(); }
Now that we have an interface that defines our location service, we can use it in the core project of our TripLog app. The main place we need to capture location in the app is on the new entry page, so coordinates can be attached to log entries when they are added. Because we want to keep our app logic separated from the user interface, we will use the location service in the new entry page's ViewModel, and not on the Page
itself.
In order to use the ILocationService
in the NewEntryViewModel
, perform the following steps:
readonly
property to the NewEntryViewModel
to hold an instance of the location service:public class NewEntryViewModel : BaseViewModel
{
readonly ILocationService _locService;
// ...
}
NewEntryViewModel
constructor to take an ILocationService
instance and set its readonly ILocationService
property:public NewEntryViewModel (INavService navService, ILocationService locService) : base (navService) { _locService = locService; Date = DateTime.Today; Rating = 1; }
NewEntryViewModel
Init
method to use the location service to set the Latitude
and Longitude
on the Entry
property:public override async Task Init () { var coords = await _locService.GetGeoCoordinatesAsync (); Latitude = coords.Latitude; Longitude = coords.Longitude; }
As you can see, we can completely work with our location service in the ViewModel even though we haven't actually written the platform-specific implementation. Although, if we run the app, we will get a runtime error, because the implementation won't actually exist, but it's useful to be able to work with the service through abstraction to fully build out and test the ViewModel.
Now that we have created an interface for our location service and updated the ViewModel to use it, we need to create the concrete platform-specific implementations. For the purposes of this chapter, we will only create the iOS and Android implementations; however, the companion code for this book contains the Windows implementation as well.
In order to tap into the platform's geo-location capabilities, we can leverage a Xamarin Component named Xamarin.Mobile. The Xamarin.Mobile Component provides an easy API to use device-specific utilities such as geo-location, media, and contacts, as shown in the following steps:
TripLog.iOS
and TripLog.Droid
) by right-clicking on the Components
folder.TripLog.iOS
project named Services
, and within it, create a new class named LocationService
that implements the ILocationService
interface we created earlier in the chapter:public class LocationService : ILocationService { public async Task<GeoCoords> GetGeoCoordinatesAsync () { var locator = new Geolocator { DesiredAccuracy = 30 }; var position = await locator.GetPositionAsync (30000); var result = new GeoCoords { Latitude = position.Latitude, Longitude = position.Longitude }; return result; } }
TripLog.Droid
project named Services
, and within it, create a new class named LocationService
that implements the ILocationService
interface we created earlier in the chapter. When using the Xamarin.Mobile Component to create a location service, the iOS and Android implementations are almost identical. The only major difference between the two implementations is that Android requires the Geolocator
to be instantiated with the current Android Activity. In Xamarin.Forms, this can be accessed via the Context
property of the static Forms
class:public class LocationService : ILocationService { public async Task<GeoCoords> GetGeoCoordinatesAsync () { var locator = new Geolocator (Forms.Context) { DesiredAccuracy = 30 }; var position = await locator.GetPositionAsync (30000); var result = new GeoCoords { Latitude = position.Latitude, Longitude = position.Longitude }; return result; } }
Now that we have created a platform-dependent service, it is time to register it into an IoC container so that we can use it throughout the rest of the code. In the next section, we will use Ninject to create registrations between our location service interface and the actual platform-specific implementations. We will also update the custom navigation service that we created in Chapter 3, Navigation Service, to use Ninject in place of the default Xamarin.Forms DependencyService.
13.59.107.152