In this article by Jonathan Peppers, author of the book Xamarin Cross-platform Application Development, we will see how Xamarin’s tools promise to share a good portion of your code between iOS and Android while taking advantage of the native APIs on each platform where possible. Doing so is an exercise in software engineering more than a programming skill or having the knowledge of each platform. To architect a Xamarin application to enable code sharing, it is a must to separate your application into distinct layers. We’ll cover the basics of this in this article as well as specific options to consider in certain situations.
In this article, we will cover:
(For more resources related to this topic, see here.)
The Model-View-ViewModel (MVVM) design pattern was originally invented for Windows Presentation Foundation (WPF) applications using XAML for separating the UI from business logic and taking full advantage of data binding. Applications architected in this way have a distinct ViewModel layer that has no dependencies on its user interface. This architecture in itself is optimized for unit testing as well as cross-platform development. Since an application’s ViewModel classes have no dependencies on the UI layer, you can easily swap an iOS user interface for an Android one and write tests against the ViewModellayer.
The MVVM design pattern is also very similar to the MVC design pattern. The MVVM design pattern includes the following:
The following figure shows you the MVVM design pattern:
It is important to note that the interaction between the View and ViewModel layers is traditionally created by data binding with WPF. However, iOS and Android do not have built-in data binding mechanisms, so our general approach throughout the article will be to manually call the ViewModel layer from the View layer. There are a few frameworks out there that provide data binding functionality such as MVVMCross and Xamarin.Forms.
To understand this pattern better, let’s implement a common scenario. Let’s say we have a search box on the screen and a search button. When the user enters some text and clicks on the button, a list of products and prices will be displayed to the user. In our example, we use the async and await keywords that are available in C# 5 to simplify asynchronous programming.
To implement this feature, we will start with a simple model class (also called a business object) as follows:
public class Product
{
public int Id { get; set; } //Just a numeric identifier
public string Name { get; set; } //Name of the product
public float Price { get; set; } //Price of the product
}
Next, we will implement our Model layer to retrieve products based on the searched term. This is where the business logic is performed, expressing how the search needs to actually work. This is seen in the following lines of code:
// An example class, in the real world would talk to a web
// server or database.
public class ProductRepository
{
// a sample list of products to simulate a database
private Product[] products = new[]
{
new Product { Id = 1, Name = “Shoes”, Price = 19.99f },
new Product { Id = 2, Name = “Shirt”, Price = 15.99f },
new Product { Id = 3, Name = “Hat”, Price = 9.99f },
};
public async Task<Product[]> SearchProducts(
string searchTerm)
{
// Wait 2 seconds to simulate web request
await Task.Delay(2000);
// Use Linq-to-objects to search, ignoring case
searchTerm = searchTerm.ToLower();
return products.Where(p =>
p.Name.ToLower().Contains(searchTerm)) .ToArray();
}
}
It is important to note here that the Product and ProductRepository classes are both considered as a part of the Model layer of a cross-platform application. Some might consider ProductRepository as a service that is generally a self-contained class to retrieve data. It is a good idea to separate this functionality into two classes. The Product class’s job is to hold information about a product, while the ProductRepository class is in charge of retrieving products. This is the basis for the single responsibility principle, which states that each class should only have one job or concern.
Next, we will implement a ViewModel class as follows:
public class ProductViewModel
{
private readonly ProductRepository repository =
new ProductRepository();
public string SearchTerm
{
get;
set;
}
public Product[] Products
{
get;
private set;
}
public async Task Search()
{
if (string.IsNullOrEmpty(SearchTerm))
Products = null;
else
Products = await repository.SearchProducts(SearchTerm);
}
}
From here, your platform-specific code starts. Each platform will handle managing an instance of a ViewModel class, setting the SearchTerm property, and calling Search when the button is clicked. When the task completes, the user interface layer will update a list displayed on the screen.
If you are familiar with the MVVM design pattern used with WPF, you might notice that we are not implementing INotifyPropertyChanged for data binding. Since iOS and Android don’t have the concept of data binding, we omitted this functionality. If you plan on having a WPF or Windows 8 version of your mobile application or are using a framework that provides data binding, you should implement support for it where needed.
You might be asking yourself at this point, how do I set up my solution in Xamarin Studio to handle shared code and also have platform-specific projects? Xamarin.iOS applications can only reference Xamarin.iOS class libraries, so setting up a solution can be problematic. There are several strategies for setting up a cross-platform solution, each with its own advantages and disadvantages.
Options for cross-platform solutions are as follows:
To understand each option completely and what different situations call for, let’s define a solution structure for each cross-platform solution. Let’s use the product search example and set up a solution for each approach.
To set up file linking, perform the following steps:
When all is done, you will have a solution tree that looks something like what you can see in the following screenshot:
You should consider using this technique when you have to reference different libraries on each platform. You might consider using this option if you are using MonoGame, or other frameworks that require you to reference a different library on iOS versus Android.
Setting up a solution with the cloned project files approach is similar to file linking, except that you will have to create an additional class library for each platform. To do this, create an Android library project and an iOS library project in the same ProductSearch.Core directory. You will have to create the projects and move them to the proper folder manually, then re-add them to the solution. Right-click on the solution and navigate to Display Options | Show All Files to add the required C# files to these two projects. Your main iOS and Android projects can reference these projects directly.
Your project will look like what is shown in the following screenshot, with ProductSearch.iOS referencing ProductSearch.Core.iOS and ProductSearch.Droid referencing ProductSearch.Core.Droid:
A Portable Class Library (PCL) is a C# library project that can be supported on multiple platforms, including iOS, Android, Windows, Windows Store apps, Windows Phone, Silverlight, and Xbox 360. PCLs have been an effort by Microsoft to simplify development across different versions of the .NET framework. Xamarin has also added support for iOS and Android for PCLs. Many popular cross-platform frameworks and open source libraries are starting to develop PCL versions such as Json.NET and MVVMCross.
Let’s create our first portable class library:
Each solution type has its distinct advantages and disadvantages. PCLs are generally better, but there are certain cases where they can’t be used. For example, if you were using a library such as MonoGame, which is a different library for each platform, you would be much better off using a shared project or file linking. Similar issues would arise if you needed to use a preprocessor statement such as #if IPHONE or a native library such as the Facebook SDK on iOS or Android.
Setting up a shared project is almost the same as setting up a portable class library. In step 2, just select Shared Project under the general C# section and complete the remaining steps as stated.
When using shared projects, file linking, or cloned project files, one of your most powerful tools is the use of preprocessor statements. If you are unfamiliar with them, C# has the ability to define preprocessor variables such as #define IPHONE , allowing you to use #if IPHONE or #if !IPHONE.
The following is a simple example of using this technique:
#if IPHONE
Console.WriteLine(“I am running on iOS”);
#elif ANDROID
Console.WriteLine(“I am running on Android”);
#else
Console.WriteLine(“I am running on ???”);
#endif
In Xamarin Studio, you can define preprocessor variables in your project’s options by navigating to Build | Compiler | Define Symbols, delimited with semicolons. These will be applied to the entire project. Be warned that you must set up these variables for each configuration setting in your solution (Debug and Release); this can be an easy step to miss. You can also define these variables at the top of any C# file by declaring #define IPHONE, but they will only be applied within the C# file.
Let’s go over another example, assuming that we want to implement a class to open URLs on each platform:
public static class Utility
{
public static void OpenUrl(string url)
{
//Open the url in the native browser
}
}
The preceding example is a perfect candidate for using preprocessor statements, since it is very specific to each platform and is a fairly simple function. To implement the method on iOS and Android, we will need to take advantage of some native APIs. Refactor the class to look as follows:
#if IPHONE
//iOS using statements
using MonoTouch.Foundation;
using MonoTouch.UIKit;
#elif ANDROID
//Android using statements
using Android.App;
using Android.Content;
using Android.Net;
#else
//Standard .Net using statement
using System.Diagnostics;
#endif
public static class Utility
{
#if ANDROID
public static void OpenUrl(Activity activity, string url)
#else
public static void OpenUrl(string url)
#endif
{
//Open the url in the native browser
#if IPHONE
UIApplication.SharedApplication.OpenUrl(
NSUrl.FromString(url));
#elif ANDROID
var intent = new Intent(Intent.ActionView,
Uri.Parse(url));
activity.StartActivity(intent);
#else
Process.Start(url);
#endif
}
}
The preceding class supports three different types of projects: Android, iOS, and a standard Mono or .NET framework class library. In the case of iOS, we can perform the functionality with static classes available in Apple’s APIs. Android is a little more problematic and requires an Activity object to launch a browser natively. We get around this by modifying the input parameters on Android. Lastly, we have a plain .NET version that uses Process.Start() to launch a URL. It is important to note that using the third option would not work on iOS or Android natively, which necessitates our use of preprocessor statements.
Using preprocessor statements is not normally the cleanest or the best solution for cross-platform development. They are generally best used in a tight spot or for very simple functions. Code can easily get out of hand and can become very difficult to read with many #if statements, so it is always better to use it in moderation. Using inheritance or interfaces is generally a better solution when a class is mostly platform specific.
Dependency injection at first seems like a complex topic, but for the most part it is a simple concept. It is a design pattern aimed at making your code within your applications more flexible so that you can swap out certain functionality when needed. The idea builds around setting up dependencies between classes in an application so that each class only interacts with an interface or base/abstract class. This gives you the freedom to override different methods on each platform when you need to fill in native functionality.
The concept originated from the SOLID object-oriented design principles, which is a set of rules you might want to research if you are interested in software architecture. There is a good article about SOLID on Wikipedia, (http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) if you would like to learn more. The D in SOLID, which we are interested in, stands for dependencies. Specifically, the principle declares that a program should depend on abstractions, not concretions (concrete types).
To build upon this concept, let’s walk you through the following example:
For reference, the full implementation of this example will look like the following snippet:
public interface ISettings
{
bool IsSoundOn
{
get;
set;
}
}
//On iOS
using MonoTouch.UIKit;
using MonoTouch.Foundation;
public class AppleSettings : ISettings
{
public bool IsSoundOn
{
get
{
return NSUserDefaults.StandardUserDefaults
BoolForKey(“IsSoundOn”);
}
set
{
var defaults = NSUserDefaults.StandardUserDefaults;
defaults.SetBool(value, “IsSoundOn”);
defaults.Synchronize();
}
}
}
//On Android
using Android.Content;
public class DroidSettings : ISettings
{
private readonly ISharedPreferences preferences;
public DroidSettings(Context context)
{
preferences = context.GetSharedPreferences(
context.PackageName, FileCreationMode.Private);
}
public bool IsSoundOn
{
get
{
return preferences.GetBoolean(“IsSoundOn”, true”);
}
set
{
using (var editor = preferences.Edit())
{
editor.PutBoolean(“IsSoundOn”, value);
editor.Commit();
}
}
}
}
Now you will potentially have a ViewModel class that will only reference ISettings when following the MVVM pattern. It can be seen in the following snippet:
public class SettingsViewModel
{
private readonly ISettings settings;
public SettingsViewModel(ISettings settings)
{
this.settings = settings;
}
public bool IsSoundOn
{
get;
set;
}
public void Save()
{
settings.IsSoundOn = IsSoundOn;
}
}
Using a ViewModel layer for such a simple example is not necessarily needed, but you can see it would be useful if you needed to perform other tasks such as input validation. A complete application might have a lot more settings and might need to present the user with a loading indicator. Abstracting out your setting’s implementation has other benefits that add flexibility to your application. Let’s say you suddenly need to replace NSUserDefaults on iOS with the iCloud instead; you can easily do so by implementing a new ISettings class and the remainder of your code will remain unchanged. This will also help you target new platforms such as Windows Phone, where you might choose to implement ISettings in a platform-specific way.
You might be asking yourself at this point in time, how do I switch out different classes such as the ISettings example? Inversion of Control (IoC) is a design pattern meant to complement dependency injection and solve this problem. The basic principle is that many of the objects created throughout your application are managed and created by a single class. Instead of using the standard C# constructors for your ViewModel or Model classes, a service locator or factory class will manage them throughout the application.
There are many different implementations and styles of IoC, so let’s implement a simple service locator class as follows:
public static class ServiceContainer
{
static readonly Dictionary<Type, Lazy<object>> services =
new Dictionary<Type, Lazy<object>>();
public static void Register<T>(Func<T> function)
{
services[typeof(T)] = new Lazy<object>(() => function());
}
public static T Resolve<T>()
{
return (T)Resolve(typeof(T));
}
public static object Resolve(Type type)
{
Lazy<object> service;
if (services.TryGetValue(type, out service)
{
return service.Value;
}
throw new Exception(“Service not found!”);
}
}
This class is inspired by the simplicity of XNA/MonoGame’s GameServiceContainer class and follows the service locator pattern. The main differences are the heavy use of generics and the fact that it is a static class.
To use our ServiceContainer class, we will declare the version of ISettings or other interfaces that we want to use throughout our application by calling Register, as seen in the following lines of code:
//iOS version of ISettings
ServiceContainer.Register<ISettings>(() => new AppleSettings());
//Android version of ISettings
ServiceContainer.Register<ISettings>(() => new DroidSettings());
//You can even register ViewModels
ServiceContainer.Register<SettingsViewMode>(() =>
new SettingsViewModel());
On iOS, you can place this registration code in either your static void Main() method or in the FinishedLaunching method of your AppDelegate class. These methods are always called before the application is started.
On Android, it is a little more complicated. You cannot put this code in the OnCreate method of your activity that acts as the main launcher. In some situations, the Android OS can close your application but restart it later in another activity. This situation is likely to cause an exception somewhere. The guaranteed safe place to put this is in a custom Android Application class which has an OnCreate method that is called prior to any activities being created in your application. The following lines of code show you the use of the Application class:
[Application]
public class Application : Android.App.Application
{
//This constructor is required
public Application(IntPtr javaReference, JniHandleOwnership
transfer): base(javaReference, transfer)
{
}
public override void OnCreate()
{
base.OnCreate();
//IoC Registration here
}
}
To pull a service out of the ServiceContainer class, we can rewrite the constructor of the SettingsViewModel class so that it is similar to the following lines of code:
public SettingsViewModel()
{
this.settings = ServiceContainer.Resolve<ISettings>();
}
Likewise, you will use the generic Resolve method to pull out any ViewModel classes you would need to call from within controllers on iOS or activities on Android. This is a great, simple way to manage dependencies within your application.
There are, of course, some great open source libraries out there that implement IoC for C# applications. You might consider switching to one of them if you need more advanced features for service location or just want to graduate to a more complicated IoC container.
Here are a few libraries that have been used with Xamarin projects:
In this article, we learned about the MVVM design pattern and how it can be used to better architect cross-platform applications. We compared several project organization strategies for managing a Xamarin Studio solution that contains both iOS and Android projects. We went over portable class libraries as the preferred option for sharing code and how to use preprocessor statements as a quick and dirty way to implement platform-specific code.
After completing this article, you should be able to speed up with several techniques for sharing code between iOS and Android applications using Xamarin Studio. Using the MVVM design pattern will help you divide your shared code and code that is platform specific. We also covered several options for setting up cross-platform Xamarin solutions. You should also have a firm understanding of using dependency injection and Inversion of Control to give your shared code access to the native APIs on each platform.
Further resources on this subject:
I remember deciding to pursue my first IT certification, the CompTIA A+. I had signed…
Key takeaways The transformer architecture has proved to be revolutionary in outperforming the classical RNN…
Once we learn how to deploy an Ubuntu server, how to manage users, and how…
Key-takeaways: Clean code isn’t just a nice thing to have or a luxury in software projects; it's a necessity. If we…
While developing a web application, or setting dynamic pages and meta tags we need to deal with…
Software architecture is one of the most discussed topics in the software industry today, and…