Platform Specifics - Xamarin.Forms 2.3.3 Look Ahead

Lately it seems that the Xamarin.Forms team has been spending a lot of time making it easier to access the individual platform’s functionality, be it iOS, Android or Windows, from the core shared project. And maybe more importantly, everything being introduced seems to be taking away the need to create custom renderers! Platform Effects have been introduced for tweaking small UI related properties of native controls. Native embedding has been introduced to add platform controls that Xamarin.Forms does not contain directly inline (and you don’t need to use compiler directives in your shared projects either!). Now with Xamarin.Forms 2.3.3 on the horizon, two more features are being introduced that continue down that path – the ability to add native views directly into XAML and something called Platform Specifics.

This post is going to take a look at Platform Specifics in-depth, because to be honest, when I first saw them – I had a hard time wrapping my head around them … and I didn’t understand their usefulness at first. But they’ve grown on me and I definitely see their utility now. In a future post I’ll take a look at adding native views directly into XAML.

What Is a Platform Specific?

This is the part that took me the longest to get my head wrapped around … I mean, a platform specific allows you to access platform functionality from the core Forms project – you can tell that much by the name. There’s already tons of ways you can do that in Forms so what makes a Platform Specific different … what is it?

The best way to think of a Platform Specific is as a traffic cop between the Xamarin.Forms controls in the core project and doing something that can only be accomplished from the individual platform project. As the traffic cop, it decides what statement that was just executed goes to which platform. The cool part about it though is you place that platform specific code – or more appropriately the code which will invoke the platform specific code – directly in the core Forms project. That “Platform Specific” code doesn’t need to have a counterpart on every platform, it doesn’t need to conform to an interface or anything like that … it can be iOS only.

So, in Pierce Boggan’s blog post introducing Platform Specifics – a button click applied a blur to an image on iOS, but didn’t do anything on Droid or Windows. The code to invoke that blur was contained right in the core project – and there was no need to use a custom renderer for the image, or do anything more. It just worked. (Well, in Xamarin’s VisualElement renderer for iOS, they provide the means to do the UIBlurEffect … but we didn’t have to do anything extra).

The code to apply the blur looks like the following:

logo.On<Xamarin.Forms.PlatformConfiguration.iOS>().UseBlurEffect(BlurEffectStyle.ExtraLight);

The code only applies to iOS … and if Android is running it or Windows is running it, it gets executed, but has no effect. Essentially you can think of it as being ignored (it’s not, but you can think of it that way). We don’t have to use any compiler directives or partial class trickery to do this. Neat-o!

How Platform Specifics Work

The way these things work is actually pretty cool. The first thing to know is that every control in Xamarin.Forms now has a new function that looks like the following:

public IPlatformElementConfiguration<T, Button> On<T>() where T : IConfigPlatform

(You can replace Button above with any of the built-in controls).

Notice IConfigPlatform – or the generic in the definition. That’s nothing more than a marker interface. And from it three classes implement it … Xamarin.Forms.PlatformConfiguration.Android , iOS and Windows . So that generic is a way of telling Forms which platform we want the code to run on.

The implementation of the On<T>() function essentially adds an IPlatformConfiguration<T, Button> object to a dictionary with the IConfigPlatform‘s type that’s passed in as the key. Long story short – there’s only going to be 3 values in that dictionary at most (one for iOS, Droid and Windows). And that brings us to what it returns … IPlatformConfiguration<out TPlatform, out TElement> where TPlatform: IConfigPlatform where TElement : Element .

Ok … there’s a lot going on in that definition … especially with all of the out parameters. But here’s the upshot … that On<T>() function will always return something like IPlatformConfiguration<iOS, Button> .

And then with the magic of extension methods, we’ll be able to implement functions that are only meant to be called on specific platforms and ignored for the rest.

Creating a Platform Specific

The key to creating a Platform Specific is to create an extension method(s) for the particular platform and UI-control we want to have the platform specific available for. Along with those extension methods, you’ll also need some bindable properties for the sake of remembering state and reacting to changes.

So let’s build up a Platform Specific that will allow us to take a Xamarin.Forms Button and turn the showsTouchWhenHighlighted property of the underlying UIButton on and off at run time. Because behind the scenes any time you want to do something directly related to an individual operating system you do need to implement it within the platform project (there is no such thing as magic), to turn the touch highlighting on and off I’ll be using an Effect .

(You can find the working example code on GitHub under my FormsPlatformSpecifics repo.)

First things first – we’ll need to keep track of whether this thing is currently on or off – so we’ll need some bindable properties to maintain state.

public static readonly BindableProperty ShowsTouchWhenHighlightedProperty =
    BindableProperty.Create("ShowsTouchWhenHighlighted", typeof(bool), typeof(CodeMillButton), false, 
                            propertyChanging: ShowsTouchWhenHighlightedPropertyChanging);
 
public static void SetShowsTouchWhenHighlighted(BindableObject element, bool value)
{
    element.SetValue(ShowsTouchWhenHighlightedProperty, value);
}
 
public static bool GetShowsTouchWhenHighlighted(BindableObject element)
{
    return (bool)element.GetValue(ShowsTouchWhenHighlightedProperty);
}

There’s two things to note. The first is that we’re taking an action on the BindableProperty’s propertyChanging delegate. That will either add or remove the Effect. (This is an optional step and applies in this case only. If you were writing a Platform Specific, you might do the same thing, or you may have an underlying renderer look for the property to change and take an action on that, or something else entirely. Point is – this is only for this use case.)

The second is that we’re setting the property the “old fashioned way” (like those unfortunate Java developers). This is because our class is going to be static – after all, the whole point of it is to add extension methods – and we don’t have access to the instance of the BindableObject (or the Button in this case) – it needs to be passed in, in order to have access to it.

Now to the good part…

We want to add on functionality to a Button on iOS – that means we’ll be looking to add extension methods to IPlatformElementConfiguration<iOS, Button> . Because we have 2 functions for getting and setting the BindableProperty – we should mirror that with 2 extension methods that invoke them. They’ll look like the following.

public static IPlatformElementConfiguration<iOS, Button> SetShowsTouchWhenHighlighted(this IPlatformElementConfiguration<iOS, Button> config, bool value)
{
    SetShowsTouchWhenHighlighted(config.Element, value);
 
    return config;
}
 
public static bool GetShowsTouchWhenHighlighted(this IPlatformElementConfiguration<iOS, Button> config)
{
    return GetShowsTouchWhenHighlighted(config.Element);
}

In both functions – note how the config variable has an Element property. As mentioned above, that property will hold the Xamarin.Forms Button we’re currently working with.

Another thing to note here is the SetShowsTouchWhenHighlighted returns IPlatformElementConfiguration<iOS, Button>. This enables a fluent API … we can chain together multiple Platform Specific calls when consuming them.

Consuming a Platform Specific

Consuming the Platform Specific is the easy part! Every control in Xamarin.Forms now has that On<T>() function. So assuming the correct namespaces have been added to the class, once the iOS has been passed as the generic – that On<iOS>() function will start to return IPlatformElementConfiguration<iOS, Button> and you’re going to see the extension methods as well!

So consuming in this case is as easy as:

theButton.On().SetShowsTouchWhenHighlighted(true);

What’s The Point?

Again, this is where I stumbled a bit. There are ways to do all of the above without using Platform Specifics. In the example project, I have a Switch that turns the Button highlighting on and off. One could just as easily add and remove an effect on the Switch’s Toggled event and achieve the same result.

The power though I believe comes when tool vendors and Xamarin start to put a ton of platform functionality into their renderers. Then they can wrap that functionality in an “on/off” fashion like I do here (or more complex functions, think something along the lines of SetupDisplayForIPad() that adjusts a bunch of properties and behaviors at once). Then it starts to bridge the gap between having to create renderers and Effects ourselves and let the vendors do it. And of course let’s not forget we’re able to invoke this functionality all from the shared core project.

Wrapping Up

Platform Specifics are a new feature in Xamarin.Forms 2.3.3 that are a bridge to individual platform functionality via extension methods. Every Xamarin.Forms control now has a function, On<T>() which returns an IPlatformConfiguration<T, TElement> object. By creating extension methods on that IPlatformConfiguration<T, TElement> object we’re able to conditionally implement functionality for a specific platform in the shared core project layer – which should be a boon for toolkit vendors to provide more deep platform internals and us developers, who now have a super easy way to consume them!