Shadows in Windows (UWP) XAML Applications – Part 4 – Custom Shadows

In part 2 of this series of posts on Shadows in Windows (UWP) XAML Applications (parts 1, 1b, 2 and 3) we saw that the composition APIs could be used to generate a DropShadow. However, what wasn’t immediately clear is that this mechanism only works for a limited set of controls, namely Shape (including Ellipse, … Continue reading “Shadows in Windows (UWP) XAML Applications – Part 4 – Custom Shadows”

In part 2 of this series of posts on Shadows in Windows (UWP) XAML Applications (parts 1, 1b, 2 and 3) we saw that the composition APIs could be used to generate a DropShadow. However, what wasn’t immediately clear is that this mechanism only works for a limited set of controls, namely Shape (including Ellipse, Line, Path, Polygon, Polyline, Rectangle), Image and TextBlock. This is because these controls are the only ones that expose the GetAlphaMask method (and frustratingly this method isn’t even part of a common interface that the controls share). This challenge has previously been pointed out by Mike Taulty in his post about creating shadows, back in 2016. I’m still amazed that there doesn’t seem to be any improvement on this over 4 years later (and no, the ThemeShadow, as I pointed out in my post, isn’t the solution to this problem).

So how do we create a shadow for elements that don’t have the GetAlphaMask? Well the answer presented by Mike was to generate an image from the element, and then use a CompositionBrush generated from the image in order to define the mask for the DropShadow. To do this, we’ll start by adding the CompositionImageBrush to our application (code taken from Mike’s post).

public class CompositionImageBrush : IDisposable
{
    CompositionGraphicsDevice graphicsDevice;
    CompositionDrawingSurface drawingSurface;
    CompositionSurfaceBrush drawingBrush;

    public CompositionBrush Brush => drawingBrush;

    private CompositionImageBrush()
    {
    }

    private void CreateDevice(Compositor compositor)
    {
        graphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(
            compositor, CanvasDevice.GetSharedDevice());
    }

    private void CreateDrawingSurface(Size drawSize)
    {
        drawingSurface = graphicsDevice.CreateDrawingSurface(
            drawSize,
            DirectXPixelFormat.B8G8R8A8UIntNormalized,
            DirectXAlphaMode.Premultiplied);
    }

    private void CreateSurfaceBrush(Compositor compositor)
    {
        drawingBrush = compositor.CreateSurfaceBrush(drawingSurface);
    }

    public static CompositionImageBrush FromBGRASoftwareBitmap(
        Compositor compositor,
        SoftwareBitmap bitmap,
        Size outputSize)
    {
        CompositionImageBrush brush = new CompositionImageBrush();

        brush.CreateDevice(compositor);

        brush.CreateDrawingSurface(outputSize);
        brush.DrawSoftwareBitmap(bitmap, outputSize);
        brush.CreateSurfaceBrush(compositor);

        return (brush);
    }

    private void DrawSoftwareBitmap(SoftwareBitmap softwareBitmap, Size renderSize)
    {
        using (var drawingSession = CanvasComposition.CreateDrawingSession(drawingSurface))
        using (var bitmap = CanvasBitmap.CreateFromSoftwareBitmap(drawingSession.Device, softwareBitmap))
        {
            drawingSession.DrawImage(bitmap,
                new Rect(0, 0, renderSize.Width, renderSize.Height));
        }
    }
        
    public void Dispose()
    {
        drawingBrush.Dispose();
        drawingSurface.Dispose();
        graphicsDevice.Dispose();
    }
}

Next, we’re going to create an extension method for UIElement that will either return the result from GetAlphaMask for those elements where it’s defined, or it will return a brush generated from taking an image shapshot of the element.

public static class UIElementHelpers
{
    public static async Task<CompositionBrush> ShadowAlphaMask(this UIElement uiElement)
    {
        CompositionBrush mask = null;
        if (uiElement is Shape shapeElement)
        {
            mask = shapeElement.GetAlphaMask();
        }
        else if (uiElement is Image imageElement)
        {
            mask = imageElement.GetAlphaMask();
        }
        else if (uiElement is TextBlock textElement)
        {
            mask = textElement.GetAlphaMask();
        }
        else if (uiElement is FrameworkElement frameworkElement)
        {
            var gridVisual = ElementCompositionPreview.GetElementVisual(uiElement);
            var elementVisual = gridVisual.Compositor.CreateSpriteVisual();
            elementVisual.Size = uiElement.RenderSize.ToVector2();
            var bitmap = new RenderTargetBitmap();
            await bitmap.RenderAsync(
                uiElement,
                (int)frameworkElement.ActualWidth,
                (int)frameworkElement.ActualHeight);
            var pixels = await bitmap.GetPixelsAsync();
            using (var softwareBitmap = SoftwareBitmap.CreateCopyFromBuffer(
                pixels,
                BitmapPixelFormat.Bgra8,
                bitmap.PixelWidth,
                bitmap.PixelHeight,
                BitmapAlphaMode.Premultiplied))
            {
                var brush = CompositionImageBrush.FromBGRASoftwareBitmap(
                    gridVisual.Compositor,
                    softwareBitmap,
                    new Size(bitmap.PixelWidth, bitmap.PixelHeight));
                mask = brush.Brush;
            }
        }
        return mask;
    }
}

Lastly we need to modify the code for generating the DropShadow to use this new extension method.

private async void Grid_Loaded(object sender, RoutedEventArgs e)
{
    var shadowColor = (Resources["ApplicationForegroundThemeBrush"] as SolidColorBrush).Color;
    var compositor = ElementCompositionPreview.GetElementVisual(Host).Compositor;

    // Create the drop shadow
    var dropShadow = compositor.CreateDropShadow();
    dropShadow.Color = shadowColor;
    dropShadow.BlurRadius = 16;
    dropShadow.Opacity = 20.0f;

    // Use the shape of the element (in this case ShadowContent) to 
    // control shape of shadow
    var mask = await ShadowContent.ShadowAlphaMask();
    dropShadow.Mask = mask;

    // Set the shadow on the visual
    var spriteVisual = compositor.CreateSpriteVisual();
    spriteVisual.Size = new Vector2((float)Host.ActualWidth, (float)Host.ActualHeight);
    spriteVisual.Shadow = dropShadow;
    ElementCompositionPreview.SetElementChildVisual(Host, spriteVisual);
}

Previously we were generating the shadow for an element called Rectangle2 – we’ve updated the code to use ShadowContent and the XAML now looks like.

<Grid Margin="50"
        Height="200"
        Width="200"
        VerticalAlignment="Bottom"
        HorizontalAlignment="Left">
    <Grid x:Name="Host" />
    <StackPanel x:Name="ShadowContent"
                Background="Pink">
        <Rectangle x:Name="Rectangle2"
                    Fill="Turquoise" />
        <TextBox />
        <Button Content="Press me!" />
    </StackPanel>
</Grid>

Ok, so I guess the only thing let to do is to run the application and show you the output.

And there you have it – a nice, easy to use extension method can can return a brush from any element that can be used to mask the DropShadow.

Shadows in Windows (UWP) XAML Applications – Part 3 – DropShadowPanel

In Part 2 of this series I talked about how to create your own shadow using the DropShadow class. I mentioned that whilst it was easy enough to do, the DropShadow had to be created in code, rather than being applied in XAML. Well the good news is that the team building the Windows Community … Continue reading “Shadows in Windows (UWP) XAML Applications – Part 3 – DropShadowPanel”

In Part 2 of this series I talked about how to create your own shadow using the DropShadow class. I mentioned that whilst it was easy enough to do, the DropShadow had to be created in code, rather than being applied in XAML. Well the good news is that the team building the Windows Community Toolkit say the same issue and have produced the DropShadowPanel which can be used to apply a shadow via XAML.

Let’s again use our two Rectangle series. I’m going to jump straight to the XAML that shows how to use the DropShadowPanel, complete with rounded corners and theme colour support.

<controls:DropShadowPanel BlurRadius="10"
                            ShadowOpacity="1"
                            Color="{ThemeResource ApplicationForegroundThemeColor}"
                            Margin="50"
                            VerticalAlignment="Bottom"
                            HorizontalAlignment="Left"
                            IsMasked="True">
    <Rectangle x:Name="Rectangle2"
                Fill="Turquoise"
                RadiusX="40"
                RadiusY="40"
                Height="200"
                Width="200" />
</controls:DropShadowPanel>

It’s worth noting here that the DropShadowPanel accepts a Color for the shadow, rather than a brush. As such I’ve had to register an additional theme resources.

var foregroundBrush = this.Resources["ApplicationForegroundThemeBrush"] as SolidColorBrush;
Resources["ApplicationForegroundThemeColor"] = foregroundBrush.Color;

And then of course we want to see what this looks like in action.

Note that the DropShadowPanel works in both light (left part of image) and dark (right part of image) themes

One last thing to note before I wrap up this post. You’ll notice in the above XAML that there is an attribute IsMasked that is set to True. This is actually the default value for this property, so could be excluded. However, I wanted to make note of it because it can be toggled to adjust the behaviour of the shadow. For example if I set this attribute to false, we see the following shadow being cast.

Clearly, in this scenario we want to set IsMasked to true so that we get the nice rounded corners in the shadow.

For anyone wanting to quickly apply a shadow in XAML, the DropShadowPanel from the Windows Community Toolkit has you covered.

Shadows in Windows (UWP) XAML Applications – Part 2 – DropShadow

Following Part 1 – ThemeShadow (and Part 1b – Lists) in this post we’re going to look at a very simple example of creating your own shadow. I’m going to reuse my simple example of two overlapping rectangles. The goal is to: Add a shadow around the bottom-left rectangle The shadow should elevate the rectangle … Continue reading “Shadows in Windows (UWP) XAML Applications – Part 2 – DropShadow”

Following Part 1 – ThemeShadow (and Part 1b – Lists) in this post we’re going to look at a very simple example of creating your own shadow. I’m going to reuse my simple example of two overlapping rectangles.

The goal is to:

  • Add a shadow around the bottom-left rectangle
  • The shadow should elevate the rectangle off the background
  • The shadow should elevate the rectangle away from the other rectangle
  • The shadow should handle changing the corner radius to allow for rounded corners
  • The shadow should handle changes to the system theme (i.e. dark mode)

Ok, so let’s see how we can achieve this by creating our own DropShadow. As with the ThemeShadow there are two elements that participate in the creation of the shadow effect. There’s the item casting the shadow (in this case Rectangle2) and then there’s the surface where the shadow needs to be rendered. In this case, since we want the shadow to appear around the edge of Rectangle2, we’re going to create an additional Grid (named Host in the following XAML) that matches the size of Rectangle2:

<Grid Height="400"
        Width="400"
        Loaded="Grid_Loaded"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Rectangle x:Name="Rectangle1"
                Margin="50"
                Height="200"
                Width="200"
                Fill="Turquoise"
                VerticalAlignment="Top"
                HorizontalAlignment="Right" />
    <Grid Margin="50"
            Height="200"
            Width="200"
            VerticalAlignment="Bottom"
            HorizontalAlignment="Left">
        <Grid x:Name="Host" />
        <Rectangle x:Name="Rectangle2"
                    Fill="Turquoise" />
    </Grid>
</Grid>

To create and apply the shadow we’re handling the Loaded event on the parent Grid. The following logic creates the DropShadow, uses Rectangle2 as a mask and then attaches the DropShadow to the SpriteVisual for the Host Grid.

private void Grid_Loaded(object sender, RoutedEventArgs e)
{
    var shadowColor = (Resources["ApplicationForegroundThemeBrush"] as SolidColorBrush).Color;
    var compositor = ElementCompositionPreview.GetElementVisual(Host).Compositor;
            
    // Create the drop shadow
    var dropShadow = compositor.CreateDropShadow();
    dropShadow.Color = shadowColor;
    dropShadow.BlurRadius = 16;
    dropShadow.Opacity = 20.0f;

    // Use the shape of the element (in this case Rectangle2) to 
    // control shape of shadow
    var mask = Rectangle2.GetAlphaMask();
    dropShadow.Mask = mask;
            
    // Set the shadow on the visual
    var spriteVisual = compositor.CreateSpriteVisual();
    spriteVisual.Size = new Vector2((float)Host.ActualWidth, (float)Host.ActualHeight);
    spriteVisual.Shadow = dropShadow;
    ElementCompositionPreview.SetElementChildVisual(Host, spriteVisual);
}

The result of this is shown in the following image

Now let’s try rounding the corners of Rectangle2

<Rectangle x:Name="Rectangle2"
            Fill="Turquoise"
            RadiusX="40"
            RadiusY="40" />

Here’s how it looks

This is looking really nice but what about the dark mode support?

Even in dark mode, we’re still seeing the shadow being cast both against the background but also on Rectangle1. Going back to the XAML and code that creates this effect, it’s important to note that the background is set using the ApplicationPageBackgroundThemeBrush and that the color that’s set for the shadow is based on the ApplicationForegroundThemeBrush. Both these brushes are theme aware, meaning that as the device switches between light and dark mode, the brushes have the appropriate color. This means that there’s little else we need to do in order to support dark mode when creating the shadow.

The upshot of creating a shadow this way is that it’s relatively straight forward but does require custom logic to be written – this is less than ideal if you want to apply shadows easily in xaml for example.

In subsequent posts we’ll look at other options for applying shadows that hopefully will mean less custom code.

Shadows in Windows (UWP) XAML Applications – Part 1b – ThemeShadow in Lists

Originally, I had planned that the next post was going to talk about making use of the Shadow property and creating a custom shadow. However, following part 1 there was a bit of dialog online talking about the use of shadows in lists. I figured that it shouldn’t be too hard to extend my example … Continue reading “Shadows in Windows (UWP) XAML Applications – Part 1b – ThemeShadow in Lists”

Originally, I had planned that the next post was going to talk about making use of the Shadow property and creating a custom shadow. However, following part 1 there was a bit of dialog online talking about the use of shadows in lists. I figured that it shouldn’t be too hard to extend my example to use the ThemeShadow for items in a list.

Let’s start with a simple list of 1000 items which we’ll be displaying using a ListView – I’m sure there’s a better way to generate random list items but I was just hacking around, so didn’t feel like engineering anything more fancy.

public IList<string> StaticItems { get; } = BuildStaticItems();

private static List<string> BuildStaticItems()
{
    var list = new List<string>();
    const int max = 1000;
    for (int i = 0; i < max; i++)
    {
        list.Add($"{max} items - {i}");
    }
    return list;
}

Next up we need the XAML layout for the ListView:

<Grid x:Name="RootGrid">
    <Grid x:Name="ParentBackgroundGrid" />

    <ListView ItemContainerStyle="{StaticResource ThousandItemContainerStyle}"
                ItemsSource="{x:Bind StaticItems}">
        <ListView.ItemTemplate>
            <DataTemplate x:DataType="x:String">
                <TextBlock Text="{x:Bind }"
                            Margin="6" />
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Grid>

There are a couple of things to note here:

  • The ItemsSource is bound to the StaticItems property from the previous code block
  • The ItemTemplate is just a simple TextBlock with a Margin set to indent the text from the edge of the item template.
  • There is a ParentBackgroundGrid priors to the ListView which will render beneath the ListView and will be the surface where the shadows are rendered onto.
  • Lastly this snippet defines an ItemContainerStyle called ThousanItemContainerStyle which we’ll go into more detail in a sec.

Note that in this XAML we haven’t defined either an instance of the ThemeShadow and we haven’t set a background on the items in the list. These will both be defined as part of the ItemContainerStyle, so that we can take advantage of the visual states to adjust the elevation (i.e. the Z axis translate), and thus the shadow cast.

Ok, so here’s the ItemContainerStyle.

<Style x:Key="ThousandItemContainerStyle"
        TargetType="ListViewItem">
    <Setter Property="Padding"
            Value="6" />
    <Setter Property="HorizontalContentAlignment"
            Value="Stretch" />
    <Setter Property="VerticalContentAlignment"
            Value="Stretch" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListViewItem">
                <Grid x:Name="Root">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Pressed">
                                <VisualState.Setters>
                                    <Setter Target="ContentBackground.Elevation"
                                            Value="0" />
                                </VisualState.Setters>
                            </VisualState>
                            <VisualState x:Name="PressedSelected">
                                <VisualState.Setters>
                                    <Setter Target="ContentBackground.Elevation"
                                            Value="0" />
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <local:ItemBackground Elevation="32"
                                            x:Name="ContentBackground"
                                            ContentPadding="{TemplateBinding Padding}" />
                    <ContentPresenter Margin="{TemplateBinding Padding}" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Things to note:

  • I’ve removed a significant portion of the default ItemsContainerStyle you would normally get when you clone the built-in style. What I’m left with is a basic ContentPresenter element and a new UserControl of type ItemBackground.
  • ItemBackground has an initial Elevation of 32 but this property is adjusted when the user presses the button to control the elevation (see the Pressed and PressedSelected visual states), and thus the amount of shadow.

Of course, now we need to look at the ItemsBackground class so we can understand how it’s generating the shadow for each item. The XAML for the ItemBackground UserControl is similar to what we had in my previous post – a root Grid which defines the ThemeShadow resource and includes a Rectangle which is the background of the item. It’s the Rectangle that has the Shadow property set and will be generating the shadow that is to be cast.

<UserControl
    x:Class="ShadowTest.ItemBackground"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
    <Grid Loaded="RootLoaded">
        <Grid.Resources>
            <ThemeShadow x:Name="SharedShadow" />
        </Grid.Resources>
        <Rectangle x:Name="Rectangle2"
                   Fill="Turquoise"
                   Margin="{x:Bind ContentPadding, Mode=OneWay}"
                   Shadow="{StaticResource SharedShadow}" />
    </Grid>
</UserControl>

We’re intercepting the Loaded event on the Grid in order to connect the ThemeShadow with the corresponding Grid to be the receiver for the shadow (in this case the Grid is the ParentBackgroundGrid defined on the page). Note that this logic could definitely be improved. It steps up the visual tree from the current item (i.e. the Grid in the ItemTemplate) all the way up to the Grid defined on the page called RootGrid.

private void RootLoaded(object sender, RoutedEventArgs e)
{
    var parent = VisualTreeHelper.GetParent(this);
    while (parent != null)
    {
        if(parent is Grid grid)
        {
            if(grid.Name == "RootGrid")
            {
                var bg = grid.Children.FirstOrDefault(x => x is Grid g && g.Name == "ParentBackgroundGrid");
                SharedShadow.Receivers.Add(bg);
                return;
            }
        }
        parent = VisualTreeHelper.GetParent(parent);

    }
}

Again, remembering that we can’t specify a ancestor as a Receiver for the shadow. Instead we’re going to find the first child element with the name ParentBackgroundGrid. This Grid will then be set as a Receiver for the ThemeShadow.

As we want to be able to adjust the Z axis translate when the user presses on the item in the list, we need to expose a mechanism whereby the ItemContainerStyle can simply set a property in the visual state definition. We need to define two dependency properties: ContentPadding, which determines the inset of content, and Elevation, which will determine the z translate value.

public Thickness ContentPadding
{
    get { return (Thickness)GetValue(ContentPaddingProperty); }
    set { SetValue(ContentPaddingProperty, value); }
}

public static readonly DependencyProperty ContentPaddingProperty =
    DependencyProperty.Register("ContentPadding", typeof(Thickness), typeof(ItemBackground), new PropertyMetadata(new Thickness(0)));

public int Elevation
{
    get { return (int)GetValue(ElevationProperty); }
    set { SetValue(ElevationProperty, value); }
}

public static readonly DependencyProperty ElevationProperty =
    DependencyProperty.Register("Elevation", typeof(int), typeof(ItemBackground), new PropertyMetadata(0, ElevationChanged));

private static void ElevationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    (d as ItemBackground).ChangeElevation((int)e.NewValue);
}

private void ChangeElevation(int newValue)
{
    Rectangle2.Translation = new Vector3(0, 0, newValue);
}

Let’s give this a run – the following image shows the list of items with the shadow appearing for each item. The only difference between the left and right sides is that in the list on the left side, the item “1000 items – 3” has been pressed.

What’s interesting about applying the shadow to the list of items is that the items almost appear to have rounded corners, in addition to the apparent elevation from the background.

The code in this blog post is very raw and I’ve no doubt there is a way to abstract the various steps to make it easier to define and connect the ThemeShadow with the shadow receiver.

Shadows in Windows (UWP) XAML Applications – Part 1 – ThemeShadow

I’ve been watching James Montemagno bring his AnimalCrossing app to life. The progress continues: #AnimalCrossing Turnip Tracking App powered by #Xamarin and amazing library creators. 99.9% shared code 🙂 pic.twitter.com/7CSlmk6nCJ — James Montemagno – ? Live on Twitch (@JamesMontemagno) April 11, 2020 What’s super impressive is that this doesn’t look anything like a regular out … Continue reading “Shadows in Windows (UWP) XAML Applications – Part 1 – ThemeShadow”

I’ve been watching James Montemagno bring his AnimalCrossing app to life.

What’s super impressive is that this doesn’t look anything like a regular out of the box Xamarin.Forms application. The use of custom fonts, curves, rounded corners and shadows gives this app its own unique look. Unfortunately, it would appear that some of the libraries used aren’t supported on Windows (UWP).

This got me thinking about the use of shadows in UWP, which lead to me sinking time into investigating what is and isn’t support. In this series of posts I’ll walk through some of the options.

Built-in Shadows

Before we get into adding our own shadows to elements in our application, it’s worth noting that UWP already comes with a lot of built-in shadow support. The guidance with regards to using shadows in UWP is that “shadows should be used in a purposeful rather than aesthetic manner”. As such, the built-in shadows appear on elements such as popups and tooltips but don’t appear gratuitously on every list item. For example, the following tooltip has a shadow that makes it appear elevated in relation to the rest of the content.

Dark Mode

I also wanted to see what support there was for dark theme. After switching my Windows theme to dark mode it was evident that there isn’t really any support for shadows. For example, here’s the same tooltip in dark mode.

Whilst there is still some illusion of elevation, this predominately comes from the border that’s been added to the tooltip, rather than an actual shadow. There is definitely no visible shadow around the sides and top of the tooltip, like there is in the light theme.

Now you might be saying “well duh, there is a shadow, you just can’t see it on the black background”. To which, my response is that a shadow, when it comes to applications, is much less about a shadow cast by a light hitting and object, and much more about the illusion of elevation and vertical separation of elements (as described by the Microsoft documentation on Z-depth). As such, even in dark mode you’d expect to see some shadow that illustrates a vertical separation of elements.

WinUI 3

Before we move on, I thought I’d take a quick look at whether there are any difference with regards to shadows in WinUI 3 (grab the WinUI 3 templates to get started). The following image shows the exact same button and tooltip combination, this time using the controls from WinUI 3.

Note that other than a change to the shape and border on the tooltip, there’s really no difference to the shadow cast by the tooltip. The behaviour in dark mode it basically the same too. Although I must admit, do like the new look tooltip with the slightly rounded corners and more gradient border, more than the standard UWP look.

Shadows using ThemeShadow

Whilst support for shadows has been around for quite a while for UWP applications, it was only recent that the ThemeShadow class was added (Windows 10, version 1903 – v10.0.18362.0), making it easier for developers to add shadow support to their applications. To demonstrate this in action, we’ll use a rather contrived example of two overlapping Rectangles, as defined in the following XAML.

<Grid>
    <Rectangle x:Name="Rectangle1"
                Height="200"
                Width="200"
                Fill="Turquoise"
                VerticalAlignment="Top"
                HorizontalAlignment="Right" />
    <Rectangle x:Name="Rectangle2"
                Height="200"
                Width="200"
                Fill="Turquoise"
                VerticalAlignment="Bottom"
                HorizontalAlignment="Left" />
</Grid>

Running this example we can see that both Rectangle shapes appear as one connected shape, flat against the white background. We’ll use a ThemeShadow to elevate the Rectangle2 (bottom left) off the background, and provide some separation from Rectangle1 (top right).

We’ll start by creating a ThemeShadow instance as a Resource with the name SharedShadow. This will be set as the Shadow property on Rectangle2.

<Grid>
    <Grid.Resources>
        <ThemeShadow x:Name="SharedShadow" />
    </Grid.Resources>
    <Grid x:Name="BackgroundGrid" />
    <Rectangle x:Name="Rectangle1"
                Height="200"
                Width="200"
                Fill="Turquoise"
                VerticalAlignment="Top"
                HorizontalAlignment="Right" />
    <Rectangle x:Name="Rectangle2"
                Height="200"
                Width="200"
                Fill="Turquoise"
                VerticalAlignment="Bottom"
                HorizontalAlignment="Left" 
                Shadow="{StaticResource SharedShadow}" />
</Grid>

Note that creating this resource and setting the Shadow property, creates the shadow but it won’t display until you tell the application what elements should show the shadow. This is done by adding elements to the Receivers collection on the ThemeShadow resource.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    SharedShadow.Receivers.Add(BackgroundGrid);
}

It’s important to note here that you can’t add an ancestor of the element casting the shadow to the Receivers collection. This seems rather counter-intuitive since you’d quite often want the shadow to appear on the background behind the element casting the shadow (which would typically be the parent grid or panel). However, it’s also easy to solve – in the above XAML a Grid called BackgroundGrid was added on which the shadow can be cast.

Even after doing all this, if you run this example you still won’t see a shadow. This is because all the elements are all technically at the same elevation (i.e. they have the same Z axis value of 0) so there is no shadow to be cast. To fix this, we simply need to translate Rectangle2 in the Z dimension.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    SharedShadow.Receivers.Add(BackgroundGrid);	
    Rectangle2.Translation += new Vector3(0, 0, 32);
}

Now we’re getting somewhere. The following image shows a shadow around Rectangle2. However, it still shows the two rectangles as being a single shape.

The reason there is no shadow being shown on Rectangle1 is that we haven’t told the application that it should show the shadow there. As before, the fix for this is to add Rectangle1 to the Receivers list of the ThemeShadow resource.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    SharedShadow.Receivers.Add(BackgroundGrid);	
    SharedShadow.Receivers.Add(Rectangle1);
    Rectangle2.Translation += new Vector3(0, 0, 32);
}

And there we have it – a shadow appearing on all sides of Rectangle2.

Of course, we couldn’t just leave it there as I’m sure you’re all wondering what happens in dark mode. Well, there’s good and bad news, as you can see in the following image.

The good news is that the shadow is still being created (see between the rectangles). The bad news is that the shadow hasn’t been inverted with the theme, meaning that you can’t see the shadow against the black background. This is seriously frustrating as it makes the ThemeShadow kind of pointless – it’s main premise was that it was suppose to handle everything for the developer.

Rounded Corners

Updated: I’d forgotten to add this last section on rounded corners in the initial post.

Going back to the designs that James had in the app he’s working on, the items all have rounded corners, giving it a softer, more casual look and feel. To do this in my simple example is relatively easy as I can just set the RadiusX and RadiusY properties.

<Rectangle x:Name="Rectangle2"
            Margin="50"
            Height="200"
            Width="200"
            Fill="Turquoise"
            VerticalAlignment="Bottom"
            HorizontalAlignment="Left"
            RadiusX="40"
            RadiusY="40"
            Shadow="{StaticResource SharedShadow}" />

But what happens to my shadow? Well, the answer is nothing good, as you can see from the following image

In addition to not being able to handle dark mode, it also appears that the ThemeShadow can’t handle non-square corners. According to the documentation, there’s also no support color. Overall the ThemeShadow is severely limited in the scenarios that it would be useful for.

Next up we’ll take a more in depth look at the Shadow property in UWP and how you can implement a shadow yourself.

Dual Screens with Surface Neo (Windows 10X) and Surface Duo (Android)

In October 2019 Microsoft announced a new category of dual screen device with flagship products Surface Neo and Surface Duo. Since then, there has been a massive undertaking by Microsoft to prepare developers of both Windows (UWP and Win32 apps) and Android apps for a new world of dual, or even multiple, screen devices. This … Continue reading “Dual Screens with Surface Neo (Windows 10X) and Surface Duo (Android)”

In October 2019 Microsoft announced a new category of dual screen device with flagship products Surface Neo and Surface Duo. Since then, there has been a massive undertaking by Microsoft to prepare developers of both Windows (UWP and Win32 apps) and Android apps for a new world of dual, or even multiple, screen devices. This included a day focused on building dual screen applications – Microsoft 365 Developer Day: Dual Screens (and here) (this event in itself was weird because it was branded as Microsoft 365 but other than a session on Microsof Graph it had very little to do with Microsoft 365 and everything to do with Windows and Android SDKs for dual screen devices). In this post I wanted to start exploring the dual screen emulators and talk about some points of interest when it comes to interacting with the screens.

Firstly, if you haven’t already, you can grab the emulator images either from the Microsoft Store (for the Windows 10X emulator) or via the Surface Duo SDK. Start at the documentation page for how to Get the Windows 10X development tools and how to Get the Surface Duo SDK.

Now that you’ve got the tools, let’s start to take a look at the emulators. Specifically I want to drill into how the different platforms handle spanning an application across the fold/hinge. The following image shows an image being spanned across the hinge on the Windows 10X emulator.

Notice how there’s a break in the image, making the image feel like it’s broken in two. What’s interesting is that when you take a screenshot using the emulator tools, this break isn’t visible, as shown in the following screenshot.

If we us the Maps application and again span the hinge, there is again a break across the hinge, making the map hard to process visually.

If we switch across to the Android emulator, you can now see that the image spans across the hinge without disturbing the image. The hinge masks or obscures part of the picture, but this is much easier for us to deal with.

And again, using the Android emulator you can see that the map continues beneath the hinge.

Ok, so let’s jump into taking a look at some code. In this case we’re going to look at some UWP code but rather than attempting to treat the two screens as a single page (as shown in soooooo many demos/samples) we’re going to use multiple windows for the application. Into a new UWP application I’ve added a Button and a TextBlock (just to show which window is which). The Button is used to launch new Windows, each showing the MainPage.

private async void Button_Click(object sender, RoutedEventArgs e)
{
    CoreApplicationView newView = CoreApplication.CreateNewView();
    int newViewId = 0;
    await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        Frame frame = new Frame();
        frame.Navigate(typeof(MainPage), null);
        Window.Current.Content = frame;
        // You have to activate the window in order to show it later.
        Window.Current.Activate();

        newViewId = ApplicationView.GetForCurrentView().Id;
    });
    bool viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId);
}

When I first run this, I can see my application taking up a single screen.

Clicking the button the second Window appears on the other screen – this is exactly what you’d hope would happen.

If we now press the button on the second Window, another new Window appears, again on the alternative screen.

Now we can start to arrange the Windows. Here I’ve dragged Window 3 into the lower right corner of the second screen, below Window 2. (ignoring the mouse cursor, which was a result of me attempting to screen capture the drag experience) You can see that there is a visual cue as to what will happen when I release the drag operation.

Window 3 expands to take the lower half of the second screen and prompts me to select what I want to take the top half.

Clicking on Window 2 it expands to fill the top half of the screen.

What’s interesting about this example, which uses Multiple Windows instead of dual panes or a single Window that spans the hinge, is that you get complete control over how the individual windows are positioned. The default behaviour works nicely with Windows appearing in the correct screen, yet you can easily override it by dragging the windows to where you want them.

Along the way (probably because so many apps are modeled on the iOS notion of only doing one thing at a time) we as app developers have forgotten how to take advantage of multiple Windows. Now is the time to think about how best to extend your application to use multiple windows and prepare it for the next wave of multi-screen devices.

Pipeline Templates: Runtime Parameters in Azure DevOps Pipelines

It appears that the Runtime Parameters of Azure DevOps Pipelines has rolled out to most organisations. As such I thought it important that the Pipeline Templates are updated to use strongly typed boolean parameters. For example in the following YAML taken from the Windows Build template has a parameter, build_windows_enabled, which is typed as a … Continue reading “Pipeline Templates: Runtime Parameters in Azure DevOps Pipelines”

It appears that the Runtime Parameters of Azure DevOps Pipelines has rolled out to most organisations. As such I thought it important that the Pipeline Templates are updated to use strongly typed boolean parameters. For example in the following YAML taken from the Windows Build template has a parameter, build_windows_enabled, which is typed as a boolean.

parameters:
# stage_name - (Optional) The name of the stage, so that it can be referenced elsewhere (eg for dependsOn property). 
# Defaults to 'Build_Windows'
- name: stage_name
  type: string
  default: 'Build_Windows'
# build_windows_enabled - (Optional) Whether this stages should be executed. Note that setting this to false won't completely
# cancel the stage, it will merely skip most of the stages. The stage will appear to complete successfully, so
# any stages that depend on this stage will attempt to execute
- name: build_windows_enabled
  type: boolean
  default: true

Previously, the parameters were passed in using variables defined in the main pipeline. The issue with this is that all variables are strings, so there’s no checking that a valid value has been passed in. For example:

Now we have the option of defining runtime parameters. These are parameters that are defined in the main pipeline yml file (ie not in a template). In the following example there are three runtime parameters defined; all boolean; all with a default value of true.

When we manually run this pipeline we’re now presented with a user interface that allows the user to adjust these runtime parameters, before hitting Run.

Another useful feature in the Run dialog is the Resources section

The Resources section lists referenced templates.

Clicking through on a template allows the user to adjust what version of the template to use. This is particularly useful if you are testing a new version of a template because you can Run a build referencing a different version, without making (and committing) a change to the actual pipeline.

Pipeline Templates v0.6.0

Here’s a summary of what’s in release v0.6.0:

Breaking Changes:

  • build-xamarin-[iOS/android/windows].yml and deploy-appcenter.yml – XXX_enabled parameter (eg windows_enabled or deploy_enabled) are now strongly typed as boolean parameters. This means you need to supply either a literal value (eg true or false) or a parameter. Use runtime parameters instead of pipeline variables to ensure values are strongly typed as boolean.

Other Changes:

  • none

Look iOS Developer, No Mac Required – Build an iOS Application using Xamarin and Visual Studio for Windows without using a Mac

If you’re a die-hard Windows user, like me, you’ll be excited to know that you can now build iOS application using Xamarin (and Xamarin.Forms) and Visual Studio, without having to buy or use a Mac. That’s right for development, you no longer need to invest, or carry around, a Mac. In this post I’ll walk you through how to enable this feature.

If you’re a die-hard Windows user, like me, you’ll be excited to know that you can now build iOS application using Xamarin (and Xamarin.Forms) and Visual Studio, without having to buy or use a Mac. That’s right for development, you no longer need to invest, or carry around, a Mac. In this post I’ll walk you through how to enable this feature.

I’m working in the latest preview of Visual Studio 2019 and as of now, to use Xamarin Hot Restart (the feature that powers the no Mac development experience), you need to check the Enable Xamarin Hot Restart option under Preview Features in the Tools, Options dialog.

After enabling Xamarin Hot Restart, make sure you restart Visual Studio. Next, set your iOS project to be the startup project by right-clicking the iOS project in Solution Explorer, followed by the Set as Startup Project menu item.

Make sure in the toolbar the option next to the play button says Local Device. Click the play button, which will kick off the Setup Hot Restart experience.

There are a couple of steps to jump through the first time you want to use Hot Restart. Make sure you follow the instructions to avoid having to redo steps.

The first step in the process it to Download iTunes – make sure you click the Download iTunes button. Do NOT install iTunes from the Microsoft Store. If you’ve done this previously, make sure you uninstall it, and then install it by clicking the Download iTunes button.

Clicking the Download iTunes button will open your default browser but it will attempt to immediately download the file – make sure you check out the downloads so you can launch the file once it’s downloaded.

Step through the iTunes installer.

Once iTunes is installed the Setup Hot Restart process will detect the presence of iTunes (note that it will not detect iTunes if you’re installed it from the Microsoft Store).

Next, make sure you have an iOS device plugged in and that you’ve clicked the Trust option on the device when prompted to trust the connected computer. The Setup Hot Restart process should detect the attached device.

Next, sign into your Apple Developer account.

And select the Development Team you want to use for provisioning.

After completing the Setup Hot Restart process you should see that the build process will continue and that in the Output window you’ll see the iPA being created and subsequently pushed to the device.

When prompted, you’ll need to launch the installed application on the iOS device – this manual step is required in order for Visual Studio to attach the debugger.

And there you have it – you now have an iOS application being debugged using Visual Studio on an actual device with no Mac required!!!

Pipeline Template: Applying a Launch Icon Badge to Identify Environments and Versions of your App

A question was raised this week by Sturla as to how to incorporate the Launch Icon Badge extension into the build process when making use of the templates from Pipeline Templates (Damien covers how to use this extension in a Azure DevOps pipeline in his post on the topic). By the way, a big thank … Continue reading “Pipeline Template: Applying a Launch Icon Badge to Identify Environments and Versions of your App”

A question was raised this week by Sturla as to how to incorporate the Launch Icon Badge extension into the build process when making use of the templates from Pipeline Templates (Damien covers how to use this extension in a Azure DevOps pipeline in his post on the topic). By the way, a big thank you to Sturla for testing each release of the templates and providing invaluable feedback along the way.

The purpose of the Launch Icon Badge extension is to make it really easy to add a banner to your application icon (or any other image) to indicate that your application is prerelease. As you’ll see from the yaml in the example below there are a lot of attributes you can control, meaning that you can use this for whatever you want to signify. For example you may want to signify what environment the app is targeting by changing the background colour on the banner.

To get started with the Launch Icon Badge extension you need to make sure you install the extension to your Azure DevOps Organisation. This is important, otherwise any attempt to use the LaunchIconBadge task will fail.

If you’re using one of the build templates from Pipeline Templates you simply need to extend it by adding the LaunchIconBadge task to the preBuild task list. This is shown at the end of the following YAML snippet.

stages:
- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_Android'
    build_android_enabled: ${{variables.android_enabled}}
    # Version information
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    # Signing information
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'
    # Solution to build
    solution_filename: $(solution_file)
    solution_build_configuration: $(solution_build_config)
    # Output information
    artifact_folder: $(artifact_android_folder)
    application_package: $(android_application_package)
    preBuild:
      - task: [email protected]1
        inputs:
          sourceFolder: '$(Build.SourcesDirectory)/src/Apps/DotNet/Uno/InspectorUno/InspectorUno/InspectorUno.Droid' # Optional. Default is: $(Build.SourcesDirectory)
          contents: '**/*.png' # Optional. Default is:  '**/*.png'
          bannerVersionNamePosition: 'bottomRight' # Options: topLeft, topRight, bottomRight, bottomLeft. Default is: 'bottomRight'
          bannerVersionNameText: 'Prerelease'  # Optional. Default is: ''
          bannerVersionNameColor: '#C5000D' # Optional. Default is: '#C5000D'
          bannerVersionNameTextColor: '#FFFFFF' # Optional. Default is: '#FFFFFF'
          bannerVersionNumberPosition: 'top' # Optional. top, bottom, none. Default is: 'none'
          bannerVersionNumberText: '$(version_prefix).$(Build.BuildId)' # Optional. Default is: ''
          bannerVersionNumberColor: '#34424F' # Optional. Default is: '#34424F'
          bannerVersionNumberTextColor: '#FFFFFF' # Optional. Default is: '#FFFFFF' 

Important Note: In v0.5.2 of the Pipeline Templates you can no longer pass in a variable to the build_android_enabled parameter using the $(variablename) syntax. As shown here you need to use the ${{variables.variablename}} syntax to ensure it’s resolved as part of inflating the templates.

When you’re attempting to get the LaunchIconBadge task to work for you, make sure that your sourceFolder and contents are configured to locate the app icons for your application. This took me a couple of iterations as I got the path wrong the first couple of times and it resulted in no images being found – check the build log for full information on what the task is doing.

Once you have every configured, your application should be built with the appropriate banner across the application icon. I configured this for the Android, iOS and Windows build for my application and this is what I now see in App Center.

Pipeline Templates v0.5.2

Here’s a summary of what’s in release v0.5.2:

Breaking Changes:

  • build-xamarin-[iOS/android/windows].yml and deploy-appcenter.yml – XXX_enabled parameter (eg windows_enabled or deploy_enabled) no longer supports passing a variable in using $(variablename) syntax. Make sure you use ${{variables.variablename}} syntax. This also means that only variables defined in the YAML file are supported – NOT variables defined in variable groups or in the UI for the pipeline. Going forward it’s recommended to use runtime parameters for getting user input when invoking a pipeline.

Other Changes:

  • none

Pipeline Templates: How to use a file for release notes?

When deploying a release to AppCenter you can specify release notes that get presented to the user when they go to download a new release. This week a question was asked as to how to specify release notes from a file when submitting a new app version to AppCenter. If you looked at the complete … Continue reading “Pipeline Templates: How to use a file for release notes?”

When deploying a release to AppCenter you can specify release notes that get presented to the user when they go to download a new release. This week a question was asked as to how to specify release notes from a file when submitting a new app version to AppCenter.

If you looked at the complete example I provided for building and deploying a Uno app to Android, iOS and Windows, you might be wondering how you can provide release notes at all since we didn’t specify any release notes when we used the AppCenter deploy template. However, if you take a look at the parameters list you can see that there is an appcenter_release_notes parameter which has a default value set. Use this parameter if you want to specify the release notes inline when invoking the template.

In the v0.5.1 release we added two new parameters that allow you to specify a file for release notes to be taken from: appcenter_release_notes_option and appcenter_release_notes_file. To specify a file for release notes, you first need to set the appcenter_release_notes_option parameter to file. Then you need to use the appcenter_release_notes_file parameter to specify the file that you want to use.

In theory this sounds easy: you add a release notes file to your repository and then simply provide a relative path to the file in the appcenter_release_notes_file parameter. This you will find does not work!!! The AppCenter deploy template does not know anything about your source code repository, since it’s designed to deploy the artifacts from a prior build stage.

Ok, so then the question is, how do we make the release notes file available to the AppCenter template? Well we pretty much answered that in the previous paragraph – you need to deploy it as an artifact from the build process.

Assuming you’re using one of the build templates from https://pipelinetemplates.com, you can easily do this by adding additional steps as part of the prePublish extension point. Here’s an example:

- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_iOS' 
    build_ios_enabled: $(ios_enabled)
    # Version information
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    # Solution to build
    solution_filename: $(solution_file)
    solution_build_configuration: $(solution_build_config)
    # Signing information
    ios_plist_filename: 'src/Apps/DotNet/Uno/InspectorUno/InspectorUno/InspectorUno.iOS/Info.plist'
    ios_cert_password: '$(ios_signing_certificate_password)'
    ios_cert_securefiles_filename: '$(ios_signing_certificate_securefiles_filename)'
    ios_provisioning_profile_securefiles_filename: '$(ios_provisioning_profile_securefiles_filename)'
    # Output information
    artifact_folder: $(artifact_ios_folder)
    application_package: $(ios_application_package)
    prePublish:
      - task: [email protected]2
        displayName: 'Copying release notes'
        inputs:
          contents: 'src/Apps/DotNet/releasenotes.txt'
          targetFolder: '$(build.artifactStagingDirectory)/$(artifact_ios_folder)'
          flattenFolders: true
          overWrite: true

- template:  azure/mobile/dep[email protected]_templates
  parameters:
    # Stage name and dependencies
    stage_name: 'Deploy_iOS'
    depends_on: 'Build_iOS'
    deploy_appcenter_enabled: $(ios_enabled)
    environment_name: $(appcenter_environment)
    # Build artifacts
    artifact_folder: $(artifact_ios_folder)
    application_package: $(ios_application_package)
    # Deployment to AppCenter
    appcenter_service_connection: $(appcenter_service_connection)
    appcenter_organisation: $(appcenter_organisation)
    appcenter_applicationid: $(appcenter_ios_appid)
    appcenter_distribution_group_ids: '5174f212-ea8c-4df1-b159-391200d7af5f'
    appcenter_release_notes_option: file
    appcenter_release_notes_file: 'releasenotes.txt'

Note that the CopyFiles task copies the release notes into the artifact_folder sub-folder of the staging directory. This is important because the AppCenter deploy template will look in the sub-folder for the release notes file specified using the appcenter_release_notes_file parameter.

Xamarin.Forms – Which tools do you use?

If you’re developing cross-platform applications using Xamarin.Forms then you’re likely to be using either Visual Studio or Visual Studio for Mac (as an aside, if you’re new to Xamarin.Forms you can get the tools you need for free, so don’t feel you need to go out and purchase the professional version of Visual Studio!). The … Continue reading “Xamarin.Forms – Which tools do you use?”

If you’re developing cross-platform applications using Xamarin.Forms then you’re likely to be using either Visual Studio or Visual Studio for Mac (as an aside, if you’re new to Xamarin.Forms you can get the tools you need for free, so don’t feel you need to go out and purchase the professional version of Visual Studio!). The question is, what other tools should you be using that can assist you and hopefully improve the quality of your code and the architecture of you application. In this post I wanted to just point out a couple of tools that are worth taking a look at.

Resharper

I think if you’re a .NET/C# developer you’d be remiss if you hadn’t used Resharper at one time or another. For me this is one of the first extensions I add when I’m setting up a new machine. However, it’s also one of the first I disable if Visual Studio is playing up – from time to time different versions of Visual Studio and Resharper just don’t play nicely together.

Also worthy of a note, and also from Jetbrains who make Resharper, is a full IDE called Rider that some developers have taken a liking to.

MFractor

Announced literally days ago – MFractor for Windows is now available. For Visual Studio for Mac users, MFractor has been an essential for Xamarin.Forms developers.

It’s early days for MFractor but the team have been hard at work and I think this will be a tool that every developer should take the time to download and give it a shot.

XamRight

Another tool that can help with coding and refactoring XAML in your Xamarin.Forms is XamRight.

XamlStyler

For keeping your XAML coding in check, XAMLStyler is a simple solution for enforcing consistent layout for your XAML.

It’s great to see all these tools appearing to support Xamarin.Forms developers. Feel free to leave a comment for tools that you use to improve your productivity.

Pipeline Templates: Complete Azure Pipelines Example for a Uno Project for iOS, Android and Windows

My last post was a bit of a long one as it covered a bunch of steps for setting up the bits and pieces required for signing an application for different platforms. In this post I just wanted to provide a complete example that shows a single multi-stage (6 in total) Azure Pipelines pipeline for … Continue reading “Pipeline Templates: Complete Azure Pipelines Example for a Uno Project for iOS, Android and Windows”

My last post was a bit of a long one as it covered a bunch of steps for setting up the bits and pieces required for signing an application for different platforms. In this post I just wanted to provide a complete example that shows a single multi-stage (6 in total) Azure Pipelines pipeline for building a Uno application for iOS, Android and Windows (UWP) and releasing them to App Center.

Secure Files

In my last post I showed how to create and populate Secure Files in Azure Pipelines. Any certificate or provisioning profile you need to use in your pipeline should be added to the Secure Files section of the Library in Azure Pipeline. My list of Secure Files looks like this:

Here we can see that we have the signing certificates for iOS and Windows, and the keystore for Android. Then we have two iOS provisioning profiles, one for my XF application and the other for my Uno application.

Variable Groups

I’ve extracted most of the variables I use in my pipeline into one of two variable groups:

The Common Build Variables are those variables that can be reused across multiple projects.

The Inspector Uno Build Variables are those variables that are specific to this project. For example it includes the AppCenter ids for the iOS, Android and Windows applications. It also includes the iOS provisioning profile which is specifically tied to this application.

Pipeline

Here’s the entire pipeline:

resources:
  repositories:
    - repository: builttoroam_templates
      type: github
      name: builttoroam/pipeline_templates
      ref: refs/tags/v0.5.0
      endpoint: github_connection
  
variables:
  - group: 'Common Build Variables'
  - group: 'Inspector Uno Build Variables'
  - name: ios_enabled
    value: 'true'
  - name: windows_enabled
    value: 'true'
  - name: android_enabled
    value: 'true'
 
stages:
- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_Android'
    build_android_enabled: $(android_enabled)
    # Version information
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    # Signing information
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'
    # Solution to build
    solution_filename: $(solution_file)
    solution_build_configuration: $(solution_build_config)
    # Output information
    artifact_folder: $(artifact_android_folder)
    application_package: $(android_application_package)

- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and dependencies
    stage_name: 'Deploy_Android'
    depends_on: 'Build_Android'
    deploy_appcenter_enabled: $(android_enabled)
    environment_name: $(appcenter_environment)
    # Build artifacts
    artifact_folder: $(artifact_android_folder)
    application_package: $(android_application_package)
    # Signing information (for Android repack to APK)
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'
    # Deployment to AppCenter
    appcenter_service_connection: $(appcenter_service_connection)
    appcenter_organisation: $(appcenter_organisation)
    appcenter_applicationid: $(appcenter_android_appid)


- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_Windows'
    build_windows_enabled: $(windows_enabled)
    # Version information
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    # Signing information
    windows_cert_securefiles_filename: '$(windows_signing_certificate_securefiles_filename)'
    windows_cert_password: '$(windows_signing_certificate_password)'
    # Solution to build
    solution_filename: $(solution_file)
    solution_build_configuration: $(solution_build_config)
    # Output information
    artifact_folder: $(artifact_windows_folder)
    application_package: $(windows_application_package)

- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and dependencies
    stage_name: 'Deploy_Windows'
    depends_on: 'Build_Windows'
    deploy_appcenter_enabled: $(windows_enabled)
    environment_name: $(appcenter_environment)
    # Build artifacts
    artifact_folder: $(artifact_windows_folder)
    application_package: $(windows_application_package)
    # Deployment to AppCenter
    appcenter_service_connection: $(appcenter_service_connection)
    appcenter_organisation: $(appcenter_organisation)
    appcenter_applicationid: $(appcenter_windows_appid)

- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_iOS' 
    build_ios_enabled: $(ios_enabled)
    # Version information
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    # Solution to build
    solution_filename: $(solution_file)
    solution_build_configuration: $(solution_build_config)
    # Signing information
    ios_plist_filename: 'src/Apps/DotNet/Uno/InspectorUno/InspectorUno/InspectorUno.iOS/Info.plist'
    ios_cert_password: '$(ios_signing_certificate_password)'
    ios_cert_securefiles_filename: '$(ios_signing_certificate_securefiles_filename)'
    ios_provisioning_profile_securefiles_filename: '$(ios_provisioning_profile_securefiles_filename)'
    # Output information
    artifact_folder: $(artifact_ios_folder)
    application_package: $(ios_application_package)

- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and dependencies
    stage_name: 'Deploy_iOS'
    depends_on: 'Build_iOS'
    deploy_appcenter_enabled: $(ios_enabled)
    environment_name: $(appcenter_environment)
    # Build artifacts
    artifact_folder: $(artifact_ios_folder)
    application_package: $(ios_application_package)
    # Deployment to AppCenter
    appcenter_service_connection: $(appcenter_service_connection)
    appcenter_organisation: $(appcenter_organisation)
    appcenter_applicationid: $(appcenter_ios_appid)


Pipeline Templates v0.5.0

Most of the v0.5.0 release has been tidying things up, reducing the number of required parameters by making the template smarter and increasing consistency across the templates

Breaking Changes:

  • build-xamarin-android.yml – changed build_platform to solution_target_platform parameter
  • build-xamarin-windows.yml – changed build_platform to solution_target_platform parameter
  • build-xamarin-windows.yml – changed windows_appxupload_name parameter to windows_upload_name to reflect support for msix

Other Changes:

  • build-xamarin-[iOS/android/windows].yml – added depends_on parameter so that the stages can be ordered
  • build-xamarin-[iOS/android/windows].yml – artifact_name, artifact_folder and application_package are no longer required and have default values
  • build-xamarin-android.yml – supports building either aab or apk based on the application_package parameter
  • build-xamarin-windows.yml – windows_upload_name parameter isn’t required as it has a default value based on the bundle name
  • deploy-appcenter.yml – added conditions to all steps so that pipeline doesn’t break if build stage doesn’t generate any output
  • All template – documentation added to parameters and steps

Pipeline Templates: Building and Deploying Uno Apps for iOS, Android and Windows

In my previous posts covering the Pipeline Templates I’ve discussed building a Xamarin.Forms apps for iOS, Android and Windows (UWP) and subsequently deploying them to AppCenter. In this post we’re going to look at doing the same with a Uno application. Given that Uno is built on top of the core Xamarin functionality, the process … Continue reading “Pipeline Templates: Building and Deploying Uno Apps for iOS, Android and Windows”

In my previous posts covering the Pipeline Templates I’ve discussed building a Xamarin.Forms apps for iOS, Android and Windows (UWP) and subsequently deploying them to AppCenter. In this post we’re going to look at doing the same with a Uno application. Given that Uno is built on top of the core Xamarin functionality, the process for both build and deploy should be fairly similar. Rather than just stepping through using the template, I’m going to cover creating a new Uno project and setting up the corresponding multi-stage pipeline to build and deploy to AppCenter.

Also, I’ve pushed v0.3.0 of the pipeline templates, which I’ll summarise at the end of this post.

Creating a Uno Application

Let’s get into it and start by creating a Uno application. Before proceeding, make sure you grab the latest Uno extension for Visual Studio that includes the project templates. From the Create a new project dialog, enter “uno” into the search box and then select the Cross-Platform App (Uno Platform) template.

Next, enter a name and location for your new application

When you hit the Create button, Visual Studio will generate a new solution with five projects, representing the head projects for iOS, Android, Windows (UWP) and Web (WASM), along with a shared project.

After creating the application, you should go through each platform and check that it builds and runs. The WASM project will take a while to build and run the first time as it needs to download some components first – don’t be alarmed it nothing appears to happen for a long time.

Build Configuration

One thing I do like to tidy up for my applications is the build configurations. This is often overlooked by developers and then wonder why they have to wait around for say the Android project to build, when they have the UWP project selected as their start up project. Here are the Release configurations I have set for the different platforms

Release Configuration with Any CPU
Release Configuration with iPhone
Release Configuration with x64 (x86 and ARM platforms are the same except the Platform for InspectorUno.UWP is x86 and ARM respectively)

Building Uno Applications

We’ll go through each Uno application separately and I’ll endeavor to highlight typical pain points that developers face. If you’re using the pipeline_templates and you run into issues, message me on Twitter and I’ll help you out. Where there are common frustrations, I’ll work to improve the templates to make them easier to use.

Let’s start by creating a new pipeline in Azure DevOps. I’m not going to step through the pipeline wizard but for what we want to do, just select the appropriate source code respository and an empty YAML file. When you get to the YAML editor, enter the following.

trigger: none
resources:
  repositories:
    - repository: builttoroam_templates
      type: github
      name: builttoroam/pipeline_templates
      ref: refs/tags/v0.3.0
      endpoint: github_connection
  
variables:
  - group: 'Uno Build Variables'

There are three parts to this inital YAML. Firstly, we’re disabling the CI trigger whilst we’re configuring the build. As we’ll be committing changes to both the build pipeline and source code, it’s as well to only have builds triggered when you’re ready, otherwise you’ll be continually cancelling builds.

Next, the resources section is where we pull in the pipeline templates. In this case we’re targeting the v0.3.0 release to ensure the templates don’t change and break our builds in the future.

Lastly, we’re pulling in the Uno Build Variables variable group. You can pick whatever name you want for this group but it needs to match the name of the variable group defined under the Library tab on Azure DevOps.

Android – Build

Starting with Android we’re going to add stages to the YAML file (note you only need to include the stages element once and then add the individual stages by referencing the corresponding template). Here’s the Android build

stages:
- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Build_Android'
    build_android: $(android_enabled)
    solution_filename: 'src/Apps/DotNet/Inspector.Uno.sln'
    solution_build_configuration: 'Release'
    build_number: '$(Build.BuildId)'
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    artifact_name: 'inspector-build'
    artifact_folder: 'Android_output'
    application_package: 'Inspector-Uno-Android.aab'
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'

There are a few things you need to have setup for this build to work:

  • solution_filename – make sure the path to your solution file is correct, relevant to the root of your code repository.
  • $(version_prefix) – make sure the version_prefix variable exists in your variable group and has the format X.Y (eg 1.0)
  • $(android_keystore_filename) – make sure the android_keystore_filename variable exists in your variable group and that it’s value matches the Secure file name of the keystore file uploaded to the Secure files under the Library in Azure DevOps.
  • $(android_keystore_alias) – make sure the android_keystore_alias variable exists in your variable group and that it is the the alias that you specified when creating the keystore. This variable should be marked as private by clicking the lock beside it in the portal – this hides it both in the variable group editor in the portal and in the log files.
  • $(android_keystore_password) – make sure the android_keystore_password variable exists in your variable group and that it is the password you specified when creating the keystore. Again this should be marked as private.

Frustratingly, the Android build didn’t just work for a couple of reasons:

  • Firstly, there was an issue where one of the images has a file name that isn’t supported by the version of Visual Studio and/or Android tooling on the windows-latest build agent, resulting in the following error:
    2020-02-08T06:04:55.7036355Z ##[error]src\Apps\DotNet\Uno\InspectorUno\InspectorUno\InspectorUno.Shared\Assets\Square44x44Logo.targetsize-24_altform-unplated.png(0,0): Error APT0003: Invalid file name: It must contain only [^a-zA-Z0-9_.]+.
    I simply excluded the file Square44x44Logo.targetsize-24_altform-unplated.png from the shared project
  • Secondly, the build agent include the Android NDK but doesn’t specify the appropriate environment variables (eg ANDROID_NDK_HOME), resulting in the following error:
    2020-02-07T12:56:19.5428557Z ##[error]C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Xamarin\Android\Xamarin.Android.Common.targets(2843,3): Error XA5101: Could not locate the Android NDK. Please make sure the Android NDK is installed in the Android SDK Manager, or if using a custom NDK path, please ensure the $(AndroidNdkDirectory) MSBuild property is set to the custom path.
    The solution here was for me to update the build-xamarin-android.yml to include these environment variables. You shouldn’t run into this error if you’re using v0.3.0 of the pipeline templates.

Android – Deploy

Here’s the YAML for deploying the Android application to AppCenter

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_Android'
    depends_on: 'Build_Android'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'Android_output'
    application_package: 'Inspector-Uno-Android.aab'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-Uno-Alpha'
    appcenter_release_notes: 'Release from deploy pipeline'
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'

Again there are a few things you need to check here but most importantly for this to work you need to setup an application in AppCenter. I created a new application

After creating the application, look at the url that appears in the browser. The piece highlighted in green is the value you need to set for the appcenter_organisation and the yellow is for the appcenter_applicationid.

You also need to make sure you setup a Service Connection between Azure DevOps and AppCenter (in this case it was given the name ‘AppCenterInspectorCI’ and assigned to the appcenter_service_connection property)

The other properties you need to check are:

  • depends_on – make sure this matches the stage_name property set on the android build stage
  • artifact_name, artifact_folder and application_package – make sure these match the corresponding value on the android build stage
  • secure_file_keystore_filename, keystore_alias and keystore_password – these should be set to the same as on the android build stage and are required so that the release process can extract and sign a fat apk from the aab file.

iOS – Build

Before we get into building your iOS application, let’s make sure you have both a signing certificate and a provisioning profile ready to go. If you have both of these, you can jump over the next two sections. These instructions can be stepped through on Windows, without the need for a Mac!

certificate

  • Install OpenSSL (Download from https://slproweb.com/products/Win32OpenSSL.html)
  • Open command prompt
  • Add OpenSSL to path (alternatively you can permanently add it to your Path environment variable if you want to have it accessible across all command prompts)
    set PATH=%PATH%;C:\Program Files\OpenSSL-Win64\bin\
  • Generate the signing request
    openssl genrsa -out ios_distribution.key 2048
    openssl req -new -key ios_distribution.key -out CertificateSigningRequest.certSigningRequest
  • Sign into the Apple Developer portal and go to Certificates (https://developer.apple.com/account/resources/certificates)
  • Click the + button to start the process of adding a new certificate
  • Select Apple Distribution from the list of certificate types and then Continue
  • Use the Choose File link to select the CertificateSigningRequest.certSigningRequest file you created in the previous steps, and then Continue
  • Important – Make sure you click the Download button to download your file. It’ll download as a file called distribution.cer unless you choose to rename it.
  • Move the distribution.cer file to the same folder that you’ve been working in
  • Complete the certificate creation from the command prompt (making sure you change PASSWORD to be a password of your choice)
    openssl x509 -in distribution.cer -inform DER -out ios_distribution.pem -outform PEM
    openssl pkcs12 -export -inkey ios_distribution.key -in ios_distribution.pem -out ios_distribution.p12 -passout pass:PASSWORD

With these steps completed you have a certificate file, ios_distribution.p12, which you can upload as a Secure File to Azure DevOps.

You’ll also need the signing identity, which we can get directly from the ios_distribution.pem that was generated along the way.
openssl x509 -text -noout -in ios_distribution.pem

Application Identifier

Before you can create a provisioning profile you need to setup an identity for your application in the Apple Developer portal. Go to the Identities tab and click on the + button to register a new application.

Select App IDs and then Continue

Enter both a Description and a Bundle ID. You may also need to select an App ID Prefix if you belong to a team. Next click Continue, followed by the Register button to complete the process of registering your application’s identity.

Note: Currently you will need to update the Info.plist file of your iOS application to set the CFBundleIdentifier to match the Bundle ID specified when creating the App ID.

Provisioning Profile

To create the provisioning profile, click on the Profiles tab in the Apple Developer portal. Click the + button to start the process of creating a new provisioning profile.

Select the Ad Hoc option under Distribution and then Continue.

Select the App ID that you previously created, then Continue

Select the certificate that needs to be used to sign the application, then Continue

Select the devices that will be able to install the application, then Continue

Give the provisioning profile a name and then click Generate.

Download the provisioning profile and add it as a Secure File in the Library in Azure DevOps. You’ll also need the provisioning profile id, which you can extract from the provisioning profile using any text editor. Open the provisioning profile and look for the UUID key; the string value immediately after it is the profile id.

Ok, we’re now ready for the build stage

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Build_iOS' 
    build_ios: true
    solution_filename: 'src/Apps/DotNet/Inspector.Uno.sln'
    solution_build_configuration: 'Release'
    ios_cert_password: '$(ios_signing_certificate_password)'
    ios_cert_securefiles_filename: '$(ios_signing_certificate_securefiles_filename)'
    ios_provisioning_profile_securefiles_filename: '$(ios_provisioning_profile_securefiles_filename)'
    build_number: '$(Build.BuildId)'
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    ios_signing_identity: '$(ios_signing_identity)'
    ios_provisioning_profile_id: '$(ios_provisioning_profile_id)'
    artifact_name: 'inspector-build'
    artifact_folder: 'iOS_output'
    application_package: 'Inspector-Uno-iOS.ipa'

iOS – Deploy

The deploy stage is relatively simple

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_iOS'
    depends_on: 'Build_iOS'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'iOS_output'
    application_package: 'Inspector-Uno-iOS.ipa'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-Uno-iOS-Alpha'
    appcenter_release_notes: 'Release from deploy pipeline'

Windows – Build

The build stage for Windows (UWP) is

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Build_Windows'
    build_windows: true
    solution_filename: 'src/Apps/DotNet/Inspector.Uno.sln'
    solution_build_configuration: 'Release'
    uwpBuildPlatform: 'x86'
    build_number: '$(Build.BuildId)'
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    windows_cert_securefiles_filename: '$(windows_signing_certificate_securefiles_filename)'
    windows_cert_password: '$(windows_signing_certificate_password)'
    artifact_name: 'inspector-build'
    artifact_folder: 'Windows_output'
    application_package: 'Inspector-Uno-Windows.msixbundle'
    windows_appxupload_name: 'Inspector-Uno-Windows.msixupload'

In order to build your Windows application you’re going to need a signing certificate. Microsoft has good documentation for creating the certificate which I would encourage you to follow. The important thing is to upload the certificate to the Secure files section under the Library tab on Azure DevOps and the corresponding password as a private variable. Damien Aicheh has a good tutorial on uploading certificates to Azure DevOps – don’t worry about the section on using the certificates as the Windows build template already does this for you.

Note: You also don’t need to worry about updating your package.appxmanifest so that the Publisher matches the Subject on the signing certificate. The Windows build template takes care of this too.

There are a couple of other parameters you’ll want to check off

  • uwpBuildPlatform – in this YAML this is only set to x86 for efficiency. If you’re planning to deploy your application to a range of devices, you should set this to x86|x64|ARM (this is the default value on the template so you can omit this property if you want to build for all three platforms)
  • $(windows_signing_certificate_securefiles_filename) – make sure the windows_signing_certificate_securefiles_filename variable exists in your variable group and that it’s value matches the Secure file name of the certificate (.pfx) file uploaded to the Secure files under the Library in Azure DevOps.
  • $(windows_signing_certificate_password) – make sure the windows_signing_certificate_password variable exists in your variable group and that it is password you specified when creating the certificate. This should be marked as private.

Note that the application package that we’re specifying here is an msixbundle. The Windows build pipeline has been upgraded to support both appx and msix bundles.

Windows – Deploy

The deploy stage for Windows (UWP) is

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_Windows'
    depends_on: 'Build_Windows'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'Windows_output'
    application_package: 'Inspector-Uno-Windows.msixbundle'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-Uno-UWP-Alpha'
    appcenter_release_notes: 'Release from deploy pipeline'

Pipeline Templates v0.3.0

As promised, here’s a quick summary of the changes in v0.3.0. Technically this should have been a major version increment as there were some breaking changes but since we’re not at v1 yet, I’ve just pushed a minor update.

Breaking Changes:

  • build-xamarin-android.yml – change android_bundle_name parameter to application_package (for consistency with deploy-appcenter.yml)
  • build-xamarin-ios.yml – change ios_ipa_name parameter to application_package (for consistency with deploy-appcenter.yml)
  • build-xamarin-windows.yml – change windows_bundle_name parameter to application_package (for consistency with deploy-appcenter.yml)

Other Changes:

  • build-xamarin-android.yml – added environment variables (ANDROID_NDK_HOME, ANDROID_NDK_PATH, AndroidNdkDirectory) so that the build process can locate the NDK. This is required for Android builds that enable AOT compilation.
  • build-xamarin-android.yml: android_manifest_filename property no longer required – template will search for AndroidManifest.xml
  • build-xamarin-windows.yml: windows_package_manifest_filename property no longer required – template will search for *.appxmanifest
  • build-xamarin-ios.yml: ios_plist_filename property no longer required – template will search for Info.plist
  • build-xamarin-windows.yml: application_package supports both appxbundle and msixbundle file types. You need to make sure the filename matches the type of bundle your application is setup to create.

Deploy Xamarin.Forms Apps to App Center from a Azure Multi-Stage Pipeline using Templates and Environments that Require Manual Approval

Wow, that title’s a mouthful, and I didn’t add in there that I’ve just pushed v0.2.0 release of the Pipeline Templates repository. In this post we’re going to add stages to a YAML based Azure DevOps pipeline in order to deploy a Xamarin.Forms application to AppCenter for testing. We’ll also be using on the of … Continue reading “Deploy Xamarin.Forms Apps to App Center from a Azure Multi-Stage Pipeline using Templates and Environments that Require Manual Approval”

Wow, that title’s a mouthful, and I didn’t add in there that I’ve just pushed v0.2.0 release of the Pipeline Templates repository. In this post we’re going to add stages to a YAML based Azure DevOps pipeline in order to deploy a Xamarin.Forms application to AppCenter for testing. We’ll also be using on the of the latest features of the Azure DevOps YAML based pipelines, Environments, to insert a manual approval gate into our multi-stage pipeline.

Pipeline Templates v0.2.0

Before we get into talking about releasing apps to AppCenter I just wanted to reiterate that there is a new release of the Pipeline Templates repository with the following changes:

  • Added a new parameter, stage_name, to iOS, Android and Windows build templates. It has a default value, which matches the value previously specified on the stage element in the template, so won’t break any existing builds. This parameter can be set by the calling pipeline so that the stage is given a known name, which can then be referenced by other stages in the dependsOn element.
  • Added deployappcenter.yml template that can be used to deploy iOS, Android and Windows apps to AppCenter. For Android, if the application_package parameter is an .aab file, the calling pipeline will also need to supply keystore information. AppCenter doesn’t support .aab files, so the pipeline uses bundletool to generate and sign a fat apk, which is submitted to AppCenter.

Deploy to AppCenter

With a classic pipelines in Azure DevOps, you can setup separate build and release pipelines. However, YAML pipelines don’t differentiate between build and release pipelines; instead you can split a single pipeline into multiple stages (as we demonstrated in the previous post when we used different templates to create different stages in the build process).

To deploy apps to AppCenter we could simply create a template, similar to what we did with the build templates, that includes the tasks necessary to deploy to AppCenter, and then add new stages to our pipeline for each app we want to deploy. However, this would limit our ability to take advantage of some of the deployment specific features that are available in Azure DevOps. For this reason, the AppCenter template makes use of a deployment job in order to do the steps necessary to release an app to AppCenter.

Using the AppCenter Template

The following YAML pipeline provides an example of using the new AppCenter deployment template to deploy Windows (UWP), Android and iOS applications to AppCenter. Note that the ref element for the repository resource has been updated to point to the v0.2.0 tag.

resources:
  repositories:
    - repository: builttoroam_templates
      type: github
      name: builttoroam/pipeline_templates
      ref: refs/tags/v0.2.0
      endpoint: github_connection
  
variables:
  - group: 'Inspector XF Build Variables'
  
stages:
## Build stages excluded for brevity
- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_Windows'
    depends_on: 'Build_Windows'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'Windows_output'
    application_package: 'Inspector-XF-Windows.appxbundle'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-XF-UWP'
    appcenter_release_notes: 'Release from deploy pipeline'

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_Android'
    depends_on: 'Build_Android'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'Android_output'
    application_package: 'Inspector-XF-Android.aab'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-XF-Android-Alpha'
    appcenter_release_notes: 'Release from deploy pipeline'
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_iOS'
    depends_on: 'Build_iOS'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'iOS_output'
    application_package: 'Inspector-XF-iOS.ipa'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-XF-iOS-Alpha'
    appcenter_release_notes: 'Release from deploy pipeline'

There are a couple of prerequisites that need to be setup in order for the deploy stages to work:

  • Each deploy stage specifies a depends_on property. This needs to correlate to the stage_name property specified on the corresponding build stage.
  • The artifact name, artifact folder and application_package properties need to match to the values used in the corresponding build stage.
  • A Service Connection needs to be established between Azure DevOps and AppCenter (in this case it was given the name ‘AppCenterInspectorCI’)
  • An application needs to be registered in AppCenter for each target platform. Each deploy stage needs the organisation (ie username or org_name) and applicationid (app_identifier). See the documentation on the AppCenterDistribute task for more information on how to find these values for your AppCenter apps.

Manual Approval with Environments

I mentioned earlier that using a deployment job would allow us to take advantage of deployment specific features. This is an area that’s currently under development and we’d expect to see more features lighting up in this area over time.

One feature that’s available today is the ability to add manual approval requirement to a deployment job. However, unlike in the classic pipeline where you’d create a manual approval requirement directly on the release pipeline, on a YAML pipeline you actually need to associated the deployment job with an environment and then add a manual approval on the environment.

You may have noticed that each of the deploy stages in the example YAML specified the environment_name property. This defines which environment you’re going to be deploying to. At this stage the only thing you can use this for in terms of deploying a mobile application is to require manual approval for the stage to continue. Let’s step through creating the environment and the approval requirement and you’ll see what I mean.

Create an Azure Pipelines Environment

Under the Pipelines tab, select Environments and then click the Create environment button in the center of the screen.

Next, provide a name for your environment and click Create. At this stage we don’t need to define any resources, so you can leave the default selection of “None”. The name that you specify for your environment has to match what you use as the environment_name property on the template.

Adding Manual Approval to the Environment

In order to add a manual approval requirement to an environment, simply open the environment (if you’ve just created the environment you’re already there). From the drop-down menu in the top-right corner, select Approvals and checks.

Next, click the + button in the top right corner.

Select Approvals, and then click Next

In the Approvals flyout you can specify a list of users and/or groups that need to approve a release to the environment. If you specify multiple users, each user needs to approve the release. If you specify a group, only one person in the group needs to approve the release.

In the Approvals flyout you can also specify a timeout; if the deployment isn’t approved for an environment within the timeout, the pipeline will fail.

Note: Currently there are no emails, or other notifications, sent to approvers. If you limit the timeout, once the specified period has elapsed if the environment hasn’t been approved, the pipeline fail and a notification will be sent out. The stage in the pipeline that failed can then be manually run and again, approval for the environment will be required.

Running the Build and Deploy Pipeline

Now when we run the pipeline, what we see in the portal is three different rows (one for each platform) with two stages (a build and deploy stage).

We’ve set the dependsOn element on each of the build stages to an empty array (ie []) meaning that they have no dependencies (btw the default is that stages will be done in the sequence that they appear in the YAML file, unless you indicate there should be no dependencies). Depending on how many build agents you have at your disposal the build stages may run in sequence, or in parallel.

Eventually, when each of the build stages completes, the corresponding deploy stage will light up indicating that it’s waiting for a check to be passed. There’s also a message box inserted into the interface to draw attention to the required approval.

Once all three of the build stages are complete, there are 3 approvals required; one for each of the deploy stages. The way we’ve structured the pipeline you have to approve the deployment for each platform.

If you wanted to require only a single approval for all three platforms you could inject an additional stage that was dependent on all three build stages. The approval would be required for this single stage, and then each of the subsequent deploy stages would be permitted to continue without further checks. The downside of this would be that you would have to wait for all builds to fail before you could deploy the app to any platform.

Summary

In this post we’ve looked at using a pipeline template for deploying Xamarin.Forms applications to AppCenter for testing across Windows (UWP), iOS and Android. In actual fact, and what I didn’t point out earlier, the deploy template can be used for any iOS, Android or Windows (UWP) app, not just a Xamarin.Forms application.

I also walked through setting up an environment so that you could add a manual approval step to the deployment process. Whilst the YAML pipelines don’t yet have all the features of the classic release pipeline, you can see from the way that the components connect and the UI that’s built in the portal that the foundations have been laid for future features to be built on.

Pipeline Templates: Building Xamarin.Forms Apps on Azure DevOps using Templates

One of the things I find frustrating is that for every new project we seem to have to recreate the build and release pipeline. In each case we step through the same steps, run into the same, albeit familiar, issues and end up with a pipeline that looks incredibly similar to the pipeline we setup … Continue reading “Pipeline Templates: Building Xamarin.Forms Apps on Azure DevOps using Templates”

One of the things I find frustrating is that for every new project we seem to have to recreate the build and release pipeline. In each case we step through the same steps, run into the same, albeit familiar, issues and end up with a pipeline that looks incredibly similar to the pipeline we setup for the last project we started. In this post I’m going to share the first set of Azure DevOps build templates that you can reuse in order to build your Xamarin Forms for iOS, Android and Windows.

Background

This started out as an experiment to consolidate three different build pipelines into a single build pipeline. Often for mobile applications developers resort to setting up a build pipeline for each target platform, and then potentially each target environment. For us this because untenable as we were working on a white-labelled product that would need to have a build pipeline for each offering, which would grow over time.

The first step was to leverage the ability in Azure DevOps to create multi-stage pipelines. I setup a different stage for each target platform. You might be asking, why did I set them up as different stages; couldn’t I have just created additional tasks, or perhaps even different jobs for the different platforms. Well, the nice thing about different stages is that they can use different build agents. This is of course an absolute must, since iOS needs to be built by a Mac agent, whilst Windows (ie UWP) needs to be built by a Windows build agent. I could have cheated and wrapped the Android build with either the iOS or Windows build to cut down on the build time but you’ll see in a bit why I kept them separate.

In addition, my initial goal for this experiment was to have a standard build template that we could use and that I could share with the community via my blog. However, I felt this was only a part solution – there are so many great posts out there on how to setup a build process for XYZ but they become stale the minute they’re published. What if we could create a set of templates that could be released, much like a product, and could evolve over time.

Pipeline Templates

In this post I’m introducing the Pipeline Templates github repository where I’ll be evolving build templates to help developers avoid the complexity of setting up the entire Azure DevOps, instead, focusing on building amazing applications. It’s still early days but check out the repository, watch and provide feedback so that we can evolve these templates as a community.

We’ll be starting with three templates that can be used to build a Xamarin.Forms application for iOS, Android and Windows. If we look at the basic structure of the repository, you’ll see that there’s a folder for Azure and a sub-folder for Mobile. The rationale was that over time this repository could house templates for other build platforms.

I also wanted to separate mobile app builds away from builds for the web or cloud services. However, the more I think about this the more I feel this separation is somewhat arbitrary because web apps built using SPA frameworks like React, Angular, Vue etc are similar in so many ways to mobile or desktop apps. I can imagine this structure will evolve over time – if you’re going to reference the templates in this repository, make sure you use the tags to ensure your build process doesn’t change or break as the templates evolve (more on this later).

Creating the Azure Build Pipeline

In order to use the pipeline templates you need to reference the GitHub repository as a resource within your build pipeline. I’ll walk through creating a new build pipeline but if you can add resources to an existing Yaml based pipeline using similar steps.

Start from the Pipelines tab in the Azure DevOps web portal and click the New Pipeline button (top right corner of the screen). Follow the steps to select your source code repository and pick a New pipeline template to use (I typically go with the Starter pipeline but either way we’re going to replace most of the yaml anyhow).

When you’re on the review tab, I suggest that you rename the yaml file. It’s not immediately obvious that you can change the name of the yaml file. The following image shows the default name on the left and then if you tap on the azure-pipelines.yml text you can actually edit it. On the right side of the image is the new filename and I’ve placed it in a pipelines folder to keep everything tidy in the repository.

Rather than making changes at the review tab, I just hit save (not the default “save and run” since we know the pipeline isn’t setup correctly).

Connecting to a GitHub Resource

According to the documentation you should be able to just add a GitHub resource directly to the Yaml file. However, in doing this I found that my pipeline didn’t work until I created an endpoint, similar to if you were to add other authenticated repositories as a resource.

Creating an endpoint is actually quite simple but you may well need additional permissions on your repository, depending on what access permissions you have. Click on Project Settings, in the bottom left corner of the Azure DevOps web portal, then click on the Service Connections tab. Click on the New service connection button, that’s in the top right corner of the Service connections screen. Select the GitHub connection type and then populate the New GitHub service connection flyout.

Once you click the Authorize button, you’ll most likely need to adjust the Service connection name – you’ll need this value in the next step.

The next step is to go back to your newly created pipeline (Note: if it doesn’t appear when you click on the Pipelines tab, you’ll need to switch view to All instead of Recent since you haven’t run your pipeline yet). Once you have the pipeline open, click Edit to bring up the yaml editor.

Replace the contents of the yaml file with the following:

resources:
  repositories:
    - repository: builttoroam_templates
      type: github
      name: builttoroam/pipeline_templates
      ref: refs/tags/v0.1.0
      endpoint: Pipeline-Templates

This block of yaml references the pipeline_templates repository that’s owned by builttoroam. It’s going to use the Pipeline-Templates service connection to access the repository where it’s going to look at tag v0.1.0. In the pipeline this resource can be referenced as builttoroam_templates, which is just a local name assigned to this resource by the pipeline.

Important: that you specify the ref attribute and specify a tag. If you don’t, you’ll be pointing to the latest available templates on master. Referencing master is fine as you setup your pipeline but you should change to referencing a tag release to make sure that as we change the repository, your build continues to work.

Xamarin.Forms Build Pipeline

Now that we’ve added a reference to the pipeline templates repository, we can make use of any of the templates. For example the following yaml references the ios Xamarin template.

stages:
- template:  azure/mobile/[email protected]_templates
  parameters:
    solution_filename: 'src/MyApp.sln'
    solution_build_configuration: 'Release'

In this case to access the template in the pipeline templates repository, we firstly need to use the @[local resource name] syntax and then secondly we need to provide the path to the yml file (ie azure/mobile).

This yaml snippet defines the stages for the build pipeline, including a stage that’s defined in the referenced template file (in this case build-xamarin-ios.yml). You can add additional stages by simply repeating the -template section and adjusting the name of the template.

This code snippet shows just the first two parameters being passed in. At the end of this post is a full example showing the required parameters. There are some additional parameters that can be used to adjust output file name and folder, as well as for adding custom steps to the beginning, pre and post build and at the end of the stage.

Full Example

The following is a fully worked example which includes iOS, Android and Windows. Note that there are a number of variables that are being passed into the templates. These are all defined within the Inspector XF Build Variables group, which can be defined via the Library tab in Azure DevOps.

resources:
  repositories:
    - repository: builttoroam_templates
      type: github
      name: builttoroam/pipeline_templates
      ref: refs/tags/v0.1.0
      endpoint: Pipeline-Templates
  
variables:
  - group: 'Inspector XF Build Variables'
  
stages:
- template:  azure/mobile/[email protected]_templates
  parameters:
    solution_filename: 'src/Apps/DotNet/Inspector.XF.sln'
    solution_build_configuration: 'Release'
    ios_plist_filename: 'src/Apps/DotNet/XF/InspectorXF/InspectorXF.iOS/Info.plist'
    ios_cert_password: '$(ios_signing_certificate_password)'
    ios_cert_securefiles_filename: '$(ios_signing_certificate_securefiles_filename)'
    ios_provisioning_profile_securefiles_filename: '$(ios_provisioning_profile_securefiles_filename)'
    build_number: '$(Build.BuildId)'
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    ios_signing_identity: '$(ios_signing_identity)'
    ios_provisioning_profile_id: '$(ios_provisioning_profile_id)'

- template:  azure/mobile/[email protected]_templates
  parameters:
    build_android: $(android_enabled)
    solution_filename: 'src/Apps/DotNet/Inspector.XF.sln'
    solution_build_configuration: 'Release'
    android_manifest_filename:  'src/Apps/DotNet/XF/InspectorXF/InspectorXF.Android/Properties/AndroidManifest.xml'
    build_number: '$(Build.BuildId)'
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'

- template:  azure/mobile/[email protected]_templates
  parameters:
    solution_filename: 'src/Apps/DotNet/Inspector.XF.sln'
    solution_build_configuration: 'Release'
    uwpBuildPlatform: '$(uwpBuildPlatform)'
    windows_package_manifest_filename:  'src/Apps/DotNet/XF/InspectorXF/InspectorXF.UWP/Package.appxmanifest'
    build_number: '$(Build.BuildId)'
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    windows_cert_securefiles_filename: '$(windows_signing_certificate_securefiles_filename)'
    windows_cert_password: '$(windows_signing_certificate_password)'

There are also some secure files that have been added to the Library, which again are referenced here using a variable defined in the variable group. The process for adding secure files is that you upload the file, give it a friendly name and then assign that friendly name to a variable that’s in the variable group.

Summary

In this post I’ve given you a very quick introduction to the Pipeline Templates repository. Over the coming posts I’ll walk through some of the templates and what you can do with them in more detail. I’d love feedback on the templates – raise an issue to suggest changes on the GitHub repository and I’ll see if I can encorporate them.

Multiple Environments Using ApkTool Extension for Azure DevOps

In my last couple of posts (here and here) I talked a bit about using the ApkTool to repack an Android APK in order to update an Android application to target different environments. To make this easier I’ve just published a preview of an extension for Azure DevOps. The ApkTool Build and Release Task extension … Continue reading “Multiple Environments Using ApkTool Extension for Azure DevOps”

In my last couple of posts (here and here) I talked a bit about using the ApkTool to repack an Android APK in order to update an Android application to target different environments. To make this easier I’ve just published a preview of an extension for Azure DevOps.

The ApkTool Build and Release Task extension can be installed from the Visual Studio Marketplace or via the Azure DevOps editing experience, by searching for ApkTool.

Build the Android APK

Before we walk through using the ApkTool task I wanted to point out that I typically separate the build and the release pipelines. However, the ApkTool task can be integrated into your build process, if that’s how you want to configure your devops process.

The build process for a Flutter app might look similar to the following (typically for a production app I’ll have an additional task that updates the version number).

Release Pipeline

In the release pipeline the basic set of steps are to:

  • Unpack the APK (using the ApkTool task)
  • Modify a configuration file to change the environment
  • Pack the APK (using the ApkTool task again)
  • Sign and Zipalign the APK
  • Push the APK to App Center for distribution

To get started, let’s add the various steps to the release pipeline. The ApkTool, once installed into your Azure DevOps instance, can be found by simply searching the tasks for ‘apktool’.

My release pipeline looks like the following.

Note: I’m using the Replace Tokens task to update the configuration of my application – you might decide to use a different task, or invoke a powershell script to do your own app customisation.

Unpack with the ApkTool Task

To unpack the Apk using the ApkTool task, you simply need to select the Unpack (decode) option for ApkTool Action and then provide the path to the apk and the name of the output folder.

Updating App Configuration

In my scenario I’m simply updating some text in a file (config.txt) that’s packaged with my application. The Replace Tokens task allows me to search for a regular expression and then replace it. In this case the group “Default Config” will be replaced by the release pipeline variable with the key Default Config.

Packing with the ApkTool Task

Once you’re done updating the app contents (you might want to also update icons and/or modify the manifest file) you can then use the ApkTool task to pack the Apk. Simply specify the Pack (build) option for the ApkTool Action, specify the Input folder (this should match the Output folder from when you unpacked the Apk) and then name of the apk to be generated.

Signing and Distributing to AppCenter

After you’ve repacked your Android application to a new Apk you’ll need to sign and zipalign your application. This can be done using the Android signing task, provided by Microsoft.

Once you’re application has been signed, it’s ready for distribution. For this you can use the App Center distribute task to push your Apk to App Center and have it automatically made available to a specific group(s) of testers.

Multiple Environments

Now that you have a release pipeline that repacks your application for a specific environment, you should consider having different stages in your release pipeline. Each stage can repack the application for a different environment.

Having a multi-stage release pipeline allows you to set up a single pipeline to progress a single build of your application from dev, to test, staging and through to production (or whatever your sequence of environments is). Each stage can have different approval gates, for example:

  • Dev – The first stage of your pipeline can be setup to automatically release on each build (you builds could be a CI build, or scheduled)
  • Test – Deployment to test can require approval from the test team, so they know which build they’re currently testing
  • Staging – Deployment to staging might require approval from the test team and customer support (depending on whether there are customers that assist with pre-release testing)
  • Production – Deployment to production might require approval from test team, customer support and product owner/manager.

Hopefully the ApkTool task for Azure DevOps will make it easy for you to setup a release pipeline. Feel free to provide feedback on the task – it’s preview at the moment but mainly because I haven’t got around to adding documentation etc.

Scripting the Repacking of Android Apps for a Release Pipeline

Following yesterday’s post on repacking an Android APK, I was pointed to an awesome post by Daniel Causer entitled Build Once Release Everywhere – APK. Daniel’s post goes into a lot of detail on how to setup your build and release process. Specifically the scripts that he’s created for automating the repacking process I described in … Continue reading “Scripting the Repacking of Android Apps for a Release Pipeline”

Following yesterday’s post on repacking an Android APK, I was pointed to an awesome post by Daniel Causer entitled Build Once Release Everywhere – APK. Daniel’s post goes into a lot of detail on how to setup your build and release process. Specifically the scripts that he’s created for automating the repacking process I described in my post (you can grab them from his github repo.

I think the logical next step is for the creation of a Azure DevOps extension that either allows you to invoke apktool from a task in the build or release pipeline; or an extension that does the whole extract; replace/rename/modify file and then repack and sign. I suspect the former might be the best way as it would give developers the most flexibility. However, it would require more steps to be configured in the release pipeline.

Repacking and Resigning an Android APK to Target Different Environments

In my previous post talking about targeting different environments I ended with the proposition that what we need to be able to do as part of the release pipeline for an app is to adjust the configuration file that’s included in the app package. In this post I’m going to manually walk through what this … Continue reading “Repacking and Resigning an Android APK to Target Different Environments”

In my previous post talking about targeting different environments I ended with the proposition that what we need to be able to do as part of the release pipeline for an app is to adjust the configuration file that’s included in the app package. In this post I’m going to manually walk through what this process would look like for an Android APK (and yes, before you all jump up and down and say that I should be using an app bundle, I’m aware of this but let’s do this process step by step).

Ok to begin, what I need is a release-ready APK and for the purpose of this post I’m going to use a Flutter app. The process I’m going to describe will work for any Android APK regardless of the toolchain/framework/technology set that you’re using to build your app. The only difference with say a Xamarin.Forms application would be how you package the configuration file; the code you’d need to write to read the configuration file and of course the XAML for displaying the contents to the screen.

Basic App Structure

My sample Flutter app starts with the default app template you get when creating a new Flutter app in VS Code. I then added a single text file, config.txt, to the assets folder; included the file in the pubspec.yaml and then adjusted the _MyHomePageState class to load the contents of the file (a basic walk through of reading files that are included in the app package are included in this post).

class _MyHomePageState extends State<MyHomePage> {
  String config = '';

  @override
  void initState() {
    super.initState();

    loadConfig(context);
  }

  void loadConfig(BuildContext context) async {
    config =
        await DefaultAssetBundle.of(context).loadString('assets/config.txt');
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'App configuration:',
            ),
            Text(
              '$config',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
    );
  }
}

The contents of the config.txt file simply says “***Default App Config***” and running the app looks like:

Release Ready APK

In order to walk through the process of repackaging an APK, I firstly need to make sure I have a release-ready APK. By this I mean that I have an APK that’s been built in release mode and that has been signed, as if I were going to submit it to the Google Play store.

The Flutter documentation has very clear instructions on how to package and sign your application. I followed these instructions to generate a keystore that is used as part of the Flutter build process to sign the application.

In order to test to make sure your APK is good to go, simply copy it to a real device and test that you can install and run it. For this I simply uploaded the APK to dropbox and then opened the file on my device. You could also attach to an email or even side load directly via USB cable if you choose.

Repackaging to Change App Configuration

The basic process we want to follow is:

  • Unpack the APK
  • Modify the config.txt file
  • Repack the APK
  • Sign the APK

Unpacking the APK

In order for us to be able to modify the config.txt file that’s packaged in the app, we first need to unpack the APK. For this I’m going to use the APKTool utility. Follow the installation instructions to make sure you have the latest version and the appropriate directories added to the PATH variable (you may need to add the Java directory to your PATH).

Once installed, to unpack an APK you can simply call the APKTool with the decode, or just “d”, argument:

   apktool d app-release.apk -o extracted_apk

Note that I specified the “-o” argument to allow me to specify the output folder.

Modify the Config.txt

In this example we’re simply going to modify the config.txt file that we have included in the app package. You could be more fancy and include a json or xml configuration file but essentially all you’re going to do in this step is modify the contents, or replace the file entirely.

In the case of my sample Flutter app, the location of the config.txt is in the sub-folder “\assets\flutter_assets\assets”. If you’re application is built using Xamarin.Forms, your configuration file may be located in a different folder – you just need to search the extracted folder and locate the file.

I’ve changed the contents of the file to “***Modifited App Config***”

Repack the APK

After making the change to the config.txt file we then need to repack the APK. For this we can again use the APKTool, this time specifying the build, or “d”, argument:

apktool b extracted_apk -o app-release-mod.apk

More detailed information on the APKTool is available from their documentation page.

Sign the APK

The last step is to sign the APK and for this I’m going to use the Uber Apk Signer along with the keystore I setup as part of configuring the Flutter release build.

java -jar uber-apk-signer-1.1.0.jar -a app-release-mod.apk --ks c:\<<path to keystore>>\key.jks --ksAlias key --ksKeyPass <<password>> --ksPass <<password>> -o app-release-mod-signed

And that’s it – if we check in the app-release-mod-signed folder there’ll be a new APK that’s signed and ready to go.

Copy the application to your device and run it and you’ll see the updated configuration value (and yes, complete with spelling mistake!!)

Repackaging Apps During Release Process

As you can see from this process, it’s not too hard to adjust a configuration value by repacking an Android APK. You can simply include the APKTool and the Uber-Apk-Signer alongside your application and script out these steps as part of your Release pipeline.

Visual State Management with BuildIt.States and Uno

I’ve posted previously on using visual states in Uno and how they can be used to effectively manage the different visual layouts a page can take on. These may be changes in layout due to the application being resized, or perhaps due to different data loading states. I’ve recently created a Uno build of the … Continue reading “Visual State Management with BuildIt.States and Uno”

I’ve posted previously on using visual states in Uno and how they can be used to effectively manage the different visual layouts a page can take on. These may be changes in layout due to the application being resized, or perhaps due to different data loading states. I’ve recently created a Uno build of the BuildIt.States library, BuildIt.States.Uno. In this post I’m going to walk through using this library to help manage visual states from within your view model.

One of the topics that’s quite hot at the moment is whether XAML has had its day and whether the new coded UI techniques provide a better solution. I’m not going to go too far into this debate but one of the things I really like about XAML is the separation of the UI (declared in XAML) from its data representation (the ViewModel).

Whilst the data binding framework of both UWP and Xamarin.Forms works well for connecting properties on a ViewModel to attributes of a UI element, there is no way to connect visual states on the page, with some aspect of the ViewModel. This is where the BuildIt.States library kicks in.

Thinking about Visual States

Before we get into using the BuildIt.States library, lets start by thinking through the different states that our page can go through. For the purpose of this post we’re going to build a simple app with the following spec:

  • The app has a single page that has a button, “Load Data”
  • When the button is pressed, the button will be hidden and a loading indicator will be shown.
  • The app will attempt to load some data.
  • Loading data will randomly succeed or fail
  • When loading is complete (either succeed or fail) the loading indicator will be hidden, a message will be displayed indicating success or failure, and the Load Data button will be displayed again.
  • If the Load Data button is pressed again, the status of the previous attempt will remain on screen until the new attempt to load data has been completed.

An initial read of this spec would seem to indicate that there is a single set of states:

  • Not Loading
  • Loading
  • Loaded – Success
  • Loaded – Failed

However, when the button is pressed for the second time, there are two more states that need to be included : Loading (Loaded – Success) and Loading (Loaded – Failed).

An alternative way of thinking about the states is that there is a group of states that pertain to whether data is being loaded, and a different group of states that pertain to whether the page has data:

LoadingStates

  • NotLoading
  • Loading

These states control whether the button or the loading indicator is visible

DataStates

  • NoData
  • Data
  • DataFailedToLoad

These states control the visibility of the data, or in this case the message indicating success or failure of the data loading.

Visual States in XAML

Since the focus of this post isn’t on how to design a page in XAML I’ll skip over the steps involved in laying out the page. In summary, after creating a new Uno project (using the Uno project templates), I opened the MainPage.xaml in Visual Studio Blend. Whilst Blend is a long way from being the design-first tool it was once envisaged as, it does still have support for defining visual states, which is sorely missing in Visual Studio (and probably the only reason I still use Blend).

Using the design surface in Blend I’m able to layout the various TextBlock, Button and ProgressRing to build the simple UI for the app. I then use the States tool window to create two Visual State Groups (LoadingStates and DataStates) and the associated Visual States for showing the appropriate elements. The resulting XAML looks like the following:

<Page x:Class="BuildItWithStatesForUno.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:local="using:BuildItWithStatesForUno"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="LoadingStates">
                <VisualState x:Name="NotLoading" />
                <VisualState x:Name="Loading">
                    <VisualState.Setters>
                        <Setter Target="LoadDataButton.(UIElement.Visibility)" Value="Collapsed" />
                        <Setter Target="LoadingProgress.(UIElement.Visibility)" Value="Visible" />
                        <Setter Target="LoadingProgress.(ProgressRing.IsActive)" Value="True" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
            <VisualStateGroup x:Name="DataStates">
                <VisualState x:Name="NoData" />
                <VisualState x:Name="Data">
                    <VisualState.Setters>
                        <Setter Target="DataSuccessTextBlock.(UIElement.Visibility)" Value="Visible" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="DataFailedToLoad">
                    <VisualState.Setters>
                        <Setter Target="DataFailedTextBlock.(UIElement.Visibility)" Value="Visible" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <StackPanel HorizontalAlignment="Center"
                    VerticalAlignment="Center">
            <TextBlock x:Name="DataSuccessTextBlock"
                       Margin="20"
                       FontSize="40"
                       Foreground="Green"
                       Text="Data Loaded Successfully!"
                       TextAlignment="Center"
                       Visibility="Collapsed" />
            <TextBlock x:Name="DataFailedTextBlock"
                       Margin="20"
                       FontSize="40"
                       Foreground="Red"
                       Text="Data Failed to Load"
                       TextAlignment="Center"
                       Visibility="Collapsed" />
            <Grid Margin="50">
                <Button x:Name="LoadDataButton"
                        HorizontalAlignment="Center"
                        Click="LoadDataClick"
                        Content="Load Data"
                        FontSize="30" />
                <ProgressRing x:Name="LoadingProgress"
                              Width="50"
                              Height="50"
                              Foreground="Blue"
                              IsActive="False"
                              Visibility="Collapsed" />
            </Grid>
        </StackPanel>
    </Grid>
</Page>

Testing the Visual States

Before moving on I wanted to test that the Visual States work, so I wired up the Load Data button with an event handler so I could switch the visual states:

private async void LoadDataClick(object sender, RoutedEventArgs e)
{
    var loadTimeInMilliseconds = rnd.Next(1000, 10000);
    var success = loadTimeInMilliseconds % 2 > 0;
    VisualStateManager.GoToState(this, "Loading", true);
    await Task.Delay(loadTimeInMilliseconds);
    VisualStateManager.GoToState(this, "NotLoading", true);
    VisualStateManager.GoToState(this, success ? "Data" : "DataFailedToLoad", true);
}

Clearly this code isn’t production ready (string literals, codebehind, no error handling etc) but when I run the app, I can see the desired state changes. Here I’m just showing Android but since it’s Uno, it should work nicely on iOS, UWP and WASM too.

Visual States in Action (Gif captured and generated using Snagit from TechSmith)

ViewModel States

Now that we’ve defined the visual states, we need a way to both control and track the states in our ViewModel. In the same way that we can use data binding to update attributes of the visual elements on the page, we need a way to mirror visual states within our ViewModel. This is where we can make use of the StateManger from the BuildIt.States library.

In the following MainViewModel, a StateManager is created and setup using two different state groups. Rather than using string literals, we use an enum to define each state group. Note that each enum has a Base value, which reflects the default, or unset, state.

public enum LoadingStates
{
    Base,
    NotLoading,
    Loading
}

public enum DataStates
{
    Base,
    Data,
    DataFailedToLoad
}

public class MainViewModel : IHasStates
{
    private readonly Random rnd = new Random();

    public IStateManager StateManager { get; } = new StateManager();

    public MainViewModel()
    {
        StateManager
            .Group<LoadingStates>()
            .DefineAllStates()
            .Group<DataStates>()
            .DefineAllStates();
    }

    public async Task LoadData()
    {
        var loadTimeInMilliseconds = rnd.Next(1000, 10000);
        var success = loadTimeInMilliseconds % 2 > 0;

        await StateManager.GoToState(LoadingStates.Loading);
        await Task.Delay(loadTimeInMilliseconds);
        await StateManager.GoToState(LoadingStates.NotLoading);
        await StateManager.GoToState(LoadingStates.Loading);
        await StateManager.GoToState(success ? DataStates.Data : DataStates.DataFailedToLoad);
    }
}

I also need to update the codebehind in MainPage to create an instance of MainViewModel and then invoke the LoadData method when the Load Data button is clicked:

public sealed partial class MainPage : Page
{
    public MainViewModel ViewModel => DataContext as MainViewModel;

    public MainPage()
    {
        this.InitializeComponent();

        DataContext = new MainViewModel();
    }
    private async void LoadDataClick(object sender, RoutedEventArgs e)
    {
        await ViewModel.LoadData();
    }
}

Connecting ViewModel to Visual States

Of course, if we run the code at this point, the state tracked within the MainViewModel don’t update the UI. To complete the loop, we need to attach another StateManager to the visual states defined on MainPage, and then bind the two StateManagers so that they can remain in sync. This is all done in the MainPage codebehind.

public sealed partial class MainPage : Page
{
    public MainViewModel ViewModel => DataContext as MainViewModel;
    private IStateManager StateManager { get; } = new StateManager();

    public MainPage()
    {
        this.InitializeComponent();

        DataContext = new MainViewModel();

        StateManager
            .Group<LoadingStates>()
            .DefineAllStates(this,this.LoadingStates)
            .Group<DataStates>()
            .DefineAllStates(this, this.DataStates);

        StateManager.Bind(ViewModel.StateManager);
    }
    private async void LoadDataClick(object sender, RoutedEventArgs e)
    {
        await ViewModel.LoadData();
    }
}

And there we go – now when you run the application and press the Load Data button the LoadData method on the MainViewModel will be invoked. As the states of the MainViewModel change, the StateManager and subsequently the visual states on the MainPage are updated.

How to Support Multiple Environments in your Mobile Application?

Whether you’re developing an Android app in Kotlin, a cross-platform app in Flutter or Xamarin Forms, or an Xbox app in C#/XAML, supporting multiple environments when building an app, is just not as easy as it should be. For example the different environments might be dev, test, staging, prodution etc to align with your dev, … Continue reading “How to Support Multiple Environments in your Mobile Application?”

Whether you’re developing an Android app in Kotlin, a cross-platform app in Flutter or Xamarin Forms, or an Xbox app in C#/XAML, supporting multiple environments when building an app, is just not as easy as it should be. For example the different environments might be dev, test, staging, prodution etc to align with your dev, test and release process. Alternatively, you might have a white-labelled app that you can configure for a particular customer by adjusting some application settings. In each of these scenarios, it would be ideal to be able to deploy our application, along with a configuration, or settings, file. In this post we’re going to discuss why this isn’t possible, how this problem is typically solved, and then discuss an alternative approach to solving the problem.

Configuration Files

Before we jump in and discuss native applications, let’s take a look at a couple of scenarios where configuration files are already supported. The first example is a typical web applications that can be built once, and then released to an almost unlimited number of environments, where each one can have different settings, or attributes, applied using some form of a settings or configuration file. For example with an ASP.NET application you can specify application settings in the web.config or app.settings files. Alternatively if you’re deploying to an Azure App Service, you can configure various settings directly via the Azure portal (including overriding settings on a per-slot basis).

The use of configuration files isn’t isolated to web applications. In fact both WinForms and WPF applications can take advantage of the ConfigurationManager class in the .Net Framework to dynamically load configuration data from a file packaged alongside the application.

The introduction of application stores (eg the iOS App Store, Google Play Store and the Microsoft Store) brought with it the notion of an application package. Applications were packaged and then signed to ensure that what was received, and subsequently installed, on the device was the same package the publisher had submitted and that had been approved for distribution. None of the main stores support distributing a configuration file alongside the application, in the same way you could have done with a private distribution of a WinForms app.

Packaged Configuration Files

Given that it’s not possible to distribute a configuration file in parallel to the application, it is necessary to include configuration files within the application package. There are a couple of alternatives that you should consider when deciding on a configuration system.

Build Configuration Constants

This post by Jon that provides some background on what a build configuration is within Visual Studio and how to take advantage of it to control the behaviour of your application during development (Debug configuration) and in production (Release configuration).

Build configurations can define compilation constants that can be used to dynamically include or exclude code at compile time. The Debug build configuration typically already has the DEBUG constant defined but you can define your own. For example in the following image the DEV_ENV constant has been defined for the Debug build configuration.

In code, you can then use these constants to determine what code gets compiled. For example in the following code, when compiled with the Debug build configuration the DEV_ENV constant is defined, so the first definition of HelloText will be compiled. For all other build configurations, the DEV_ENV constant isn’t defined, so the second definition is compiled.

public static partial class Constants
{
#if DEV_ENV
    public const string HelloText = "Hello World - Dev Environment";
#else
    public const string HelloText = "Hello World";
#endif
}

You can extend this to include or exclude entire files by modifying the project file. There is no UI built into Visual Studio for doing this but the syntax of the csproj project file is relative simple, so not too hard to tweak. The following example demonstrates how to exclude two files (since all files are include by default within the project folder system), DebugConstants.cs and ReleaseConstants.cs, and then to selectively include them for the different build configurations.

<Project Sdk="Microsoft.NET.Sdk">
  ....
  <ItemGroup>
    <Compile Remove="DebugConstants.cs" />
    <Compile Remove="ReleaseConstants.cs" />
  </ItemGroup>
  <ItemGroup Condition="'$(Configuration)'=='Debug'">
    <Compile Include="DebugConstants.cs" />
  </ItemGroup>
  <ItemGroup Condition="'$(Configuration)'=='Release'">
    <Compile Include="ReleaseConstants.cs" />
  </ItemGroup>
  ....
</Project>

As you switch between Debug and Release build configurations in Visual Studio you can actually see the change in the Solution Explorer, showing which files will be included. In the following image the left screenshot of the Solution Explorer window shows that the DebugConstants.cs file has been included in the Debug configuration, whilst the right shows the ReleaseConstants.cs is included for the Release configuration.

Copy and Replace

In this post by Andrew he covers how you can include an app.settings file within your application. This is similar to the approach presented by Adam in his post on using Configuration Files in Xamarin.Forms.

The app.settings file can be replaced during the build process in order to switch between different environments. You can either choose to replace the entire app.settings file, or you can simply substitute individual key-value pairs.

Mobile Build Tools

Dan Siegel (of Prism notoriety) has developed some mobile build tools that he’s been working on to make it easier for developers to setup DevOps for mobile applications. I’d highly recommend integrating these tools into your build pipeline.

Build v Release Tasks for Multiple Environments

Ok, so before I wrap up this post I want to go back to the original premise I discussed. What I want to be able to do is to build my application once and then have different configurations for each environment. We can think of the devops for our application in two stages, Build and Release. The Build part of our process should do just that, it should build our application, and it should only have to build it once irrespective of what environment it’s going to target. The Release part of our process should augment the application configuration so that it targets the different environment.

The solutions presented so far have all resulted in the need to have different builds setup for each environments, so none of them present an ideal solution. The primary issue with applications is that the packaging format doesn’t support an external configuration file, so it’s not as simple as deploying a web application where you can simply change the configuration file.

To address this issue we need to look at how we can re-package our application during the release process, allowing us to modify a configuration file that’s included as part of the application package. More on this to come….