The past few months, Roy Cornelissen and I presented several sessions on how you can achieve code sharing using the Xamarin.Android and Xamarin.iOS products, and not only enable code sharing cross iOS and Android, but also Windows Phone and Windows 8.
During our sessions we always show some simple language tricks that can help you simplify your code base in terms of how you share your code. In this blog post I want to show you these simple tricks and provide you with some sample code that you can use yourself to try it out.
So when you try to share code, you need to make sure that you don’t leak any platform specific API’s or types when you call or use certain API’s. In our sessions we often use the picture below to show where the places are that you need to write a little bit of glue code to ensure you don’t leak those platform specific things into the shared code base.
The grey areas in the diagram are where we need to bridge platform specific to shared logic. Furthermore, on the right side of the diagram, we mention a couple of cross cutting concerns that are usually easy to share across platforms. These are concerns like data management, security or utilities like analytics or logging.
First, let’s start with how we set up our solution structure, and get into the coding tricks from there.
We have found that an often advocated approach for code sharing, using linked files, doesn’t play nice with some version control systems. We would run into issues with locked files, and source control systems that just didn’t handle this scenario very well. So we have adopted a slightly different approach.
We let each platform have its own solution that resides in the same root folder for the application. Each of the platforms will have (at least) two projects: the app itself and a Shared library project (you can call it Core, Shared, Implementation, whatever suits your coding standards). The shared library .csproj files are all put into the same directory, so that on disk, all of the platforms have the exact same files in the same structure.
Such a solution structure looks as follows:
and on iOS:
In the screenshots, you’ll also see that we have included and excluded specific files in each project. This is the next step in our code sharing tricks.
Platform agnostic API’s
Now the big question is: how can we create those platform agnostic API’s?
We have done this by using Partial classes and Partial methods. So how does that work?
With Partial classes, you can create a Class and spread the implementation of that class across multiple files. So we have a class A in file A.cs and we can have the second part of class A but now in a file called A.Android.cs or A.iOS.cs, depending on the platform.
So now what we do is, we define the platform agnostic API in the file called A.cs and that file is included in all our projects. Next, we define public methods that are called by the shared code in our application, but the actual work will be delegated to a Partial method. This partial method is implemented in the partial class file for the particular platform.
Now in the platform specific implementation, we can use any platform specific library we want since it will be included into one platform project only.
This saves us from having to use #ifdef statements all over our code, and also prevents any overhead that you might get from creating a complex inheritance chain.
We also provided a simple demo project that you can download below, that will show you how you can set up the folder structure for your solutions so the right files go in the right folders. The folder setup looks as follows for the entire solution:
The folder SharedCode contains all the shared libraries included in the four different project types for each and every device. The projects are mapped to the exact same folders, so making a change in a shared code file in any of those solutions will trigger the change in the others as well. You also see the SharedDemo.<device> projects. These are the device specific implementations for each device, providing the native UI experience we strive for.
Design patterns for reuse
Now that we have a way to abstract away some platform specific API’s for accessing device capabilities from shared code, we now need a way to have shared logic interact with device specific UI code. First, let’s look at the design patterns that are common for the platforms we work with:
Apple uses a strict MVC model, with a clear separation between controller logic and views. Android uses different terminology, but generally the same concepts and separation of concerns. Windows Phone and Windows 8 all rely on the MVVM pattern.
Basically, common underlying pattern is the good old MVC pattern, so that is how we set up our apps:
The Controller is our shared piece of logic. The Controller can interact with device capabilities through the platform agnostic API’s that we discussed earlier, will call back end services and manipulates the model.
The Model is shared across platforms.
The View, on top, is the actual platform specific implementation. In the Windows platforms, there will be a ViewModel between the View and the Controller. In iOS, this will be your UIViewController and in Android it will be your Activity class that sits in between.
It’s easy to define a shared Controller API that is called from upstream callers only. However, in order to keep the UI responsive, all mobile platforms advocate the use of asynchronous background tasks to perform heavy lifting. Assuming that the heavy work is implemented in shared logic, you’ll need a way to have the Controller report back to the platform specific UI.
One way to have the UI react to Controller results is to have the UI subscribe to PropertyChanged events, much the same way that MVVM works in the Windows platforms.
Another way that we have found helpful is by use of Action<T> delegates. This is illustrated in the diagram below:
Each controller method gets two extra parameters: an Action delegate that will be invoked when the heavy work has completed successfully, passing any resulting data as the argument and an Action<Exception> delegate that will be invoked when an exception as occurred, passing the exception as an argument. Each platform can have its own implementation of such a delegate, as shown in the diagram.
This way, the shared controller logic can do callbacks to the platform specific UI without actually knowing the platform. You could also do this with interfaces that you’d have to implement in your ViewModel, UIViewController or Activity, but we found that this lead to unnecessary clutter and dependencies.
As mentioned, you can find the patterns we described in the sample app that you can find here: https://dl.dropboxusercontent.com/u/16850010/SharedDemo.zip
Furthermore, we recommend that you watch our entire session on the Xamarin Evolve website: http://xamarin.com/evolve/2013#session-shy07zqsoz
You can find Roys blog here, where he also has some nice posts about our experiences and the Evolve Conference.