1. Setting the Stage
When it comes to writing mobile applications, it's important to integrate with the platform specific features that are available to you when it makes sense. For instance, if you were writing a navigation app, it would make sense for you to use the geolocation features of the device and platform. If you were creating an app to help people with a vision impairment, you would want to integrate with any text-to-speech features that were available as well.
It's the developers that take advantage of these features that set themselves and their apps apart from the rest. These simple things take just an ordinary app and make it great. But what happens when you want to take advantage of these features, but you have decided to adopt Xamarin.Forms as your cross-platform mechanism of choice? Do you have to give up hope on these features just because you decided that your app needs to be cross-platform and you want to be able to share as much logic and user interface code as possible? Absolutely not.
These types of questions inevitably cause some issues for developers that adopt newer technologies such as Xamarin.Forms. Prior to the release of Xamarin.Forms, when you were working directly with Xamarin.iOS, Xamarin.Android, and Windows Phone project templates, accessing these types of features was fairly straightforward. From the Xamarin perspective, if you could find sample C#—or even native language and SDK documentation—for a particular feature, you could simply map your code to the native concepts, because Xamarin did such a spectacular job of translating the same native concepts on those platforms into C# language constructs. Windows Phone features were even easier because there was no translation necessary. All you had to do was read the documentation.
Luckily for us as developers, Xamarin has put a lot of time and effort into designing a mechanism for us to access these same features even if we choose to use their Xamarin.Forms abstraction layer. This mechanism is know as the DependencyService
.
2. DependencyService
Overview
At first glance, a name like DependencyService
may seem a little intimidating. It sounds like some fancy programming terminology that only the elite few understand. If you have ever worked with Dependency Injection (DI) or Inversion of Controller (IoC) containers, you should feel right at home with the DependencyService
. If you haven't, I assure you that it is a very simple concept to understand once you break it down into its components.
What Is the DependencyService
?
At it's most basic, DependencyService
is a class. It's a class whose sole purpose of existence is to allow you to register any number of classes throughout your application. By register, I mean take any class you have and make it known to the service. Once the DependencyService
knows about a class, it can go and retrieve an instance of that class whenever necessary. That is the other purpose of the DependencyService
. If at any point in your application, you decide that you need an instance of a class that has been registered in the DependencyService
, you can request or get an instance of it.
When you really get down into the nuts and bolts of the DependencyService
, this is a very broad generalization. But from a developer's point of view, that's almost all you need to know. However, there is one other concept you need to be aware of when working with the DependencyService
, interfaces. When it comes to the DependencyService
and all of this registering and retrieving, you are typically doing that with respect to interfaces. This means that when you register a class, you are registering it as an implementation of a particular interface. And when you are retrieving a class, you are actually asking the DependencyService
for an implementation of that interface. At this point, you don't really care what the implementation is, you just want a class that implements this interface.
How Does the DependencyService
Work?
Now that you have a basic understanding at a conceptual level of what the DependencyService
is, let's dig a little deeper and see how it actually works.
To use the DependencyService
, you need three things:
- Interfaces: An interface is simply a construct that defines what members must be present within any class that chooses to implement, or agree, to this contract.
-
Registration: Registration is merely the mechanism of letting the
DependencyService
know that a particular class wishes to be registered and be able to be retrieved later on.
-
Location: This concept is often associated with a pattern in software development know as the Service Locator pattern. This simply means that you can go to a single place, the
DependencyService
, and request some functionality, a class, without having to directly instantiate a new instance.
Let's dig into each one of these concepts in a little more detail.
3. Interfaces
Interfaces are very common occurrences in most Object Oriented Programming (OOP) languages these days. Using an interface allows you to define a contract that contains a series of properties, methods, events, etc., that must be implemented by any class that agrees to that contract.
Here's a very simple example of an interface and a class that implements that interface.
public interface IFileGrabber { string GetFileContents(string fileUri); } public SimpleGrabber : IFileGrabber { public string GetFileContents(string fileUri) { return GetFileFromFileSystem(fileUri); } }
This seems like a very simple example, but it serves the purpose quite well. The IFileGrabber
interface defines a single method, GetFileContents
. The SimpleGrabber
class agrees to or implements the IFileGrabber
interface, which means that it must contain an implementation for the one method.
Now, instead of having to implement other code in your application directly against a concrete class, SimpleGrabber
, you can start to reference the IFileGrabber
interface instead. Imagine you have another class in your application that looks like this:
public class DataRetriever { private IFileGrabber _fileGrabber; public DataRetriever(IFileGrabber fileGrabber) { _fileGrabber = fileGrabber } public string GetFileContents(string fileUri) { return _fileGrabber.GetFileContents(fileUri); } }
By using the IFileGrabber
interface instead of a concrete class, you have the ability to create other mechanisms to retrieve files from different places and the DataRetriever
class wouldn't care. Let's assume we have another class that looks like this:
public class NetworkGrabber : IFileGrabber { public string GetFileContents(string fileUri) { return GetFileFromNetwork(fileUri); } }
You now care less about how the class or the GetFileContents
method is implemented, you just know that at least the members that are defined in the interface are present and that means you can continue to code away using just that interface as a reference. This is an incredibly important concept when it comes to the DependencyService
.
4. Registration
In the context of the DependencyService
, Xamarin has made the process of registering a class quite simple. Since you already have defined your interface and at least one class that implements it, you can register it in the DependencyService
using a very simple assembly attribute.
Let's continue using the above example and register the SimpleGrabber
class. The class definition would now looking something like this:
[assembly: Xamarin.Forms.Dependency(typeof(SimpleFileGrabber))] // Any namespace declaration that may exist public SimpleGrabber : IFileGrabber { public string GetFileContents(string fileUri) { return GetFileFromFileSystem(fileUri); } }
All you need to do is add the assembly reference above your class definition and outside of any namespace definition that may be contained within that file as well. By doing this simple task, you will have successfully registered the SimpleGrabber
class as an implementation of the IFileGrabber
interface.
When registering a class, that class must contain a parameterless constructor in order for the DependencyService
to instantiate it. In my example above, I haven't defined a constructor so the compiler will, by default, create a parameterless constructor for me.
5. Location
The final piece of the puzzle is getting an instance of a registered class. This is actually the easiest part of the entire process. To retrieve an instance of a registered class, you simply use the DependencyService
class and it's generic Get<>()
method. Here's a simple example:
public class FileHelper { public string GetFileContents(string fileUri) { return DependencyService.Get<IFileGrabber>().GetFileContents(fileUri); } }
In this case, at runtime, you don't care where the DependencyService
is getting the concrete class that implements the IFileGrabber
interface. All you care about is that the class implements the IFileGrabber
interface.
6. Using the DependencyService
Now that you have a conceptual understanding of what the DependencyService
is and how to use it, let's create a simple application to put it to use.
For this example, I will be using Xamarin Studio 5, but feel free to use Visual Studio 2013 if you wish. Start by creating a new solution. In the New Solution dialog box, under the C# category on the left, select the Mobile Apps project family. On the right hand side, select either the Blank App (Xamarin.Forms Portable) or the Blank App (Xamarin.Forms Shared) project template. The code and the resulting application will be the same regardless of the template you choose.
In this example, I will be using the Portable Class Library (PCL) version of the template. Give a name to the project. I will be naming the solution and first project DependencyServiceSample. Then click the OK button.
This process will create three separate projects:
- DependencyServiceSample - Shared library (PCL)
- DependencyServiceSample.Android - Android project
- DependencyServiceSample.iOS - iOS project
Xamarin Studio doesn't support creating Windows Phone projects. If you are using Visual Studio, this process will create four projects. It will create the above three projects as well as a Windows Phone project named DependencyServiceSample.WinPhone.
In the shared library (DependencyServiceSample), create a new interface file and name it ISampleInterface and give it the following implementation:
namespace DependencyServiceSample { public interface ISampleInterface { string GetData(); } }
The is a standard looking interface file that defines a simple method named GetData
that will return a string
. Once again, the important point to understand is that from the perspective of the shared code file, it doesn't care what the implementation of this interface looks like. The only thing that matters is that whatever implementation is provided for this interface, it has a method named GetData
that will return a string
.
Next, we modify the App.cs file to use the DependencyService
to get an instance of the ISampleInterface
to use in your Xamarin.Forms app. Modify the GetMainPage
method to look like the following:
public static Page GetMainPage () { return new ContentPage { Content = new Label { Text = DependencyService.Get<ISampleInterface>().GetData(), VerticalOptions = LayoutOptions.CenterAndExpand, HorizontalOptions = LayoutOptions.CenterAndExpand, }, }; }
Notice that the only difference is that the Text
property of the Label
has been changed to the following line:
DependencyService.Get<ISampleInterface>().GetData()
This way, you are using the DependencyService
class and the generic Get<>()
method to retrieve whatever implementation of the ISampleInterface
is implemented in the platform specific project that is currently running. Once that instance has been retrieved, you are calling the GetData
method to get back a string and set the Text
property of the Label
.
The last step has two parts (three if you are using Visual Studio). At this point, you will need to implement the ISampleInterface
interface in all of the platform specific projects in your solution.
Let's start in the DependencyServiceSample.Android application. All you need to do is create a new class file in the project and give it any name you like. I have named mine Sample_Android. Replace the default implementation with the following:
using System; using DependencyServiceSample.Android; [assembly: Xamarin.Forms.Dependency(typeof(Sample_Android))] namespace DependencyServiceSample.Android { public class Sample_Android : ISampleInterface { #region ISampleInterface implementation public string GetData () { return "I came from the Android project!"; } #endregion } }
This is a simple class that implements the ISampleInterface
interface and its implementation is to simply return a string
stating that it's coming from the Android project. The only difference is the use of the assembly
attribute at the top of the file that registers this class with the DependencyService
so that it can be retrieved later.
Now, let's create another implementation of this interface in the iOS project. Create a new class in the iOS project, name it Sample_iOS, and replace the default implementation with the following:
using System; using DependencyServiceSample.iOS; [assembly: Xamarin.Forms.Dependency(typeof(Sample_iOS))] namespace DependencyServiceSample.iOS { public class Sample_iOS : ISampleInterface { #region ISampleInterface implementation public string GetData () { return "I came from the iOS project!"; } #endregion } }
The implementation is exactly the same as the Android version, except that it returns a different string stating that it's coming from the iOS project this time. The final step is to run the application and see if you are getting the result you are expecting.
Here is the result of the iOS application running.
Here is the result of the Android application running.
As you can see, both applications run successfully. Not only do they run, but they are successfully running from a shared Xamarin.Forms project that is controlling the user interface. From that user interface code within Xamarin.Forms, you are now able to dip directly into the platform specific projects to access native code.
7. Where to Go From Here
Now that you have the skills to use the DependencyService
to get access to native functionality from Xamarin.Forms, the sky's the limit. You can continue to write simple implementations as you have done in this tutorial or you can start to tap into more interesting features of the platforms.
One of the most interesting resources to take a look at for integrating into your DependencyService
is the Recipes section of the Xamarin website. Here you'll find platform specific implementations of getting access to a number of features including:
- Networking
- Audio
- Video
- Geolocation
- Accelerometers
All of these features are at your disposal when it comes to Xamarin.Forms applications. With the DependencyService
, these features can be summoned at a moment's notice.
Conclusion
Now that you know and understand the DependencyService
, you no longer have to feel intimidated when you need to access platform specific features from a Xamarin.Forms application. You now possess the tools that allow you to tap into those amazing native features of the devices that will ultimately allow you to differentiate your apps from the rest in the app stores.
Next Step: Watch the Course
If you'd like to learn more about Xamarin, then check out our course Building Multi-Platform Apps With C# in Xamarin.
In the course, you will learn how to create a cross-platform application from a single code base that will run on three distinctly different platforms: iOS, Android, and Windows Phone 8. Think it can’t be done? In just a little while you will be doing it yourself. Let’s get to work.
You can take the straight away with a completely free 14 day trial of a Tuts+ subscription. Take a look at our subscription options to get started or, if you're just interested in this course, you can buy it individually for $15! Here's a preview to get you started:
Comments