The Xamarin Project Mystery

In this post I will try to clear up some missconceptions about the Xamarin Forms project structure, have a closer look at the differences between PCL and Shared Projects and explain some tricks that you can do with both of them.

First there was confusion

I recall when I started with Xamarin the wizard created solution looked pretty alien to me. At this time I started with a PCL based solution because that's what was recommended everywhere. We'll have closer look ate this later in this post

pcl_project_structure

There was a (portable) subproject and one with .Android and one with .iOS extension. After reading some docs I understood that the PCL part is a platform independent library and in the other two are the platform specific code. Still I did not understand how are these projects connected to each other. I got further confused reading that I cannot call any platform specific code from the PCL project unless I use the Xamarin Dependency Service

This left the feeling that there must be some black magic going on under the hood that brings all together which left me a bit uncomfortable but it seemed to work somehow.

No Magic in here

The most confusing part was that all my Apps important code like the App class and all my pages were in the PCL project. So I thought that's the project that will generate the final App and that the other two just get linked somehow.

What I did learn while already happy coding was that the .Android and .iOS projects are the so called Platform Projects which will result in our final App when compiled and linked. This projects will just link in the PCL part which just a dll, that's all to it. So in reality it was exaclty the oposite that I first thought.

PCL_project_schema

This has some interesting consequences. Although code in the PCL cannot access code inside the platform projects the other way round is indeed possible.

This suddenly also explained how the DependencyService could work. It's code is located in the Xamarin Forms (XF) library which is a PCL itself and which is referenced from our PCL project and from the Platform Project which makes it possible for the Platform Project to register instances of platform dependent classes inside the XF code which then can be retrieved via it's platform independent interface from inside our PCL project.

We don't need magical attributes and Services

to access platform specific code. All it takes is a static property in a class in our PCL project and a platform independent interface like

public interface IFileStorage
{       
    string ReadAsString(string filename);
    byte[] ReadAsBytes(string filename);
}

In each of the Platform projects we implement this interface with a platform specific implementation.

Android:

namespace XamarinSampleSolutionPCL.Droid
{
    public class FileStorage : IFileStorage
    {
        public string ReadAsString(string filename)
        {
            var bytes = ReadAsBytes(filename);
            return Encoding.UTF8.GetString(bytes);
        }

        public byte[] ReadAsBytes(string filename)
        {
            AssetManager assets = Android.App.Application.Context.Assets;
            using (Stream stream = assets.Open(filename))
            {
                using (var memStream = new MemoryStream())
                {
                    stream.CopyTo(memStream);
                    return memStream.ToArray();
                }
            }
        }

    }
}

iOS

namespace XamarinSampleSolutionPCL.iOS
{
    public class FileStorage : IFileStorage
    {
        public byte[] ReadAsBytes(string filename)
        {
            var data = File.ReadAllBytes(filename);

            return data;
        }

        public string ReadAsString(string filename)
        {
            var data = ReadAsBytes(filename);

            if (data == null)
                return string.Empty;

            return System.Text.Encoding.UTF8.GetString(data);
        }

    }
}

In our App class in the PCL Project I added a static property:

namespace XamarinSampleSolutionPCL
{
    public partial class App : Application
    {
        public static IFileStorage FileStorage;

To make everything working together we have to create an instance of the platform specific classes and assign it to this static property and we can use it inside the PCL over this static property.

Here is the example how to do it in Android:

protected override void OnCreate(Bundle bundle)
{
    TabLayoutResource = Resource.Layout.Tabbar;
    ToolbarResource = Resource.Layout.Toolbar;

    base.OnCreate(bundle);

    global::Xamarin.Forms.Forms.Init(this, bundle);

>>  App.FileStorage = new FileStorage(); // Thats the interesting line

    LoadApplication(new App());
}

I don't say that this is an optimal solution! But it should give you a better understanding of how this projects play together. Using a ServiceLocator like the XF DependencyService has the advantage that your PCL code does not use a static property of any of your classes and you can access the instance from anywhere in your code. If you want to use a ServiceLocator I personally prefer Splat which gives you much better control and does work without magical class attributes.

What are these other files for?

This might seem trivial for some of you but from my experiences in the Xamarin Slack not everyone knows. Stay tuned we will get more interesting stuff in a minute.

  • package.config: nuget packages that you add to your project are stored in here. But don't think you can just edit it to add or remove packets because they are also referenced in your .csproj files. So always use the nuget Manager or the console

In the PCL

  • App.xaml and App.xaml.cs: if you have read my previous post (if not do it now) you know that the combination of .xaml and .xaml.cs just creates a class. In this case your Appclass which has the responsibility to create your first page in it's constructor. Although it has a .xaml part this is not a page. In the .xaml part you can define global styles and the like. If I don't need the xaml part I often replace this combination with a simple App.cs class like
public class App : Application
{
    public static IFileStorage FileStorage;

    public App()
    {
        InitializeComponent();

        MainPage = new XamarinSampleSolutionPCL.MainPage();
    }

    protected override void OnStart()
    {
        // Handle when your app starts
    }

    protected override void OnSleep()
    {
        // Handle when your app sleeps
    }

    protected override void OnResume()
    {
        // Handle when your app resumes
    }
}

In the Android project:

  • MainActivity.cs: OnCreateis the first method that is called when your App starts up. So all platform specific initialization should go there.

In the iOS project:

  • AppDelegate.cs: here FinishedLaunchingis the first Method to get called when our App starts up.

Shared Projects, the black sheep of the family

If you ask in the community which one of the two you should choose you almost for certainly will hear Shared Projects are bad which actually almost got to some sort of dogma.

But first what are Shared Projects anyway? So lets have a look what the structure of a shared project looks like:

shared_project_structure

Looks pretty similar but what's that strange symbol:

shared_part_symbol

This is actually a pseudo project which means it's not a real project at all but a trick of Visual Studio (That's the reason why you cannot add a nuget to it).

All files that you add to this "project" will be added to all platform projects at the same time by using file links.

shared_project_shema

I left out the link step intentionally

So if all files get compiled to the same binary this means that all files could access platform specific code. But to be able to compile the files in the shared part in both platform projects they should not do that because what knows an iOS compiler about Android APIs.

The idea is that you only put classes in the shared part that don't use any platform specific code.

But (and here comes the critique of the people that think shared projects are bad) you can use #if __ANDROID__ and #if __IOS__ in the shared part to still use platform dependent code which makes a mess of your code and is bad style.

That you can do this does not means you have to or you should do and doesn't make Shared Projects bad. Although it somehow comforts me to know that I have this possibility as last resort 🙂 .

As long as you follow this rule there is really nothing bad about Shared Projects and fact is the core App code will seldom reused in another project.

Put all code you want to reuse somewhere else in a separate PCL project and reference that from the Platform Projects and you are fine.

Advantages of Shared Projects

If you ever used a PCL project you will earlier or later have encountered the moment where a nuget that you want to use does not match the PCL profile of your PCL project which is pretty annoying especially because you cannot change your PCL profile without uninstalling all nugets first. Even worse as more and mode nugets are migrated to netstandardthe successor to PCL you will get more conflicts.

A shared project can consume any nuget independent from it's PCL profile. So for now I actually recommend Shared Projects if you start a new one. This might change when netstandard tooling gets more mature and with the release of netstandard 2.0.

Accessing Platform code from the shared part of Shared Project

As we should not use #if what else can we do. Actually you have two possibilities.

  1. You still can use a ServiceLocator like Splat and register your platform specific interface implementations in the Platform project classes and access them via the interface and the Locator in the Shared part.

  2. You can use partial classes what someone called the poor man's bait and switch. This makes especially sense if the class contains platform independent and dependent code.

Let's implement the same service as in the PCL Solution in the Shared project:

In the Shared Part we add a file named FileStorage.cs

    public partial class FileStorage
    {

        // This is a stupid example for a platform independent method
        string ReadUpperString(string filename)
        {
            return ReadAsString(filename).ToUpper();
        }

        string ReadAsString(string filename)
        {
            return ReadAsStringImplementation(filename);
        }

        byte[] ReadAsBytes(string filename)
        {
            return ReadAsBytesImplementation(filename);
        }

    }

In the Android Part we add a file named FileStorage.Android.cs

Please note that we cannot use the same file name

public partial class FileStorage 
{
    public string ReadAsStringImplementation(string filename)
    {
        var bytes = ReadAsBytes(filename);
        return Encoding.UTF8.GetString(bytes);
    }

    public byte[] ReadAsBytesImplementation(string filename)
    {
        AssetManager assets = Android.App.Application.Context.Assets;
        using (Stream stream = assets.Open(filename))
        {
            using (var memStream = new MemoryStream())
            {
                stream.CopyTo(memStream);
                return memStream.ToArray();
            }
        }
    }

}

This part can fully access the Android APIs

And in iOS:

public partial class FileStorage
{
    public byte[] ReadAsBytesImplementation(string filename)
    {
        var data = File.ReadAllBytes(filename);

        return data;
    }

    public string ReadAsStringImplementation(string filename)
    {
        var data = ReadAsBytes(filename);

        if (data == null)
            return string.Empty;

        return System.Text.Encoding.UTF8.GetString(data);
    }

Our Solution now looks like this:

shared_project_structure_partial_class

You can find both solutions on my Github Repository:

PCL Solution

Shared Solution

Contact me:

Leave a Reply

Your email address will not be published. Required fields are marked *