Wednesday 27 June 2018

Xamarin Sharedproject

There are two ways to share your business logic inside of an xamarin app, one is a Shareproject and the other is a portable class library; in this post we'll discuss the former.

A Sharedproject shares all of it's content with each individual target project, meaning that everything inside your shared project is copied into your iOS, Droid and UWP projects at compile time. This means that the code written in your sharedproject will become part of your Droid, iOS and UWP projects and then complied using that target projects compilation settings. Sharedprojects do not have an output of their own, there is not dll or exe, they just become part of the projects that they are linked to.



This will work just fine if you have no platform independent code, but for anything but the most trivial apps this will not be the case and we need to come up with some code sharing strategies.

Conditional compilation

The simplest method is Conditional compilation, which is the use of preprocessor directives to decide which code gets compiled in which project.

Symbol Project
#if __MOBILE__ Any mobile project (vs. desktop)
#if __ANDROID__ Xamarin.Android - defined by the compiler
#if __IOS__ Xamarin.iOS - defined by the compiler
#if __MAC__ Xamarin.Mac - defined by the compiler
#if __TVOS__ iOS tvOS - defined by the compiler
#if __WATCHOS__ iOS watchOS - defined by the compiler
#if WINDOWS_UWP Windows 10 UWP - defined in build settings

These conditional compilation symbols are defined in the project settings and need to be define for both DEBUG and RELEASE Builds separately.

  • The advantage of this method is that it's fairly simple and doesn't take too much planning or thinking about to leverage
  • The disadvantage is that it becomes very difficult to maintain as platform independent code grows.
  • This is the appropriate approach when you have very little Platform-specific code 

Class Mirroring

Another Code sharing strategy in Shareprojects is Class Mirroring, class mirroring let's you overwrite code in your shared project with code inside your platform specific project, let's take a look at the following Xamarin form

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="pav.SharedProject00.MainPage">
    <StackLayout>
        <Button x:Name="MyButton" Text="click me"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"/>
    </StackLayout>
</ContentPage>

Now let's take a look at the codebehind

using pav.SharedProject00.Services;
using System;
using Xamarin.Forms;

namespace pav.SharedProject00
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
            MyButton.Clicked += MyButton_Clicked;
        }

        private void MyButton_Clicked(object sender, EventArgs e)
        {
            var ms = new MessageService();
            ms.Show("Hello world", "This is my message");
        }
    }
}


we simply create an event handler for the clicked event on our button, in this handler we call the show method on our MessageService, so let's take a look at that.

namespace pav.SharedProject00.Services
{
    public class MessageService
    {
        public void Show(string title, string message) => Popup.ShowPopup(title, message);
    }
}

now our message simply calls our static ShowPopup method on our Popup class, but where is that define or i should where are they defined, because each project get's their own implementation of ShowPopup, first let's take a look at our android implementation

Droid


using Android.App;
using Android.Text;
using Android.Widget;

namespace pav.SharedProject00.Services
{
    internal class Popup
    {
        internal static void ShowPopup(string title, string message)
        {
            var toast = Html.FromHtml($"<big><b>{title}</b></big>: {message}");
            Toast.MakeText(Application.Context, toast, ToastLength.Short).Show();
        }
    }
}

And now let's take a look at our UWP implementation 

UWP

using Windows.UI.Popups;

namespace pav.SharedProject00.Services
{
    internal class Popup
    {
        internal static void ShowPopup(string title, string message)
        {
            var dialog = new MessageDialog(message,title);
            dialog.ShowAsync();
        }
    }
}

notice that both implementation are different however they both have the same namespace and the same signature, this allows us to specify our platform specific code in the independent projects

Partial Classes & Methods

A third option for sharing code in a Sharedproject is to use partial classes and methods; this approach let's you define part of your class inside your sharedproeject and the other part inside your platform specific project.

But first let's take a look at the following console application to get a feel for partial classes.

using System;

namespace Pav.ConsoleSharedOne
{
    partial class Person {
        public DateTime BirthDate { get; set; }
        public bool Male { get; set; }
        partial void displayPerson();
    }

    partial class Person {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int GetAge()
        {
            var today = DateTime.Today;
            var age = today.Year - BirthDate.Year;
            if (BirthDate > today.AddYears(-age))
                age--;
            return age;
        }

        partial void displayPerson()
        {
            var prefix = Male ? "Mr." : "Ms.";
            var msg = $"{prefix} {FirstName} {LastName} is {GetAge()} years old";
            Console.WriteLine(msg);
        }

        public void DisplayPerson() => displayPerson();
    }

    class Program {
        static void Main(string[] args) {
            var p1 = new Person {
                Male = true,
                FirstName = "Pawel",
                LastName = "Ciucias",
                BirthDate = new DateTime(1984, 1, 31)
            };

            var p2 = new Person {
                Male = false,
                FirstName = "Magda",
                LastName = "Tywoniuk",
                BirthDate = new DateTime(1984, 6, 28)
            };

            p1.DisplayPerson();
            p2.DisplayPerson();

        }
    }
}

now as the name implies partial classes let us break our logic out into multiple class declarations, now above i define both parts of the Person class in the same file, but i could have just as easily separated it into two different files.

PersonOne.cs
using System;
namespace Pav.PartialClassExample
{
    partial class Person
    {
        public DateTime BirthDate { get; set; }
        public bool Male { get; set; }
        partial void displayPerson();
    }
}

PersonTwo.cs
using System; 
namespace Pav.PartialClassExample
{
    partial class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public int GetAge()
        {
            var today = DateTime.Today;
            var age = today.Year - BirthDate.Year;
            if (BirthDate > today.AddYears(-age))
                age--;
            return age;
        }

        partial void displayPerson()
        {
            var prefix = Male ? "Mr." : "Ms.";
            var msg = $"{prefix} {FirstName} {LastName} is {GetAge()} years old";
            Console.WriteLine(msg);
        }

        public void DisplayPerson() => displayPerson();
    }
}


Main.cs
using System;
using Pav.PartialClassExample;

namespace Pav.ConsoleSharedOne{
    class Program {
        static void Main(string[] args) {
            var p1 = new Person {
                Male = true,
                FirstName = "Pawel", LastName = "Ciucias",
                BirthDate = new DateTime(1984, 1, 31)
            };

            var p2 = new Person {
                Male = false,
                FirstName = "Magda", LastName = "Tywoniuk",
                BirthDate = new DateTime(1984, 6, 28)
            };

            p1.DisplayPerson();
            p2.DisplayPerson();
        }
    }
}

now some caveats
  • All parts of a partial class have to be within the same namespace, they can be in different files, but must share the same namespace.
  • partial methods cannot have an accessor such as [public, private, etc]
  • it goes without saying but partial methods must be void
So how does this apply to SharedProjects? 
First add another console application to your solution
Next add a Shared project to your solution


Then add references from both your console applications to your Shared project


now once that's done move your PersonOne.cs file into your shared project.
create a different implementation of PersonTwo.cs in your second console application.


You should have something like the above, notice that both console applications have implementations for PersonTwo.cs and share the implementation for PersonOne.cs

Let's take a look at our new implementation of PersonTwo.cs


using System;

namespace Pav.PartialClassExample
{
    partial class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public bool Married { get; set; }
        public int GetAge()
        {
            var today = DateTime.Today;
            var age = today.Year - BirthDate.Year;
            if (BirthDate > today.AddYears(-age))
                age--;
            return age;
        }

        partial void displayPerson()
        {
            string prefix;
            if (Male)
                prefix = "Mr.";
            else if (Married)
                prefix = "Mrs.";
            else
                prefix = "Miss.";

            var msg = $"{prefix} {LastName} is {GetAge()} years old";
            Console.WriteLine(msg);
        }

        public void DisplayPerson() => displayPerson();
    }
}

It's a slight change in the displayPerson() method but the results are different. 

Now you may be wondering ok, well that's fine and dandy, but why don't i just accomplish the same thing with inheritance, I could define a person class inside my shared project and then inherit that implementation in my platform specific projects and override methods that need to be overridden and so one; and the answer is complexity. 

In a complex project inheritance is more likely along the lines you want to head, but that's with the assumption that your Sharedproject is the Business layer, but what if it's the View layers as well? Finally move your program file from one of your console apps into your shared project and delete it from the other so that your solution looks like the following.


now based on whichever console project you execute you'll leverage a different version of the PersonTwo.cs file, but both console applications will share PersonOne.cs and Program.cs. Remember that the content of the shared project is complied inside of each console application, so you can think of the above solution as.