Creating shared utility libraries in a cross-platform Xamarin environment adds challenges when compared to creating standard single-platform libraries. This post shows the architecture I use to easily handle those challenges, with detailed steps and settings for creating the libraries and for their use.
Overview
Every development group has at least one “utility” library that holds the common code they use in most of their projects. That code could be anything from logging routines to string extensions to database or HTTP functions, and it might also include routines for graphics or screen drawing, user alerts, or other UI-centric code.
For groups doing cross-platform development in Xamarin/C#, creating libraries that work properly in a cross-platform environment adds extra complexities. Although the basic routines like string extensions or datetime functions are common across all platforms, anything relating to device hardware or user interface or even file I/O varies by platform. Even code that might normally be thought of as “common” may in reality vary by platform, including logging or certain datetime functions or even some string handling. This adds new challenges to the task of creating a simple, shared utility library.
The typical approach for creating and using cross-platform code in a Xamarin environment is to use dependency injection or the ServiceLocator pattern, resolving references to the library classes at runtime. This works well for larger subsystems and more complex components, even for code that is inside the application itself, but it is overkill for accessing the multitude of smaller routines typically found in an external utility library. Also, using those strategies to access small utility classes leaves the calling code littered with excess “noise” for things as simple as converting a datetime value.
One Approach
One approach to solving this problem is to separate the utility code into two different sets of libraries: a common library that contains only the code that works across all platforms, then another set of platform specific libraries, with each platform-specific library containing the version of the code for that one platform. However, this approach pushes the burden of knowing whether each and every library routine is platform-agnostic or platform-specific onto each developer who wants to use them in order to know which libraries to reference in each class. Even worse, for utility classes where most of the code is cross-platform but where a few methods vary by platform, you need to create two different classes, one in each library, with class names that are similar-but-different: “DateTimeUtils” for the common methods vs. “DateTimeUtilsIOS” for the iOS-specific methods, for example. Although you could use the same class name in both libraries, doing so would require you to more fully-qualify the class name on each use, referencing something like “XPlatUtils.DateTimeUtils.DateTimeOffsetToUtc()” for a common method vs “IOSUtils.DateTimeUtils.DateTimeToNSDate()” for an iOS-specific method, for example. It’s ugly and it smells bad.