My Approach: Almost Bait and Switch
The approach I recommend is very similar to the design pattern commonly called the “Bait and Switch PCL” trick, or as Miguel de Icaza calls it, the “Advanced PCL” pattern. We aren’t necessarily creating PCLs, but the pattern is the same. The pattern still creates a platform-specific library for each platform, but in a way that doesn’t require the developers using the library to know or care about the differences. Moreover, it allows additional platform specific classes or methods to be included in what appear to be “shared” classes, eliminating the need to have two class names or two namespaces. (The developer calling the platform-specific methods will still need to be aware of restrictions by platform, but only insofar as knowing to not call an iOS-specific library routine when writing Android code.) For example, in the “DateTime” example above, there would be only a single “DateTimeUtils” class. All platforms would use the same “DateTimeOffsetToUTC” method, but when coding for the iOS platform, you could also use the iOS-specific “DateTimeToNSDate” method. Both these methods — the cross-platform “ToUTC” method and the iOS-specific “ToNSDate” method — are inside the one single “DateTimeUtils” class.
At its most fundamental level, all you really end up doing is creating different versions of the same library — one for each platform — that all have the same library name, assembly name, and assembly version number, and where the method signatures are identical among all versions of the library. Source code is shared among the platform specific libraries wherever possible and extended where necessary, but because the signatures are all the same, the calling application source code will call the “same” method at coding time but it will be compiled and linked against the appropriate platform specific library.
The “Bait and Switch PCL” trick works in exactly the same way: code is duplicated among all versions of the library and is stubbed out for the PCL version, with all the different platform-specific libraries and the PCL library having the same library name, assembly name and version number, and method signatures. When these libraries are installed via NuGet, if both a PCL and a platform-specific library are available in the NuGet package, NuGet will install the platform-specific library for the platform of the current project. Your code will seem to be referring to the PCL version of the library while you code, but you will actually compile and link against the platform-specific library. There really is no “magic” — everything is simply set up so the calling application code can refer to the same method signatures regardless of platform but let the compiler and linker do the work of ensuring that the correct platform-specific version is brought in at compile and run time.
On the surface it might appear that having all these multiple libraries and versions of the code involves a lot more work and has the potential to become a maintenance nightmare, but by following the pattern below, this is definitely not the case. The maintenance is not much larger than with a single platform solution, and the benefits are large.
High-Level Overview of the Approach
Library Creation:
- Share everything possible
- Create one library for each platform
- Externally, all platform-specific libraries look almost identical
- Create one PCL with just stubs for reference by PCL-based projects (such as cross-platform Xamarin.Forms projects)
- Put all abstractions and interfaces into a single, shared, “Abstractions” library
- Create a “shared code” project; include this in all libraries except the “Abstractions” library
- Use partial classes to contain code where most methods are cross-platform but a few are platform specific
Library Use:
- Include the “Abstractions” library and the platform-specific library in each project, using the specific library for that platform
- Include the “Abstractions” library and the PCL “stubs” library in any PCL projects in the app
- Update the shared libraries in your solutions via your Continuous Integration solution