Xamarin, Sharing is Good

Achindra Bhatnagar
Achindra
Published in
4 min readApr 15, 2017

Xamarin saves a lot of programming, testing and maintenance effort by using a shared codebase. You write code, find bugs and fix them, just Once, not the number of platforms that you are supporting. Although we do have some platform specific code like the native UI code or the controller code that takes you from UI controls to the logic or platform specific features, there is still about 70–85% code that can be shared.

There is a C# version of every native library type in Xamarin which lets you program in C# and it generates a full native code for the respective platform. You, thus, get native performance from shared code.

You can share code for accessing web services or databases, parsing data or your core business logic. For a feature that requires platform specific code, you can create abstractions over it and call it from the shared code, but before that, it is a good idea to check if an abstraction already exists

  • .NET Foundation
  • NuGet
  • Xamarin Component Store (commercial binary components)
  • github.com/Xamarin/plugins (Open Source Plugins)
  • github.com/Xamarin
  • Xamarin.Social
  • Xamarin.Auth (OAuth)
  • Xamarin.Mobile (common mobile services)

These abstraction are really helpful an saves lot of your time.

So, how is shared code organized?

Shared Projects

Xamarin has a new type of project called Shared Projects; Common code lives in this container. While compiling, all files in shared project are compiled into each target platform. Be sure to add reference to shared project in each of the platform project that you have.

SharedProject

There are a few techniques of writing shared code that we should now look into.

Conditional Compilation

We can create blocks of code with #if compiler directives (same as C language). For each platform we can define these conditinal compilation symbols through project build settings.

The down side is that it is difficult to see what get compiled and if a change breaks another target build without compiling for it. It is also difficult to manage code like this.

// Shared Project
public class NotesApp
{
void SaveFile()
{
#if __IOS__
new UIAlertView("File Save",
"File saved to cloud drive",
null,
"OK").Show();
#endif
}
}

Some of the known symbols are

Class Mirroring

This is a more structured approach in which we define a class in each of the platforms but we keep the class name and methods same. We can then use the same class name and methods in the shared code and its implementation will come from its respective target project.

The class can use inheritance to provide shared functionality where possible and maximize shared code.

This is easier to maintain but you still need to compile for all platforms to be sure that there are no breaking changes.

// Shared Project
public class NotesApp
{
void SaveFile()
{
Message.Show("File Save", "File saved to cloud drive");
}
}
// iOS Project
internal class Message()
{
internal static void Show(string head, string msg)
{
new UIAlertView(head, message, null, "OK").Show();
}
}
// Android Project
internal class Message()
{
internal static void Show(string head, string msg)
{
Android.App.AlertDialog.Builder dialog = new AlertDialog.Builder(this);
AlertDialog alert = dialog.Create();
alert.SetTitle(head);
alert.SetMessage("msg");
alert.SetButton("OK", (c, ev) =>
{
// 'OK' click action
});
alert.Show();
}
}

Partial Classes and Methods

C# has Partial keywork to define partial classes and methods. A partial class definition can be split across multiple source files. The common part of class is defined in the shared project while platform specific methods are added by respective projects in the class.

A partial method on the other hand is to tell the compiler that the method is optional. At compile time if the method definition is found in respective project, code is emitted to call the method. Otherwise every call to the method is removed from the binary.

This is helpful if you want to do something on a particular platform and not on other.

// Shared Project
partial class NotesApp
{
partial void Show(string head, string msg);
void OnSaveFile()
{
Show("File Save", "File saved to cloud drive");
}
}
// iOS Project
partial class NotesApp()
{
void Show(string head, string msg)
{
new UIAlertView(head, message, null, "OK").Show();
}
}
// Android Project
partial class NotesApp()
{
// Method not defined
}

These patterns will help you write shared code and make it easy to maintain and extend.

Try it yourself!

//
//Conditional Compilation Sample
//
// Sample Shared Codepublic class Phone
{
public string Name { get; set; }
public long Number { get; set; }
public string Address { get; set; }
}
public static class PhoneDirectory
{
const string YelloPages = "YellowPages.json";
public static async Task<IEnumerable<Phone>> Load()
{
using (var reader = new StreamReader(await OpenFile()))
{
return JsonConvert.DeserializeObject<List<Phone>>(await reader.ReadToEndAsync());
}
}
private async static Task<Stream> OpenFile()
{
#if __ANDROID__
return Android.App.Application.Context.Assets.Open(YelloPages);
#endif
#if __IOS__
return File.OpenRead(YelloPages);
#endif
#if WINDOWS_UWP
var fileStream = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(YelloPages);
return await fileStream.OpenStreamForReadAsync();
#endif
return null;
}
}
// Sample iOS code
public override async void ViewDidLoad()
{
base.ViewDidLoad();
var data = await PhoneDirectory.Load();
TableView.Source = new ViewControllerSource<Phone>(TableView)
{
DataSource = data.ToList(),
TextProc = s => s.Name,
DetailTextProc = s => s.Number + "-" + s.Address,
};
}
// Sample Android code
protected override async void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
var data = await PhoneDirectory.Load();
ListAdapter = new ListAdapter<Phone>()
{
DataSource = data.ToList(),
TextProc = s => s.Name,
DetailTextProc = s => s.Number + "-" + s.Address
};
}
// Sample UWP Code
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
DataContext = await PhoneDirectory.Load();
}
//
// * if you are wondering what is DataContext?,
// DataContext is the default source of bindings in WPF
// We are populating that in our code and UI refreshes on its own
//

Happy Hacking!

--

--