Maintaining multiple code bases for the various mobile platforms is one of the chores that today's developers have to contend with. For businesses, this translates to additional overhead that's both costly and time-consuming. Fortunately, Microsoft has the solution in the form of Xamarin.Forms. Xamarin.Forms is a library of APIs that allows developers to build native apps for Android, iOS, and Windows completely in C#. Perhaps the most compelling feature of Xamarin.Forms is its support for native UI - you code the user interface of your application using Xamarin.Forms-specific forms and controls. At runtime, each form and its controls are mapped to the respective native UI elements of each platform. This way, your application looks like a first-class citizen.

Although Xamarin.Forms allows you to focus on the UI aspects of your app development process, there are often times when you need to access platform-specific functionalities, such as location services, hardware access (camera, accelerometer, etc.), speech capabilities, and more. In Xamarin.Forms, this is achieved using the DependencyService class.

Although Xamarin.Forms allows you to focus on the UI aspects of your app development process, there are often times when you need to access platform-specific functionalities. This is where DependencyService comes in.

In this article, I'll walk you through the basics of DependencyService in Xamarin.Forms and work on a location service project that runs on both iOS and Android.

Components of DependencyService

In order to use DependencyService in Xamarin.Forms, you need three components:

  • Interface: Defines the functionalities to be performed by each respective platform
  • Implementation: The implementation by each platform of the functionalities defined by the interface
  • Registration: Each implementing class must register with DependencyService via a metadata attribute. Doing so allows the DependencyService to supply the correct implementation during runtime.

Once the three components are in place, your shared code can explicitly call DependencyService for the implementation of the interface. Sound complicated? The following simple project makes it clear.

First, using Xamarin Studio create a new Xamarin.Forms project and name it NativeFeatures (see Figure 1).

Figure 1: Creating the Xamarin.Forms project
Figure 1: Creating the Xamarin.Forms project

Interface

Once the project's created, observe that there are three projects in the solution (see the left side of Figure 2):

  • NativeFeatures: contains the shared code of the application
  • NativeFeatures.Droid: contains the code for the Android application
  • NativeFeatures.iOS: contains the code for the iOS application
Figure 2: The three projects in the solution (left) and adding an interface file to the shared code project
Figure 2: The three projects in the solution (left) and adding an interface file to the shared code project

Let's now add an interface file to the NativeFeatures project (see the right side of Figure 2) and name it IMyInterface.cs. Populate it as follows:

using System;
namespace NativeFeatures
{
    public interface IMyInterface
    {
        string GetPlatformName();
    }
}

The interface file is straight-forward; it contains a method signature named GetPlatformName().

Implementation

With the interface already added in the shared code project, you now want to implement the interface in each of the platforms. Let's add two class files named PlatformDetails.cs in the NativeFeatures.Droid and NativeFeatures.iOS projects (see Figure 3).

Figure 3: Adding the implementation files to the Android and iOS projects
Figure 3: Adding the implementation files to the Android and iOS projects

Populate the PlatformDetails.cs in the NativeFeatures.iOS project as follows:

using System;
namespace NativeFeatures.iOS
{
    public class PlatformDetails : IMyInterface
    {
        public PlatformDetails()
        {
        }
        public string GetPlatformName()
        {
            return "I am iPhone!";
        }
    }
}

Here, you implement the IMyInterface interface and provide the implementation for the GetPlatformName() method.

Likewise, populate the PlatformDetails.cs in the NativeFeatures.Droid project as follows:

using System;
namespace NativeFeatures.Droid
{
    public class PlatformDetails : IMyInterface
    {
        public PlatformDetails()
        {
        }
        public string GetPlatformName()
        {
            return "I am Android!";
        }
    }
}

Registration

Now that you have the interface and implementation for each platform done, how does DependencyService know who is implementing the interface? To allow DependencyService to know that, you need to register each implementing class using a metadata attribute. This comes in the form of:

[assembly: Xamarin.Forms.Dependency(typeof(ClassName))]

This metadata attribute registers a class with DependencyService as an implementing class so that it can be called during runtime.

Add the following statements to the PlatformDetails.cs in the NativeFeatures.iOS project:

using System;
using NativeFeatures.iOS;
[assembly: Xamarin.Forms.Dependency(typeof(PlatformDetails))]
namespace NativeFeatures.iOS
{
    public class PlatformDetails : IMyInterface
    {
        public PlatformDetails()
        {
        }
        public string GetPlatformName()
        {
            return "I am iPhone!";
        }
    }
}

Add the following statements to the PlatformDetails.cs in the NativeFeatures.Droid project:

using System;
using NativeFeatures.Droid;
[assembly: Xamarin.Forms.Dependency(typeof(PlatformDetails))]
namespace NativeFeatures.Droid
{
    public class PlatformDetails : IMyInterface
    {
        public PlatformDetails()
        {
        }
        public string GetPlatformName()
        {
            return "I am Android!";
        }
    }
}

Calling DependencyService

Finally, it's time to put all in action! In the NativeFeatures.cs file located in the shared code project, add the statements to match what's shown in Listing 1.

Listing 1: Calling the DependencyService from the Shared Code

using System;

using Xamarin.Forms;

namespace NativeFeatures
{
    public class App : Application
    {
        public App()
        {
            // The root page of your application
            var content = new ContentPage
            {
                Title = "NativeFeatures",
                Content = new StackLayout
                {
                    VerticalOptions = LayoutOptions.Center,
                    Children = {
                        new Label {
                            HorizontalTextAlignment =
                                TextAlignment.Center,
                            //Text = "Welcome to Xamarin Forms!"
                            Text =
                                DependencyService.Get<IMyInterface>().
                            GetPlatformName()
                        }
                    }
                }
            };

            MainPage = new NavigationPage(content);
        }

        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
        }
    }
}

The DependencyService.Get<IMyInterface>() method returns the implementing class in each platform, and using the returned class, you then call the GetPlaformName() method.

When you now deploy the application onto an iPhone Simulator, you should see the display as shown in Figure 4.

Figure 4: The iOS application running on the iOS Simulator
Figure 4: The iOS application running on the iOS Simulator

The same application running on Android displays the output, as shown in Figure 5.

Figure 5: The Android application running on an Android device
Figure 5: The Android application running on an Android device

Implementing Location-Based Services (LBS) using DependencyService

What you've seen in the previous section is really simple. To better understand how DependencyService works in real life, let's consider the scenario where you need to write an application that displays your current location. In this case, you have to use the location manager in each platform to get your location. This is a perfect example to use DependencyService.

For this example, let's create a new Xamarin.Forms project and name it LBS. As usual, let's add an interface file named IMyLocation.cs to the shared code project, as shown in Figure 6.

Figure 6: Adding the interface file to the shared code
Figure 6: Adding the interface file to the shared code

Populate the IMyLocation.cs file as follows:

using System;
namespace LBS
{
    public interface IMyLocation
    {
        void ObtainMyLocation();
        event EventHandler<ILocationEventArgs>
            locationObtained;
    }
    public interface ILocationEventArgs
    {
        double lat { get; set; }
        double lng { get; set; }
    }
}

In the above, the IMyLocation interface has the following:

  • ObtainMyLocation(): This method starts the location service.
  • locationObtained: This event is fired whenever there is a new location reported by the location service on each platform. The location is passed via the event through an object of type IlocationEventArgs. The IlocationEventArgs interface defines an object containing two properties: lat and lng.

Implementing Location-Based Services in Android

It's time to implement the location services in Android. For this, add a new Class file named GetMyLocation.cs to the LBS.Droid project (see Figure 7).

Figure 7: Adding the implementation file for the Android project
Figure 7: Adding the implementation file for the Android project

Populate the GetMyLocation.cs file as shown in Listing 2.

Listing 2: Implementing LBS in Android

using System;

using Android.Content;
using LBS.Droid;
using Xamarin.Forms;
using Android.Locations;

[assembly: Xamarin.Forms.Dependency(typeof(GetMyLocation))]
namespace LBS.Droid
{
    //---event arguments containing lat and lng---
    public class LocationEventArgs : EventArgs,
    ILocationEventArgs
    {
        public double lat { get; set; }
        public double lng { get; set; }
    }

    public class GetMyLocation : Java.Lang.Object,
        IMyLocation,
        ILocationListener
        {
            LocationManager lm;

            public void OnProviderDisabled(string provider) { }

            public void OnProviderEnabled(string provider) { }

            public void OnStatusChanged(string provider,
                Availability status, Android.OS.Bundle extras)
            { }

            //---fired whenever there is a change in location---
            public void OnLocationChanged(Location location)
            {
                if (location != null)
                {
                    LocationEventArgs args = new LocationEventArgs();
                    args.lat = location.Latitude;
                    args.lng = location.Longitude;
                    locationObtained(this, args);
                };
            }

            //---an EventHandler delegate that is called when
            // a location is obtained---
            public event EventHandler<ILocationEventArgs>
            locationObtained;

            //---custom event accessor that is invoked when client
            // subscribes to the event---
            event EventHandler<ILocationEventArgs>
            IMyLocation.locationObtained
            {
                add
                {
                    locationObtained += value;
                }
                remove
                {
                    locationObtained -= value;
                }
            }

            //---method to call to start getting location---
            public void ObtainMyLocation()
            {
                lm = (LocationManager)
                Forms.Context.GetSystemService(Context.LocationService);
                lm.RequestLocationUpdates(
                    LocationManager.NetworkProvider,
                        0,   //---time in ms---
                        0,   //---distance in metres---
                        this);
            }

            //---stop the location update when the object is
            // set to null---
            ~GetMyLocation()
            {
                lm.RemoveUpdates(this);
            }
        }
    }

The code in Listing 2 does the following:

  • The GetMyLocation class implements the IMyLocation and ILocationListener interfaces. The ILocationListener interface is required in Android; it contains the methods signature for OnProviderDisabled(), OnProviderEnabled(), OnStatusChanged(), and OnLocationChanged().
  • When the shared code calls the ObtainMyLocation() method, you first obtain an instance of the LocationManager class. The LocationManager in Android provides location services.
  • You can use the LocationManager class to find your location using GPS or a combination of WiFi and cellular network (known as NetworkProvider). Here, I'm using the NetworkProvider.
  • Whenever a new location is found, the LocationManager class fires the OnLocationChanged() method. In this method, you'll package the latitude and longitude of the location using the LocationEventArgs class, which you've declared in the top part of the class. Once that's done, you'll fire the locationObtained event.
  • You also need to create a custom event accessor that's invoked when a client of the GetMyLocation class subscribes to the locationObtained event.

In Android, you need to add the AccessCoarseLocation permission to your project in order to use the NetworkProvider. Then, in the AndroidManifest.xml file of the LBS.Droid project, check the AccessCoarseLocation permission (see Figure 8).

Figure 8: Adding the permission for the Android project
Figure 8: Adding the permission for the Android project

Implementing Location-Based Services in iOS

Like the Android project, add a new Class file to the LBS.iOS project and name it GetMyLocation.cs (see Figure 9).

Figure 9: Adding the implementation file for the iOS project
Figure 9: Adding the implementation file for the iOS project

Populate the GetMyLocation.cs file as shown in Listing 3.

Listing 3: Implementing LBS in iOS

using System;

using CoreLocation;
using LBS.iOS;

[assembly: Xamarin.Forms.Dependency(typeof(GetMyLocation))]
namespace LBS.iOS
{
    //---event arguments containing lat and lng---
    public class LocationEventArgs : EventArgs,
    ILocationEventArgs
    {
        public double lat { get; set; }
        public double lng { get; set; }
    }

    public class GetMyLocation : IMyLocation
    {
        CLLocationManager lm;

        //---an EventHandler delegate that is called when a
        // location is obtained---
        public event EventHandler<ILocationEventArgs>
            locationObtained;

        //---custom event accessor when client subscribes
        // to the event---
        event EventHandler<ILocationEventArgs>
            IMyLocation.locationObtained
        {
            add
            {
                locationObtained += value;
            }
            remove
            {
                locationObtained -= value;
            }
        }

        //---method to call to start getting location---
        public void ObtainMyLocation()
        {
            lm = new CLLocationManager();
            lm.DesiredAccuracy = CLLocation.AccuracyBest;
            lm.DistanceFilter = CLLocationDistance.FilterNone;

            //---fired whenever there is a change in location---
            lm.LocationsUpdated +=
                (object sender, CLLocationsUpdatedEventArgs e) =>
            {
                var locations = e.Locations;
                var strLocation =
                    locations[locations.Length - 1].
                    Coordinate.Latitude.ToString();
                strLocation = strLocation + "," +
                    locations[locations.Length - 1].
                    Coordinate.Longitude.ToString();

                LocationEventArgs args =
                    new LocationEventArgs();
                args.lat = locations[locations.Length - 1].
                    Coordinate.Latitude;
                args.lng = locations[locations.Length - 1].
                    Coordinate.Longitude;
                locationObtained(this, args);
            };

            lm.AuthorizationChanged += (object sender,
                CLAuthorizationChangedEventArgs e) =>
                {
                    if (e.Status == CLAuthorizationStatus.AuthorizedWhenInUse)
                    {
                        lm.StartUpdatingLocation();
                    }
                };

                lm.RequestWhenInUseAuthorization();
            }

            //---stop the location update when the object is set to
            // null---
            ~GetMyLocation()
            {
                lm.StopUpdatingLocation();
            }
        }
    }

The code in Listing 3 is similar to that of Listing 2, except:

  • In iOS, you use the CLLocationManager class to get your locations.
  • In iOS, you need to explicitly ask the user for permission to get his/her location, and you need to handle the AuthorizationChanged event of the CLLocationManager instance.

In the Info.plist file of the LBS.iOS project, add a new key as shown in Figure 10.

Figure 10: Adding the permission key for the iOS project
Figure 10: Adding the permission key for the iOS project

Using the GetMyLocation Class

Now that the implementations for the IMyLocation interface is finally done, call it in the shared code. Add the statements needed to the LBS.cs file, as shown in Listing 4.

Listing 4: Calling the DependencyService in the Shared Code

using System;

using Xamarin.Forms;

namespace LBS
{
    public class App : Application
    {
        Label lblLat, lblLng;
        IMyLocation loc;

         public App()
         {
             lblLat = new Label
             {
                 HorizontalTextAlignment = TextAlignment.Center,
                 Text = "Lat",
             };

         lblLng = new Label
         {
             HorizontalTextAlignment = TextAlignment.Center,
             Text = "Lng",
         };

         // The root page of your application
         var content = new ContentPage
         {
             Title = "LBS",
             Content = new StackLayout
             {
                 VerticalOptions = LayoutOptions.Center,
                 Children = {
                     new Label {
                         HorizontalTextAlignment =
                             TextAlignment.Center,
                             Text = "Current Location"
                     },
                     lblLat,
                     lblLng
                 }
            }
        };

        MainPage = new NavigationPage(content);
    }

    protected override void OnStart()
    {
        // Handle when your app starts
        loc = DependencyService.Get<IMyLocation>();
        loc.locationObtained += (object sender,
            ILocationEventArgs e) =>
        {
            var lat = e.lat;
            var lng = e.lng;
            lblLat.Text = lat.ToString();
            lblLng.Text = lng.ToString();
        };
        loc.ObtainMyLocation();
    }

    protected override void OnSleep()
    {
        // Handle when your app sleeps
    }

    protected override void OnResume()
    {
        // Handle when your app resumes
    }
}

You are now ready to test the application. On the iPhone Simulator, you'll first be asked to grant permission (see Figure 11). Click Allow.

Figure 11: The iOS application asking for permission to obtain user location
Figure 11: The iOS application asking for permission to obtain user location

Once permission is granted, you'll see the location displayed (see Figure 12).

Figure 12: The iOS application displaying the user's location
Figure 12: The iOS application displaying the user's location

Likewise, on the Android, you will see the location displayed (see Figure 13).

Figure 13: The Android application displaying the user's location
Figure 13: The Android application displaying the user's location

Displaying a Map

Now that you can display locations in both the iOS and Android apps, let's do something more interesting. Displaying the latitude and longitude isn't cool enough - a better idea would be to display a map showing the current location. In addition, when you move, you want the map to move as well.

Fortunately, displaying a map in Xamarin.Forms is pretty simple. Just add the Xamarin.Forms.Maps package to the three projects in the solution (see Figure 14).

Figure 14: Adding the Xamarin.Forms.Maps package to the three projects in the solution
Figure 14: Adding the Xamarin.Forms.Maps package to the three projects in the solution

The Xamarin.Forms.Maps package automatically displays Google Maps on Android devices and the Apple Maps on iOS devices.

Displaying Maps in Android

In the MainActivity.cs file in the LBS.Droid project, add in the following statement in bold to initialize the map:

protected override void OnCreate(Bundle bundle)
{
    TabLayoutResource = Resource.Layout.Tabbar;
    ToolbarResource = Resource.Layout.Toolbar;
    base.OnCreate(bundle);
    global::Xamarin.Forms.Forms.Init(this, bundle);
    Xamarin.FormsMaps.Init (this, bundle);
    LoadApplication(new App());
}

Next, you need to apply for an Android Maps API key in order to display the Google Maps in Android. Follow the steps below:

  • Using a Web browser, go to https://code.google.com/apis/console/.
  • Log in using your Google account.
  • Click on the CREATE PROJECT button.
  • Give a name to your project and click Create.
  • In the API Manager page, click the Google Maps Android API link (see Figure 15).
  • Click the ENABLE button at the top of the page.
  • Click the Create Credentials button. You should now get your API key.
Figure 15: Applying for the Google Maps Android API key
Figure 15: Applying for the Google Maps Android API key

Add the following statements in bold to the AndroidManifest.xml file and replace Your_API_Key with the API key you have just obtained (see Listing 5).

Listing 5: Adding the permissions and the Maps API key to the AndroidManifest.xml file

<?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="net.learn2develop.lbs">
    <uses-sdk android:minSdkVersion="15"
        android:targetSdkVersion="23" />

<application android:label="LBS">
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="Your_API_Key" />
</application>

<uses-permission 
    android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission 
    android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission 
    android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<uses-permission
    android:name="android.permission.ACCESS_MOCK_LOCATION" />
<uses-permission
    android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission
    android:name="android.permission.ACCESS_WIFI_STATE" />    
<uses-permission 
    android:name="android.permission.INTERNET" />
</manifest>

Displaying Maps in iOS

Add in the following statements in bold to the AppDelegate.cs file in the LBS.iOS project to initialize the map:

public override bool FinishedLaunching(
    UIApplication app, NSDictionary options)
{
    global::Xamarin.Forms.Forms.Init();
    Xamarin.FormsMaps.Init();
    LoadApplication(new App());
    return base.FinishedLaunching(app, options);
}
}

You can now display the map in the UI. You'll also move the map to display the current location. To do that, add the statements needed from Listing 6 to the LBS.cs file.

Listing 6: Adding the code to display and move the map

public class App : Application
{
    Label lblLat, lblLng;
    IMyLocation loc;

    Map map;

    public App()
    {
        lblLat = new Label
        {
            HorizontalTextAlignment = TextAlignment.Center,
            Text = "Lat",
        };

        lblLng = new Label
        {
            HorizontalTextAlignment = TextAlignment.Center,
            Text = "Lng",
         };

         map = new Map(MapSpan.FromCenterAndRadius(
             new Position(37, -122), Distance.FromMiles(10)))
         {
             VerticalOptions = LayoutOptions.FillAndExpand,
             BackgroundColor = Color.Blue,
             IsShowingUser = true,
         };

         // The root page of your application
         var content = new ContentPage
         {
             Title = "LBS",
             Content = new StackLayout
             {
                 //VerticalOptions =
                 //    LayoutOptions.Center,
                 Children = {
                     new Label {
                         HorizontalTextAlignment = TextAlignment.Center,
                         Text = "Current Location"
                      },
                      lblLat,
                      lblLng,
                      map
                  }
             }
        };
        MainPage = new NavigationPage(content);
    }

    protected override void OnStart()
    {
        // Handle when your app starts
        loc = DependencyService.Get<IMyLocation>();
        loc.locationObtained += (object sender, 
            ILocationEventArgs e) =>
        {
            var lat = e.lat;
            var lng = e.lng;
            lblLat.Text = lat.ToString();
            lblLng.Text = lng.ToString();

             var position = new Position(lat, lng);
             map.MoveToRegion(
                 MapSpan.FromCenterAndRadius(
                     new Position(position.Latitude, position.Longitude),
                     Distance.FromMiles(1)));
        };

        loc.ObtainMyLocation();
    }

    protected override void OnSleep()
    {
        // Handle when your app sleeps
    }

    protected override void OnResume()
    {
        // Handle when your app resumes
    }
}

When you now run the application on the iPhone simulator, you'll be able to see the map (see Figure 16). What's more, the map will move whenever the location changes.

Figure 16: Showing the user location using Apple Maps on iOS
Figure 16: Showing the user location using Apple Maps on iOS

Similarly, you'll see the map on the Android device (see Figure 17).

Figure 17: Showing the user location using Google Maps on Android
Figure 17: Showing the user location using Google Maps on Android

Summary

In this article, you learned how DependencyService works and how you can use it to access platform-specific features. DependencyService is a very useful (and necessary) technology as it bridges the gaps left by Xamarin.Forms.