Cross Platform C#

Native Services with Xamarin.Forms? It's DependencyService to the Rescue

Sometimes you need to make platform-specific calls. That's where DependencyService comes in handy.

As you know, Xamarin.Forms is a great mechanism to create cross-platform applications that run natively on iOS, Android and Windows Phone. Xamarin.Forms provides a great set of services that run the same across all OSes, however, there are times when it's necessary to make calls that are platform-specific. That's what DependencyService is for.

I'll show an example by performing reverse geocoding on each platform. For each platform, there will be a reverse geocode call made to the platform-specific implementation. Most Web services that provide reverse geocoding services cost money for each call. But by calling the platform-specific implementation, there's no need to call out to a Web service from within your code. The platform-specific reverse geocode will call its own Web service for which your program does not need to pay.

How DependencyService Works
The DependencyService allows shared code in Xamarin Forms to easily resolve code interfaces into platform-specific code. This platform-specific code can be in iOS, Android or Windows Phone and is accessed by an implemented interface.

There are several pieces to calling the platform-specific code via the DependencyService:

  • Interface: Implemented in shared code, it defines the API call that will be made into the platform specific code.
  • Implementation: This is the platform-specific code that will be called. The implementation is within the platform projects of a Xamarin.Forms solution. The class that implements the interface must have a parameterless constructor for the DependencyService.
  • Registration: Along with the code implementation, the code must be marked with a necessary attribute to register a class. This will allow the DependencyService to find the code and to create an instance of the class to call methods on the class.
  • Location: The Xamarin.Forms shared code will call into the platform-specific code using the DependencyService.Get<>.
  • Return Types: When using a return type, it will need to be within the cross-platform shared code.

Drilling Down
Let's look at an actual implementation using Visual Studio 2015. First, let's look at the interface and type definitions in the cross-platform project. In this example there's a class called LocationAddress. This class contains several properties that we want to get values from, specifically the address, city, state/province, zip code, and country.

Note: The application can be set up to provide additional information. This is merely an example.

public class LocationAddress
{
  public LocationAddress()
  {
  }
  public string Name { get; set; }
  public string Address1 { get; set; }
  public string Address2 { get; set; }
  public string Province { get; set; }
  public string City { get; set; }
  public string ZipCode { get; set; }
  public string Country { get; set; }
}

This is a definition of the interface. The interface to implement will be the iReverseGeocode interface. The interface contains a method call named ReverseGeocodelatLonAsync. This method will take two doubles -- which represent the latitude and longitude -- perform a reverse geocode, and return an object with location information. In this specific example, the latitude and longitude of the center of my city (Knoxville, Tenn., United States) will be used:

public interface IReverseGeocode
{
  Task<LocationAddress> ReverseGeoCodeLatLonAsync(double lat, double lon);
}

Listing 1 shows the class that will need to be implemented within the Android application.

Listing 1: ReverseGeoCode Class for Android
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Locations;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using DependencyServiceExample.Helpers;
  [assembly: Dependency(typeof(DependencyServiceExample.Droid.Dependency.ReverseGeocode))]
namespace DependencyServiceExample.Droid.Dependency
{
  public class ReverseGeocode : IReverseGeocode
  {
    public ReverseGeocode()
    {
    }
 
    public async Task<DependencyServiceExample.Helpers.LocationAddress> 
      ReverseGeoCodeLatLonAsync(double lat, double lon)
    {
      var geo = new Geocoder(Forms.Context);
      var addresses = await geo.GetFromLocationAsync(lat, lon, 1);
      if (addresses.Any())
      {
        var place = new DependencyServiceExample.Helpers.LocationAddress();
        var add = addresses[0];
        place.Name = add.FeatureName;
        if (add.MaxAddressLineIndex > 0)
        {
          place.Address1 = add.GetAddressLine(0);
        }
        place.City = add.Locality;
        place.Province = add.AdminArea;
        place.ZipCode = add.PostalCode;
        place.Country = add.CountryCode;
        return place;
      }
 
      return null;
    }
  }
}

Note several things within this code:

  • It includes several Xamarin.Forms namespaces.
  • The assembly must be registered, which is done via the dependency attribute.
  • Android uses a context object to directly access various Android services. In a Xamarin.Android app, this is directly available in an Activity or Fragment. Xamarin.Forms provides direct access to the context via the Forms.Context static object.
  • The call to perform the reverse geocoding is the exact same one that a program would use to call into a Xamarin.Android app.

Finally, the following method shows an example call to reverse geocoding code:

private async void HandleLocation()
{
  var currentLat = 35.960638;
  var currentLon = -83.920739;
  var address = await DependencyService.Get<Helpers.IReverseGeocode>().ReverseGeoCodeLatLonAsync(
    currentLat, currentLon);
  if (address == null) return;
 
  lbl.Text = String.Format("{0}, {1}", address.City, address.Province);
 
}

This example hardcodes the latitude and longitude to Knoxville, Tenn., United States. This is for example purposes, to show the concept.

Listing 2 is source code that shows the platform-specific code running in iOS.

Listing 2: Platform-Specific ReverseGeoCode Class for iOS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Foundation;
using UIKit;
using CoreLocation;

using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using DependencyServiceExample.Helpers;

[assembly: Xamarin.Forms.Dependency(typeof(DependencyServiceExample.iOS.Dependency.ReverseGeocode))]
namespace DependencyServiceExample.iOS.Dependency
{
    public class ReverseGeocode : IReverseGeocode
  {
    public ReverseGeocode()
    {
    }

      async public Task<DependencyServiceExample.Helpers.LocationAddress> 
        ReverseGeoCodeLatLonAsync(double lat, double lon)
    {
      var geoCoder = new CLGeocoder();
      var location = new CLLocation(lat, lon);
      var placemarks = await geoCoder.ReverseGeocodeLocationAsync(location);
      if (placemarks.Any())
      {
        var place = new DependencyServiceExample.Helpers.LocationAddress();
        var pm = placemarks[0];
        place.Name = pm.Name;
        place.City = pm.Locality;
          place.Province = pm.AdministrativeArea;
        place.ZipCode = pm.PostalCode;
        place.Country = pm.IsoCountryCode;
        return place;
      }
      return null;
    }
  }
}

Just like in the Android code, the iOS code uses code specific to the iPhone. Note the use of the Xamarin.Forms iOS namespaces, as well as the CLLocation and CLGeocoder classes.

Figure 1 shows the output in the iPhone Simulator running on the Mac. Running the same application on a Google Nexus results in similar output.

[Click on image for larger view.] Figure 1. Output of ReverseGeoCode Running in the iOS Simulator on a Mac

One interesting note is that the reverse geocode within each platform will return different data coming back. It's the job of the developer to determine what's important and what in't. For example, the Android reverse geocode returned additional information, such as the latitude/longitude that was within an area called "Old City." In the specific situations where I've been performing reverse geocoding, this was not important, but it might be for other applications.

Looking Back
In retrospect, I hope you found that using the DependencyService in Xamarin.Forms to call into platform-specific code to be a pretty simple thing to do. My example demonstrated how to perform reverse geocoding, but nearly any type of platform-specific code can be called.

About the Author

Wallace (Wally) B. McClure has authored books on iPhone programming with Mono/Monotouch, Android programming with Mono for Android, application architecture, ADO.NET, SQL Server and AJAX. He's a Microsoft MVP, an ASPInsider and a partner at Scalable Development Inc. He maintains a blog, and can be followed on Twitter.

comments powered by Disqus

Featured

Subscribe on YouTube