Exercise 2: Share code using Portable Class Libraries
We will work on the same project we did in the previous exercise, but this time we will use a Portable Class Library to share our JSON data loading between the iOS, Android and Windows projects. We will be starting almost where you left off in the last exercise, without the Shared Project implementation, but the code in the platform projects has already been filled out.
Note: Portable Class Libraries have been deprecated in favor of .NET Standard libraries, but you may still find these libraries used in some third-party or legacy code. Knowledge about these libraries also transfers well to using .NET Standard projects.
To complete the exercise, you will need Visual Studio for Windows or macOS with the Xamarin development tools installed. You will also need either an emulator/simulator or a device to run the exercise on. Please see the setup page if you need help installing the Xamarin development environment.
Open the starter solution
Let's begin with a fresh copy of the MyTunes application again. This is the same code you worked on in the previous exercise except all the shared code has been removed. As a result, the project will not build until we make some changes.
- Find the MyTunes starter solution in the Exercise 2 > Start folder of your copy of the cloned or downloaded course materials.
- Open the MyTunes.sln starter solution in either Visual Studio on Windows or Mac.
- Select a platform-specific startup project. To do this, you might have to change the target - for example, the iOS project will only be built if you select either an iPhone or iPhoneSimulator build type.
Add the Portable Class Library
Next, let's create the Portable Class Library (PCL) in our solution. This will hold our shared code.
Right-click on the solution node and select Add > Add New Project.... This will open the New Project wizard in Visual Studio which has several screens to fill in.
Right click on the solution node and select Add > New Project.... This will open the New Project dialog.
Add the code into the Portable Class Library
It's likely that the New Project wizard added an empty source file into your PCL project. You can delete this file.
Next, we need to add our shared source files found in the Exercise 2 > Assets folder of your copy of the cloned or downloaded course materials.
There are two source files:
File | Description |
---|---|
Song.cs |
this defines a class which provides information about a single song. |
SongLoader.cs |
this is a class which can parse a JSON file off disk into a set of Song objects. It utilizes the Newtonsoft Json.NET parser. |
Add both C# files to your Portable Class Library project.
Add a NuGet reference to Json.NET
Open the NuGet references window for the Portable Class Library.
- Right-click on the Packages folder in the PCL project and select Add Packages....
- Search for "Newtonsoft.Json" and select the package.
- Click the Add Package button to add it to your project.

- Right-click on the References folder in the PCL project and select Manage NuGet Packages....
- Select the Browse tab in the NuGet window.
- Search for "Newtonsoft.Json".
- Select the entry and click the Install button on the right-hand panel.

To add this NuGet package via the Package Manager Console, found in the menu under Tools > NuGet Package Manager > Package Manager Console, run the following command:
Install-Package Newtonsoft.Json -ProjectName MyTunes.Shared
Add a reference to the PCL
- Add a reference to the PCL for each of the target projects you want to work with (iOS | Windows | Android).
- Build the solution. The projects should now all build properly - however they will throw an ArgumentNullException at runtime because we've not added the code to load the file.
Create an abstraction
The songs.json file is located in each of the platform-specific projects - just as we had it in the Shared Project version of this exercise.
Since we've got the data file stored in a platform-specific way, we will need to use platform-specific code to get to our data.
The code to read the json file is a separate DLL from each platform-specific project; we can't just add the code into the library. In addition, because it's a Portable Library, file I/O is prohibited - we can only work with a Stream
. We need to create an abstraction - let's use an interface.
Create an IStreamLoader
- Create a new interface in the Portable Class Library. Name it
IStreamLoader
. -
It should have a single method which takes a
string
filename and returns aStream
.- Note: You will need to add
using System.IO
or fully qualify theStream
class.
- Note: You will need to add
- Name the method
GetStreamForFilename
.
namespace MyTunes
{
public interface IStreamLoader
{
System.IO.Stream GetStreamForFilename(string filename);
}
}
Implement the abstraction
- In each platform specific project, create a new class named
StreamLoader
that implementsIStreamLoader
. - Use the platform specific instructions below for each platform to complete the
GetStreamForFilename
method.
- Use the
System.IO.File.OpenRead
method to open and read the file into aStream
. - Verify that the iOS project builds.
using System.IO;
using MyTunes.Shared;
...
class StreamLoader : IStreamLoader
{
public Stream GetStreamForFilename(string filename)
{
return File.OpenRead(filename);
}
}
- Android assets are loaded through an Android
Context
, so pass in aContext
to the constructor of your implementation and save it into a field, this will make it easy to get to assets. When we instantiate theStreamLoader
we will pass in theActivity
as the parameter. - Use the passed context's
Assets.Open
method to open the filename. - Make sure the Android project builds, check the below code if you have any trouble.
using System.IO;
using MyTunes.Shared;
...
public class StreamLoader : IStreamLoader
{
private readonly Context context;
public StreamLoader(Context context)
{
this.context = context;
}
public Stream GetStreamForFilename(string filename)
{
return context.Assets.Open(filename);
}
}
- Use the
Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync
method to open the file. This returns aStorageFile
. - Because it's
async
, we would normally useasync
andawait
like we did in the last exercise. However, that would change our interface contract - which we don't want to do. So, in this case, use theAsTask()
extension method and then get theResult
property to get theStorageFile
- this will cause the call to be synchronous. - Call
OpenStreamForRead().Result
on theStorageFile
to retrieve aStream
. - Make sure the Windows project builds.
This same logic can be used for the Windows UWP project.
using MyTunes.Shared;
using System;
using System.IO;
using MyTunes.Shared;
using Windows.ApplicationModel;
...
public class StreamLoader : IStreamLoader
{
public Stream GetStreamForFilename(string filename)
{
return Package.Current.InstalledLocation.GetFileAsync(filename)
.AsTask().Result
.OpenStreamForReadAsync().Result;
}
}
The projects should all still build properly - however they will still throw an ArgumentNullException at runtime because we've not started using the StreamLoader
implementations.
Prepare to inject the abstraction
Next, we need to provide the abstraction to the PCL. Again, we could use a variety of techniques here, and you are encouraged to try different variations if you have time. In this case, we will add a static
property to the SongLoader
class and set it prior to calling Load
. We could also pass it into the Load
method, or change the class to be non-static and then pass it into a constructor. Any of these would be valid - with the constructor form probably the most ideal since we really do need the abstraction to execute.
- Open the SongLoader.cs file and add a
public static
property namedLoader
of typeIStreamLoader
. - Edit the
OpenData
method to test the property fornull
and then use it to open the passed filename. - If the
Loader
implementation hasn't been set and is stillnull
, then throw an exception.
Note: If you did not change the namespace of yourIStreamLoader
interface when you added the new source file, then you might need to include theMyTunes.Shared
namespace to have access that type.
public static class SongLoader
{
const string Filename = "songs.json";
public static IStreamLoader Loader { get; set; }
...
private static Stream OpenData()
{
if (Loader == null)
{
throw new Exception("Must set platform Loader before calling Load.");
}
return Loader.GetStreamForFilename(Filename);
}
}
Again, the projects will still build, but without setting our Loader
implementation, we will now throw our new Exception at runtime.
Inject the abstraction
Finally, set the property in each of your platform projects to your implementation. This should be done just before call to the async SongLoader.Load
method. The specific location is shown in each of the code blocks below.
On iOS, we will add code to the ViewDidLoad
override of MyTunesViewController. This is called as the main page is initialized.
// MyTunesViewController.cs
public async override void ViewDidLoad()
{
base.ViewDidLoad();
// Load the data
SongLoader.Loader = new StreamLoader();
var data = await SongLoader.Load();
...
}
On Android, we'll add our logic into the OnCreate
override of MainActivity. Remember that on Android you will need to pass the running Activity
to supply the context parameter for the constructor.
// MainActivity.cs
protected async override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SongLoader.Loader = new StreamLoader(this);
var data = await SongLoader.Load();
...
}
On Windows, we'll use the OnNavigatedTo
override in the MainPage code-behind.
// Mainpage.xaml.cs
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
SongLoader.Loader = new StreamLoader();
this.DataContext = await SongLoader.Load();
}
Test the applications
Run each of the platforms - they should all display the song list. Remember to select a build configuration for each platform - for example, Windows UWP requires either x86
or x64
and iOS requires either iPhone
or iPhoneSimulator
.



Exercise summary
In this exercise, you have utilized a Portable Class Library to hold code that is shared between several application styles. You used a design pattern known as Dependency Injection to integrate platform-specific logic into the shared code in the form of file I/O. You can learn more about the DI pattern through the documentation links on the right.
You can view the completed solution in the Exercise 2 > Completed folder of your copy of the cloned or downloaded course materials.