Welcome back to part 2 of the ongoing saga of sharing code across platforms using Xamarin. The last time we talked about the why and when to share code, today we’ll cover one of the ways of how to share code.
Unfortunately we won’t be sharing pizza today, but there are 3 ways to share code across projects:
- Linked files
- Shared Projects
- Portable Class Libraries
Linked files are going the way of the dodo and generally are not used any longer and are being replaced in favor of Shared Projects. So let’sinvestigate how to share code using “Shared Projects”, and in the next post in this series we’ll investigate Portable Class Libraries, or PCLs.
What are Shared Projects?
A good place to start with is defining exactly what is meant by aShared Project. To quote Xamarin:
In other words, Shared Projects are a project type which get compiled directly into the project referencing them, and you can even use platform specific APIs within them– which are identifiedby preprocessor directives. That means you can use specific iOS, Android or Windows Phone APIs within a Shared Project and the preprocessor directive ignores anything not a part of the platform you’re compiling for. Under the hood, Visual Studio and Xamarin Studio treat Shared Projects the same way they treat linked files.
Shared Projects were introduced in Visual Studio 2013 R2 and also in Xamarin Studio 5.
Pros And Cons of Shared Projects
Advantages
One reallycool thing about Shared Projectsis that theyhave full access to all of the assemblies of .NET framework, which set them apart from the limited access that PCLs have.
You can use platform specific code within a Shared Project. So that whiz-bang feature that you developed for Android, but don’t really need for iOS, can be included within the Shared Project itself. Or more practically, certain folders within the file system are accessed differently across the platforms – but the differences in accessing those folders can be accounted for within a Shared Project.
One final benefit is that because the Shared Project is compiled directly into the referencing application’s binary – if there is a project in your solution which does not reference the Shared Project, it is not included at all in it’s binary. No unnecessary bloat.
Disadvantages
While a Shared Project does have full access to the .NET framework, you cannot add any references to the Shared Project itself. That means any 3rd party libraries, even libraries that you’ve created elsewhere, are not available.
Another is no separate DLL is created – all of the Shared Project’s code is compiled to be a part of the referencing application’s binary itself. Any assets, such as images or other media are compiled into the referencing binary as well. So code sharing across different solutions is not possible with the Shared Project.
Refactoring is difficult with a Shared Project. Any code within a preprocessor directive that is not a part of the active project will not be touched during a refactor. That can lead to a mess very easily.
All of the preprocessor directives themselves can also lead to a real mess in your code.
How To Implement Shared Projects
Let’s get down to it and talk abouthow to implement a Shared Project. One of the first things to know are the preprocessor directives. For our purposes, we’re only interested in the ones associated with mobile projects:
- IOS
- ANDROID
- WINDOWS_PHONE
Each directive is surrounded by two underscore characters. It is also worth noting thatspecific versions of Android can be targeted with the following directive:
- ANDROID_14
The code within the above preprocessor statement will only be compiled into applications targetingthe Android 14 level API and above.
Any code contained within the preprocessor directive will only be compiled into the platform the preprocessor directive is referring to. Otherwise, using a Shared Project is no different than adding a “normal” class library into your solution.
Example
Let’s demonstrate this with an example. Throughout this series of tutorials, we’ll be creating a Tabata app. A Tabata is a high intensity interval exercise, where you exerciseat a high intensity for a set amount of time, rest for a set amount of time, and repeat. The app we’ll develop will let the user specify the exercisetime, the rest time, and how many sets they want to do. It will also keep a history of the Tabatas completed in a CSV file.
The first step in creating a shared code library is to identify the code which can be shared. In our case, we’ll model the individual Tabata with properties of Date, Work Interval, Rest Interval and Number of Sets. We’ll also have a list class which contains a history of all of the completed exercises and the ability to read them from the saved CSV file.
The next step is to createa solution that has both an iOS and Android project in it. From there we’ll need to add a Shared Project. That’s under the C# node in the “Add New Project” dialog box. The naming convention usually follows
From there you’ll see a new project has been added, but there is no “References” folder (because references to other librariescan’t be added) and alsothe basic dummy class file.
Next we’ll go ahead and add code to the Shared Project. All the code will be up in GitHub, but let me point out the interesting part … the preprocessor directives with specific platform code in it. An example follows:
public void SaveTabata()
{
this.TabataDate = DateTime.Now;
string fileName = string.Empty;
#if __IOS__
// Open the documents folder and write to the file
var docPath = MonoTouch.Foundation.NSFileManager.DefaultManager.GetUrls(
MonoTouch.Foundation.NSSearchPathDirectory.DocumentDirectory,
MonoTouch.Foundation.NSSearchPathDomain.User)[0];
fileName = Path.Combine(docPath.Path, "data.csv");
#elif __ANDROID__
fileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments,
"data.csv");
#endif
if (File.Exists(fileName)) {
File.AppendAllText(fileName, string.Format ("{0},{1},{2},{3},{4}",
TabataDate.ToShortDateString (),
this.NumberOfSets, this.WorkInterval, this.RestInterval, Environment.NewLine));
}
else {
File.WriteAllText (fileName, string.Format ("{0},{1},{2},{3},{4}",
TabataDate.ToShortDateString (), this.NumberOfSets, this.WorkInterval, this.RestInterval,
Environment.NewLine));
}
}
The way to access the “user documents” directory is completely different in an iOS application versus an Android application. As such,inside of the preprocessor directives, we’re using iOS specific APIs, but not so in the Android directive. In fact, the code within the Android directive won’t even get compiled with the iOS application. In the example, the iOS project is active thus its code is enabled, while the Android portion is disabled.
The full solution can be found out on GitHub here:https://github.com/codemillmatt/TabataShared. The cool thing about using the Shared Project is that all of the application logic is contained within it. All of the timers, all of the file I/O. The only thing that the respective platform projects implement is the UI.
Summary
We went through what exactly a Shared Project is, discussed the pros and cons of Shared Projects, how to implement them and a demoed quick code example. The main thing to take away here is that Shared Projects enable you to use platform specific APIs in the same library referenced from projects targeting completely differentplatforms. However, the Shared Project cannot be used outside of its current solution.
In the next post in this series of creating cross platform code, we’ll talk about Portable Class Libraries and how they stack up against Shared Projects.
Comments