Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

How to Fix Styles in Xamarin Forms in UWP.NET Native

DZone's Guide to

How to Fix Styles in Xamarin Forms in UWP.NET Native

A side effect of Xamarin Forms styles in UWP development causes errors in the app's appearance can be solved using this method. Read on for details.

· Mobile Zone
Free Resource

Download this comprehensive Mobile Testing Reference Guide to help prioritize which mobile devices and OSs to test against, brought to you in partnership with Sauce Labs.

Xamarin Forms is awesome. If you have learned XAML from WPF, Silverlight, Windows Phone, Universal Windows Apps or UWP, you can jump right in using the XAML you know (or at least something that looks remarkably familiar) and start to make apps that will run cross platform on iOS, Android, and UWP. So potentially your app can run not only on phones but also on XBox, HoloLens, and PCs.

OnPlatform FTW!

One of the coolest things is the OnPlatform construct. For instance, you can have something like this:

<Style TargetType="Label" x:Key="OtherTextStyle" >
    <Setter Property="FontSize">
        <OnPlatform x:Key="FontSize" x:TypeArguments="x:Double" >
            <On Platform="Windows" Value="100"></On>
            <On Platform="Android" Value="30"></On>
            <On Platform="iOS" Value="30"></On>
        </OnPlatform>
    </Setter>
</Style>

This indicates the label that has this style applied to it should have a font size of 100 on Windows, 30 on Android, and 30 on iOS. In the demo project, I have defined some styles in the App.xaml, and the net result is that it looks like this on Android (top), iOS (middle) and Windows (bottom).

imageimage

image

The result is not necessarily very beautiful, but if you look in the MainPage.xaml, you will see that everything has a style and no values are hard coded. You can also see that although the Android and iOS apps are mobile apps and the Windows app is essentially an app running on a tablet or a PC (the demarcation line between these is becoming hazier with each day), it will still work out using OnPlatform.

I have used various constructs. Apart from the inline construct as I showed above, there's also this one:

<OnPlatform x:Key="ImageSize" x:TypeArguments="x:Double" >
    <On Platform="Windows" Value="150"></On>
    <On Platform="Android" Value="100"></On>
    <On Platform="iOS" Value="90"></On>
</OnPlatform>

<Style TargetType="Image" x:Key="ImageStyle" >
    <Setter Property="HeightRequest" Value="{StaticResource ImageSize}" />
    <Setter Property="WidthRequest" Value="{StaticResource ImageSize}" />
    <Setter Property="VerticalOptions" Value="Center" />
    <Setter Property="HorizontalOptions" Value="Center" />
</Style>

It's a construct I would very much recommend, as it enables you to reuse the ImageSize value for other things, for instance, the height of button, in another style. You can also use these doubles directly in XAML, like I did with SomeOtherTextFontSize in the last label in MainPage.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="UWPStyleIssue.MainPage">
    <Grid VerticalOptions="Center" HorizontalOptions="Center" >
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <Image Grid.Row="0"
            Source=
               "https://media.licdn.com/mpr/mpr/shrinknp_400_400/[abbreviated]jpg"
           Style="{StaticResource ImageStyle}"></Image>
        <Label Text="Welcome to                       Xamarin Forms!" Grid.Row="1"             Style="{StaticResource TextStyle}"/>
        <Label Text="Yet another line" Grid.Row="2" Style="{StaticResource OtherTextStyle}"/>
        <Label Text="Last Line" Grid.Row="3" FontSize="{StaticResource SomeOtherTextFontSize}"/>
    </Grid>
</ContentPage>

Although I do not recommend this practice - styles are much cleaner - sometimes needs must and this can be handy.

I can hear you think by now: "Your point please, kind sir?" (or most likely something less friendly). Well... it works great on Android, as you have seen. It also works great on iOS. And yes, on Windows too...

OnPlatform WTF?

... until you think, "Let's get this puppy into the Windows Store." As every Windows Developer knows, if you compile for the Store, you compile for Release, which kicks off the .NET Native toolchain. This is very easy to spot as the compilation process takes much longer. The result is not Intermediary Language (IL), but binary code - an exe - which makes UWP apps so much faster than their predecessors. Unfortunately, it also means the release build is an entirely different beast than a debug build, which can have some unexpected side effects. In our application, if you run the Release build, you will end up with this.

image

That is quite some "side effect." No margin to pull the first text up, no font size (just default), no image... WTF indeed.

Analysis

Unfortunately, I had some issues with another library (FFImageLoading) which took me on the wrong track for quite a while, but after I had fixed that I noticed that when I changed the styles from OnPlatform to hard coded values, the styling started to work again - even in.NET Native. So if I did this:

<x:Double x:Key="ImageSize">150</x:Double>
<!--<OnPlatform x:Key="ImageSize" x:TypeArguments="x:Double"  >
    <On Platform="Windows" Value="150"></On>
    <On Platform="Android" Value="100"></On>
    <On Platform="iOS" Value="90"></On>
</OnPlatform>-->

At least my image showed up again:

image

With a deadline looming and a ginormous style sheet in my app, I really had no time to make a branch with separate styles for Windows. We had to go to the store and we had to go now. Time for a cunning plan. I came up with this:

A Solution/Workaround/Hack/Fix... Sort of

So it works when the styles do contain direct values, not OnPlatform, right...? If you look at App.xaml.cs in the portable project, you will see a line in the constructor that's usually not there, and it's commented out.

public App()
{
    InitializeComponent();
    //this.FixUWPStyling();

    MainPage = new UWPStyleIssue.MainPage();
}

If you remove the slashes and run the app again in Release....

image

Magic happens. All styles seem to work again. This is because of an extension method that's in the file ApplicationExtensions, that you will find in the Portable project in de Extensions folder:

public static void FixUWPStyling(this Application app)
{
    if (Device.RuntimePlatform == Device.Windows)
    {
        app.ConvertAllOnPlatformToExplict();
        app.ConvertAllOnDoubleToPlainDouble();
    }
}

The first method, ConvertAllOnPlatformToExplict, does the following:

  • Loop through all the styles.
  • Loop through all the setters in a style.
  • Check if the setter's property name is either "HeightRequest," "WidthRequest," or "FontSize."
  • If so, extract the Windows value from the OnPlatform struct.
  • Set the setter's value to a plain double with as value the extracted Windows value.

It's crude, it requires about everything to be in OnPlatform, but it does the trick. I am not going to write it all out here, it's not very great code, and you can see it all on GitHub anyway.

Then, for good measure, it calls ConvertAllOnDoubleToPlainDouble, which loops through the all the doubles, like

<OnPlatform x:Key="ImageSize" x:TypeArguments="x:Double" >...</OnPlatform>

It extracts the Windows value, removes the OnPlatform from the resource dictionary, and adds a new plain double with the Windows style only to the resource dictionary. For some reason, replacement is not possible.

Conclusion

There is apparently a bug in the Xamarin Forms.NET Native UWP tooling, which causes OnPlatform values being totally ignored. With my dirty little trick, you can at least get your styles to work without having to rewrite the whole shebang for Windows or have a separate style file for it. Note that this does not fix everything- if you have other value types (like GridHeights), you will need to add your own conversion to ConvertAllOnPlatformToExplict. What I have given you was enough to fix my problems, but not all potential issues that may arise from this bug.

I hope this drives Xamarin for UWP adoption forward, and I also hope this helps the good folks in Redmond fix the bug. I've pretty much identified what goes wrong, now they "only" have to take of the how.

A demo project with the fix can be found here.

Analysts agree that a mix of emulators/simulators and real devices are necessary to optimize your mobile app testing - learn more in this white paper, brought to you in partnership with Sauce Labs.

Topics:
uwp ,xamarin forms ,mobile

Published at DZone with permission of Joost van Schaik, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}