Cross Platform C#
Using Portable Class Libraries in iOS and Android Apps
Portable Class Libraries create a single DLL that can be used across platforms, making code sharing easier. Learn how to use them by building a weather app with a portable core.
- By Greg Shackles
- 02/21/2014
In the past, sharing code across different platforms typically involved multiple projects, one for each target platform, all of them linking to the same set of source files. This works, but also results in much more overhead in maintaining those linked projects as the main project changes over time.
Portable Class Libraries (PCLs) were introduced recently to help make sharing code across platforms simpler, and now they're supported in Xamarin applications as well. With a PCL, you select the platforms you wish to target with the library and are restricted to just the APIs that are available across all of them. The end result is a single DLL you can safely use across all of those platforms. Think of it as the lowest common denominator of APIs for all of the selected platform targets. This cuts out much of the overhead seen with linked projects as the library is updated and refactored, because the changes only need to be made once and are immediately available everywhere.
Currently, PCLs allow you to target the following platforms:
- Microsoft .NET Framework
- Silverlight
- Windows Phone
- .NET for Windows Store apps
- Xamarin.Android
- Xamarin.iOS
- Xbox
PCLs bring with them many conveniences, but that's not to say file linking doesn't still have its merits as well. For example, one consequence of using a PCL is that you lose the ability to inject platform-specific implementations right into the class library through techniques such as adding additional files, partial classes, partial methods or conditional compilation. As with most things, it all comes down to selecting the right tool for the job, and portable libraries are definitely powerful tools to keep in your belt.
Licensing Changes One of the other major changes for PCLs is Microsoft licensing terms. Originally the licenses for Microsoft PCLs made available on NuGet explicitly said that they could only be used on Windows platforms, eliminating the possibility of using them on iOS or Android via Xamarin, even though they would technically work. Microsoft lifted this restriction, enabling developers to use these libraries on any platform. This includes many great libraries, such as SignalR, HTTP Client, Immutable Collections, Async and more. All of these and others are now permitted and compatible for use in Xamarin applications, and can easily be added right through NuGet.
Building a Portable Library
To demonstrate how to write a PCL that can be used across multiple platforms, I'll build a small app that uses the user's current location to fetch a description of the weather there. Most of the logic will live in the portable library, but because getting the user's location will depend on the platform, each platform will inject its own method for doing so.
To start out, create a new PCL named WeatherCore. When prompted to select the target frameworks, choose .NET Framework 4.5, Silverlight 5, Windows Phone 8, .NET for Windows Store apps, Xamarin.Android and Xamarin.iOS (see Figure 1). The result is a library with the common APIs for all of those platforms, and means this library will truly be portable across a very wide range of platforms.
Because not all of the platforms support HttpClient and Async, those features can be added through the packages made available by Microsoft, which are now fully licensed across any platform. Install the NuGet packages for Microsoft HTTP Client Libraries and Microsoft Async into the portable library.
Next, add a new class called LocationInfo, which will be a simple data object to hold the user's coordinates:
namespace WeatherCore
{
public class LocationInfo
{
public double Latitude { get; private set; }
public double Longitude { get; private set; }
public LocationInfo(double latitude, double longitude)
{
Latitude = latitude;
Longitude = longitude;
}
}
}
Because each platform needs to provide its own implementation for getting the user's location, the portable library will provide a simple abstraction against which to work. Add a new interface to the project named ILocationProvider that exposes a single method to asynchronously returns the user's current location:
using System.Threading.Tasks;
namespace WeatherCore
{
public interface ILocationProvider
{
Task<LocationInfo> GetCurrentLocation();
}
}
For this demo I'm going to use the free API provided by OpenWeatherMap to get the weather for a location. The API provides a lot of interesting weather information, but to keep things simple here I'm just going to grab the description of the weather and ignore the rest. Add a new class named Weather to the library, which will be used to deserialize the data returned from the API:
using System.Xml.Serialization;
namespace WeatherCore
{
[XmlRoot("current")]
public class Weather
{
[XmlElement("weather")]
public Details WeatherDetails { get; set; }
public class Details
{
[XmlAttribute("value")]
public string Value { get; set; }
}
}
}
Now that the data object is set, all that's left is to add some code to tie it all together. Add a new class named WeatherClient, as shown in Listing 1.
Listing 1: Adding the Class To Talk to OpenWeatherMap's API
using System.Net.Http;
using System.Threading.Tasks;
using System.Xml.Serialization;
namespace WeatherCore
{
public class WeatherClient
{
private readonly ILocationProvider _locationProvider;
public WeatherClient(ILocationProvider locationProvider)
{
_locationProvider = locationProvider;
}
public async Task<string> GetCurrentWeather()
{
var location = await _locationProvider.GetCurrentLocation();
string url =
string.Format("http://api.openweathermap.org/data/2.5/weather?lat={0}&lon={1}&mode=xml",
location.Latitude, location.Longitude);
using (var client = new HttpClient())
{
var response = await client.GetAsync(url);
var serializer = new XmlSerializer(typeof (Weather));
using (var stream = await response.Content.ReadAsStreamAsync())
{
var weather = (Weather) serializer.Deserialize(stream);
return weather.WeatherDetails.Value;
}
}
}
}
}
For the sake of a simple demo, I'm just using the API's XML endpoint and the built-in XML deserialization libraries to process the data. You could alternatively use the JSON format as well, and make use of numerous JSON libraries available via NuGet, such as Json.NET. WeatherClient takes a location provider in its constructor, forcing the calling platform to pass in the appropriate implementation. When GetCurrentWeather is called, it awaits the location of the user, and then uses those coordinates to call OpenWeatherMap and fetch the current weather. Note that this is taking advantage of newer features such as async/await and HttpClient all within a portable library. Pretty awesome, right?
iOS
With the library complete, it's time to add an iOS app that uses it. In the past this would involve creating a linked iOS class library project, but because this app is using portable libraries, that step can be skipped entirely. To get started, create a new HelloWorld iOS app called WeatherAppiOS, and add a reference to WeatherCore. Because WeatherCore checked the box for Xamarin.iOS, Visual Studio allows you to add this reference because it knows it's a supported platform.
You'll also need to install the Microsoft Async package from NuGet for things to function properly. The one catch here is it will add some references conflicting with what's provided by default by Xamarin. To fix this, simply remove the iOS project's references for System.Runtime and System.Threading.Tasks.
First, iOS will need to provide its implementation of ILocationProvider, as shown in Listing 2.
Listing 2: Implementing ILocationProvider in iOS
using System.Linq;
using System.Threading.Tasks;
using MonoTouch.CoreLocation;
using WeatherCore;
namespace WeatherAppIOS
{
public class LocationProvider : ILocationProvider
{
private readonly CLLocationManager _locationManager;
public LocationProvider()
{
_locationManager = new CLLocationManager();
_locationManager.StartUpdatingLocation();
}
public Task<LocationInfo> GetCurrentLocation()
{
var completion = new TaskCompletionSource<LocationInfo>();
_locationManager.LocationsUpdated +=
(sender, args) =>
{
_locationManager.StopUpdatingLocation();
var coordinate = args.Locations.First().Coordinate;
completion.SetResult(new LocationInfo(coordinate.Latitude, coordinate.Longitude));
};
_locationManager.StartUpdatingLocation();
return completion.Task;
}
}
}
When the location is requested, it will use the built-in location management library in iOS to get the location. One key thing to note here is that once the location is determined, StopUpdatingLocation is called on the location manager object. Listening for location updates can come at the cost of battery life on a device if you use it too much, so it's important to be conscious of this in your apps to avoid draining the user's battery when you don't need to.
Finally, replace the default MyViewController class with the following code:
using MonoTouch.UIKit;
using WeatherCore;
namespace WeatherAppIOS
{
public class MyViewController : UIViewController
{
private WeatherClient _client;
public override async void ViewDidLoad()
{
base.ViewDidLoad();
_client = new WeatherClient(new LocationProvider());
var weatherDescription = await _client.GetCurrentWeather();
new UIAlertView("Current Weather", weatherDescription, null, "Ok", null).Show();
}
}
}
When the screen is loaded, it creates an instance of WeatherClient with the iOS location provider, and waits for a result. Once it gets the result, it just displays it as an alert (see Figure 2). The first time your app requests a location on iOS the user will be prompted to give the app permission to use the user's location. Once that's granted, it's remembered for future requests so the user won't be prompted again. If you're running in the simulator and haven't manually updated the simulator's location before, by default it will place you in Cupertino, Calif., the location of Apple headquarters.
Extending to Other OSes
As you can see, it's easy to create a PCL containing a large amount of an app's code that's compatible with a long list of platforms. In this case, the iOS side of the implementation was minimal, and plugged neatly into the shared component. Now you can take this portable library and, using this approach, extend it into Android, Windows Phone or any other supported platform.