XAM110 Intro to Cross-Platform

Exercise 1: Share code using a Shared Project

The goal of this exercise is to load data from a JSON file and display it in an existing Xamarin application. The code to manage loading the file will be shared between the platform-specific projects using a Shared Project.

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

In your copy of the cloned or downloaded course exercise materials, find the Exercise 1 > Start folder. We will be using the MyTunes starter solution and the additional assets found there.

  1. Open the MyTunes.sln solution and build it to make sure you are ready to begin.
NOTE: Make sure you have selected a startup project and platform your individual development environment supports.

Add the Shared Project

As a first step, let's add a new Shared Project into the solution.

  1. Right click on the Solution node and select Add > Add a New Project.
  2. Select Shared Project - it can be found in the Multiplatform > Library category. Click Next to continue. Selecting the Shared Project template in the New Project wizard.
  3. Name the project "MyTunes.Shared" and click Create to create the project. Configuring the new project settings in the New Project wizard.
  4. You can delete the template C# file added to the project if there is one. We will replace it with some pre-written files.
  1. Right click on the solution name in the Solution Explorer and select Add -> New project....
  2. Select Shared Project - it can be found under the Visual C# category, or you can use the Search Box at the top of the dialog to quickly locate the template. Creating and naming a new project from the Shared Project template in the New Project wizard.
  3. Name the project "MyTunes.Shared"
  4. Click OK to create the project.

Add provided code to the Shared Project

In the Exercise 1 > Assets folder of your copy of the cloned or downloaded course materials there are two source files you need to add to your shared project:

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.
  1. Add both C# files to your shared project - right-click on the project node in the Solution pad and select Add > Add Files...; you can either add the files into the root of the project, or create a folder in the project to store them in.
  2. Look at both source files to get a sense of the content you will be working with.
  1. Add both C# files to your shared project - right-click on the project node in the Solution Explorer and select Add > Existing Item...; you can either add the files into the root of the project, or create a folder in the project to store them in.
  2. Look at both source files to get a sense of the content you will be working with.

Add a reference to the Shared Project

Next, we'll reference the shared project from each of the platform-specific projects.

  1. Right-click on each platform specific project and open the Add > References... dialog (or right-click the desired projects References node and select Add Reference...).
  2. Select the Shared Projects section, and then select the shared project you created earlier.
Adding a project reference from platform projects to the shared project with the Reference Manager.
Add a reference to the shared project
  1. Do this for each of the platform-specific projects you want to work with (iOS | Android | Windows).
  1. Right-click on the References folder in each platform-specific project and select Edit References....
  2. In the references dialog, select the Projects tab and then select the MyTunes.Shared project in the list.
Adding a project reference from platform projects to the shared project with the Edit References window.
Add a reference to the shared project
  1. Do these steps for each of the projects you want to work with (iOS | Android).

If you try building the projects now you'll get a compilation error. We'll need to add a reference to Newtonsoft Json.NET (which we'll do in the step).


Add the Json.NET NuGet package

  1. Open the NuGet dialog by right-clicking on the Packages folder in each of the platform-specific projects you want to work with and selecting Add Packages....
  2. Search online and find the Newtonsoft.Json component - it should be one of the first packages displayed (due to its popularity).
image
  1. Open the NuGet Package Manager - right-click on the References folder of a platform head project and select Manage NuGet Packages....
image
  1. In the Browse tab, search for Newtonsoft.Json - it should be one of the first results (due to its popularity).

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

This will install the package in all compatible projects in your solution.

  1. Add the package to each of your supported platforms - the package must be added into the platform-specific projects, notice it's not possible to add a NuGet package to the shared project.
  2. You should now be able to compile each platform-specific project.

Use the song loader from the Shared Project

Now, let's add some code into each project to use load data using the SongLoader class. Follow the steps below for each platform you will be using:

  1. Locate the ViewDidLoad method in the MyTunesViewController.cs.
  2. Comment out the existing TableView.Source assignment.
  3. Load the data using SongLoader.Load. This method uses the Task based Async pattern; you will need to await the call and decorate ViewDidLoad with the async keyword.
  4. Call ToList on the IEnumerable<Song> returned from SongLoader.Load - we'll use this in the next step. (This will require the System.Linq namespace.)
  5. Create a new ViewControllerSource<Song> and assign the following properties:
  6. DataSource to the List from the previous step.
  7. TextProc to a lambda that returns the name of a song: s => s.Name.
  8. DetailTextProc to a lambda that returns the artist and album: s => s.Artist + " - " + s.Album.
  9. Assign the ViewControllerSource to TableView.Source.
using System.Linq;
...
public async override void ViewDidLoad()
{
    base.ViewDidLoad();

    //TableView.Source = new ViewControllerSource<string>(TableView) {
    //    DataSource = new string[] { "One", "Two", "Three" },
    //};

    // Load the data
    var data = await SongLoader.Load();

    // Register the TableView's data source
    TableView.Source = new ViewControllerSource<Song>(TableView)
    {
        DataSource = data.ToList(),
        TextProc = s => s.Name,
        DetailTextProc = s => s.Artist + " - " + s.Album,
    };
}
  1. Locate the OnCreate method in MainActivity.cs.
  2. Comment out the existing ListAdapter assignment.
  3. Load the data using SongLoader.Load. This method uses the Task based Async pattern; you will need to await the call and decorate OnCreate with the async keyword.
  4. Call ToList on the IEnumerable<Song> returned from SongLoader.Load - we'll use this in the next step. (This will require the System.Linq namespace.)
  5. Create a new ListAdapter<Song> and assign the following properties:
  6. Set the DataSource property to your new list.
  7. Set the TextProc to a lambda that returns the song name: s => s.Name.
  8. Set the DetailTextProc to a lambda that returns the artist and album: s => s.Artist + " - " + s.Album.
  9. Assign the object to the ListAdapter property.
using System.Linq;
...
protected async override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);

    //ListAdapter = new ListAdapter<string>() {
    //    DataSource = new[] { "One", "Two", "Three" }
    //};

    var data = await SongLoader.Load();

    ListAdapter = new ListAdapter<Song>()
    {
        DataSource = data.ToList(),
        TextProc = s => s.Name,
        DetailTextProc = s => s.Artist + " - " + s.Album
    };
}
  1. Locate the OnNavigatedTo method in the MainPage.xaml.cs.
  2. Comment out the existing this.DataContext assignment and tempData variable.
  3. Load the data using SongLoader.Load. This method uses the Task based Async pattern; you will need to await the call and decorate OnNavigatedTo with the async keyword.
  4. Call ToList on the IEnumerable<Song> returned from SongLoader.Load - we'll use this in the next step. (This will require the System.Linq namespace.)
  5. Assign the resulting list to the DataContext property.
There is already a DataTemplate setup to display the song data.
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
    //var tempData = Enumerable.Range(1, 3).Select(i =>
    //{
    //    return new
    //    {
    //        Name = "Song" + i,
    //        Artist = "Artist" + i,
    //        Album = "Album" + i
    //    };
    //}).ToList();

    // this.DataContext = tempData;

    var data = await SongLoader.Load();

    DataContext = data.ToList();
}

Make sure each project compiles. It will fail at runtime because we haven't provided the data yet - let's do that next.


Add the songs.json data file

Next you will add the songs.json file into each platform-specific project from the Exercise 1 > Assets folder of your copy of the cloned or downloaded course materials

Make sure all your projects still build successfully. The platform projects will throw an ArgumentNullException at runtime if run, though, until we read our new data.


Add code to read the data file

Now that the data file has been added to each platform-specific project, we need to use platform-specific code to load our data. We can use any of the three approaches outlined in the course (conditional compilation, cloning and partial classes), and you should experiment and try each one in turn; however for the sake of time, we will use conditional compilation in these instructions.

All our work will be done in the SongLoader class in the shared project, specifically we'll be working in the existing OpenData method which will open the file and return it as a System.IO.Stream.

Keep in mind; with conditional compilation, the definition of the OpenData method may change as you add support for additional platforms. For example, adding support for the Windows.Storage APIs in Windows will cause the method to change and support a Task based return type.

You still want to use a single OpenData method and as a result you may have to refactor the code consuming this method.

Follow the instructions below for the target platforms you want to support.

  1. In the OpenData method, add a compiler directive for iOS; The Xamarin.iOS compiler defines __IOS__ for this purpose. (If you are doing multiple platforms, use can use elif for "else if" on subsequent conditional blocks.)
  2. In your conditional code block, use the System.IO.File.OpenRead method to open the file using the const string FileName.
  3. Make sure the iOS project builds.
private static Stream OpenData()
{
#if __IOS__
    return File.OpenRead(Filename);
#else
   // TODO: add code to open file here.
    return null;
#endif
}
  1. Add a conditional marker for the Android code; Xamarin.Android defines the __ANDROID__ symbol for this. (If you are doing multiple platforms, use can use elif for "else if" on subsequent conditional blocks.)
  2. In your conditional code block, use the Android.App.Application.Context.Assets.Open method to open the filename.
  3. Make sure the Android project builds.
private static Stream OpenData()
{
#if __ANDROID__
    return Android.App.Application.Context.Assets.Open(Filename);
#else
   // TODO: add code to open file here.
    return null;
#endif
}
  1. Add a conditional marker for Windows; Microsoft defines WINDOWS_UWP. (If you are doing multiple platforms, use can use elif for "else if" on subsequent conditional blocks.)
  2. In your conditional code blocks, use the Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync method to open the filename. This returns a StorageFile.
  3. Call OpenStreamForReadAsync on the StorageFile to retrieve a Stream.
  4. Because UWP APIs are frequently async, you will need to change the method signature to be async and use the await keyword - the calling code will also need to be adjusted for this.
  5. The code for the other platforms can remain the same - however you will get a compiler warning because the method is decorated with async but doesn't contain any awaited calls for iOS and Android. You can ignore these warnings.
  6. Make sure the Windows project builds.
public static async Task<IEnumerable<Song>> Load()
{
    using (var reader = new StreamReader(await OpenData())) {
        return JsonConvert.DeserializeObject<List<Song>>(
                await reader.ReadToEndAsync());
    }
}

private async static Task<Stream> OpenData()
{
#if WINDOWS_UWP
   var sf = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(Filename);
   return await sf.OpenStreamForReadAsync();
#else
   // TODO: add code to open file here.
   return null;
#endif
}

Run the application

Run the application on each platform - they should all display the song list.

Screenshot of the completed exercise app running on Android
Screenshot of the completed exercise app running on UWP
Screenshot of the completed exercise app running on iOS

Exercise summary

Congratulations!

In this exercise, we created a Shared Project and referenced it from iOS, Android, and Windows UWP projects. The Shared Project contained both platform-agnostic code, as well as platform-specific code isolated using compiler directives for each platform.

You can view the completed solution in the Exercise 1 > Completed folder of your copy of the cloned or downloaded course materials.

Go Back