XAML Back to Basics #7: ItemsPanel

The next post in the series originally written by Beatriz Stollnitz. Original post available on Github. How to change the layout of an ItemsControl I will show in this sample two ways to change the layout of an ItemsControl. This sample uses XmlDataProvider, which allows binding to XML data. The easiest way to change the layout … Continue reading “XAML Back to Basics #7: ItemsPanel”

The next post in the series originally written by Beatriz Stollnitz. Original post available on Github.

How to change the layout of an ItemsControl

I will show in this sample two ways to change the layout of an ItemsControl. This sample uses XmlDataProvider, which allows binding to XML data.

The easiest way to change the layout of an ItemsControl is simply by setting the ItemsPanel property to the Panel that will contain the items:

<ListBox ItemsSource="{Binding Source={StaticResource xmlData}}" (...) >
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>

Alternatively, for more extensive customizations, you can create a ControlTemplate. This ControlTemplate allows you to replace the whole VisualTree, including picking a new Panel to hold the items. For example, the following markup shows a ControlTemplate that adds a Border and changes the Panel on the ItemsControl:

<ControlTemplate x:Key="listBoxTemplate">
    <Border BorderBrush="Orange" 
            BorderThickness="2" 
            Margin="10,0,10,10">
        <StackPanel Orientation="Horizontal"
            IsItemsHost="True" />
    </Border>
</ControlTemplate>

<ListBox 
    ItemsSource="{Binding Source={StaticResource xmlData}}" 
    Template="{StaticResource listBoxTemplate}" (...) />

Most people get this far in this scenario, but often forget to set the IsItemsHost property in the Panel. IsItemsHost is a property that says “Use this Panel to lay out the items in the ItemsControl.” Notice that selection still works as usual.

If you want your items to wrap onto multiples lines, you can use a WrapPanel in place of the StackPanel. In this scenario, bear in mind that the default template for ListBox contains a ScrollViewer, so your items won’t wrap. To make them wrap, you can either provide your own ControlTemplate or, if you don’t need selection to work, use an ItemsControl instead of a ListBox.

As I mentioned before, I am using XmlDataProvider to bind to XML data. This is how I converted the GreekGods CLR data source I’ve used in previous samples:

<Window.Resources>
    <XmlDataProvider XPath="/GreekGods/GreekGod" x:Key="xmlData">
        <x:XData>
            <GreekGods xmlns="">
                <GreekGod>
                    <Name>Aphrodite</Name>
                    <Description>Goddess of love, beauty and fertility</Description>
                    <RomanName>Venus</RomanName>
                </GreekGod>
                (...)
            </GreekGods>
        </x:XData>
    </XmlDataProvider>
</Window.Resources>

The only thing to keep in mind when binding to XML is that instead of using the Path property in the Binding object, you should use the XPath property. You can use either Path or XPath syntax for DisplayMemberPath.

WPF Source Code

WPF

UWP/Uno Notes

The XmlDataProvider doesn’t exist for UWP applications. Instead the GreekGods XML data has been added as an XML file with build action of Embedded Resource. The data is loaded on startup and set as the ItemsSource for each ListBox.

There is also no support for binding using an XPath expression. For this a simple XmlElementConverter has been added.

UWP/Uno Source Code

UWP
WebAssembly (WASM)

XAML Back to Basics #6: SelectedValue v SelectedItem

The next post in the series originally written by Beatriz Stollnitz. Original post available on Github. What is the difference between SelectedValue and SelectedItem? When they are used by themselves, these two properties are very similar. The need for both and the difference between the two becomes apparent when SelectedValuePath is also set. For example, consider … Continue reading “XAML Back to Basics #6: SelectedValue v SelectedItem”

The next post in the series originally written by Beatriz Stollnitz. Original post available on Github.

What is the difference between SelectedValue and SelectedItem?

When they are used by themselves, these two properties are very similar. The need for both and the difference between the two becomes apparent when SelectedValuePath is also set.

For example, consider our well-known GreekGods data source. I set the DataContext of the StackPanel to be that collection through code:

GreekGods items;
items = new GreekGods();
mainStackPanel.DataContext = items;

And used an empty Binding to bind that collection to the ListBox. I know that I want to select the GreekGod with description “Messenger of the Gods” (even though I am only displaying the Name of each God). This is when SelectedValuePath becomes useful. Each item in the ListBox is a GreekGod object, so by setting SelectedValuePath to “Description” I am able to drill down into the Description property of each GreekGod. Then I just have to set SelectedValue to the description I am looking for and the item becomes selected.

<StackPanel Name="mainStackPanel">
    <ListBox ItemsSource="{Binding}" DisplayMemberPath="Name" SelectedValue="Messenger of the Gods" SelectedValuePath="Description" Name="listBox1" (...) />
</StackPanel>

The difference between SelectedValue and SelectedItem should be obvious now. SelectedValue returns the string it was set to (“Messenger of the Gods”), while SelectedItem returns the actual GreekGod object with that description.

string messengerOfGods = (string)(listBox1.SelectedValue);
GreekGod hermes = (GreekGod)(listBox1.SelectedItem);

SelectedValue is particularly useful when only part of your item is stored in the model you are data binding to. In this scenario, you would data bind the SelectedValue property to the partial information in your model but the ListBox can show a lot more information about that item.

If you have ideas of how to combine these two properties in one, we would love to hear it.

WPF Source Code

WPF

UWP/Uno Source Code

UWP

XAML Back to Basics #5: DisplayMemberPath

The next post in the series originally written by Beatriz Stollnitz. Original post available on Github. DisplayMemberPath As I’ve shown in previous posts, binding an ItemsControl to an IEnumerable data source is really easy (remember that ListBox and ComboBox derive from ItemsControl). With DisplayMemberPath it’s even easier for the scenario where you want to display only … Continue reading “XAML Back to Basics #5: DisplayMemberPath”

The next post in the series originally written by Beatriz Stollnitz. Original post available on Github.

DisplayMemberPath

As I’ve shown in previous posts, binding an ItemsControl to an IEnumerable data source is really easy (remember that ListBox and ComboBox derive from ItemsControl). With DisplayMemberPath it’s even easier for the scenario where you want to display only one property of each data item as text. Before DisplayMemberPath, this scenario required the use of a DataTemplate that would specify the property we’re interested in, like in the following xaml:

<Window.Resources>
    <DataTemplate x:Key="itemTemplate">
        <TextBlock Text="{Binding Path=Name}" />
    </DataTemplate>
</Window.Resources>

<ItemsControl ItemsSource="{StaticResource greekGods}" ItemTemplate="{StaticResource itemTemplate}" />

The Data Binding team realized that this was a very common scenario and could be simplified, which was the motivation for introducing the DisplayMemberPath property in ItemsControl. The scenario above can now be done in a single line of xaml:

<ItemsControl ItemsSource="{StaticResource greekGods}" DisplayMemberPath="Name" />

It’s that easy 🙂

The image below shows both versions of the ItemsControl, the one on the left is using DataTemplate and the one on the right is using DisplayMemberPath.

WPF Source Code

WPF

UWP/Uno Source Code

XAML Back to Basics #4: ComboBox Binding

The next post in the series originally written by Beatriz Stollnitz. Original post available on Github. How to bind the items of a ComboBox (and get its ComboBoxItems) Binding the items of a ComboBox is pretty much the same as binding the items of a ListBox: <Window.Resources> <local:GreekGods x:Key=”greekGods”/> <DataTemplate x:Key=”itemTemplate”> <TextBlock Text=”{Binding Path=Name}” /> </DataTemplate> … Continue reading “XAML Back to Basics #4: ComboBox Binding”

The next post in the series originally written by Beatriz Stollnitz. Original post available on Github.

How to bind the items of a ComboBox (and get its ComboBoxItems)

Binding the items of a ComboBox is pretty much the same as binding the items of a ListBox:

<Window.Resources>
    <local:GreekGods x:Key="greekGods"/>

    <DataTemplate x:Key="itemTemplate">
        <TextBlock Text="{Binding Path=Name}" />
    </DataTemplate>
</Window.Resources>

<ComboBox ItemsSource="{StaticResource greekGods}" ItemTemplate="{StaticResource itemTemplate}" Width="200" Name="comboBox"/>

The reason for this similarity is that both ComboBox and ListBox derive from ItemsControl, and ItemsSource and ItemTemplate are properties on ItemsControl.

If you read my previous post about how to get a ListBoxItem from a data bound ListBox, you’re probably thinking that you don’t need to keep reading to know how to do the same thing for a ComboBox. There is a little trick that you should be aware of, though.

If you use similar code to the solution of my previous post, you will notice that the ComboBoxItems are null:

GreekGod greekGod = (GreekGod)(comboBox.Items[0]);
ComboBoxItem cbi1 = (ComboBoxItem)(comboBox.ItemContainerGenerator.ContainerFromIndex(0));
ComboBoxItem cbi2 = (ComboBoxItem)(comboBox.ItemContainerGenerator.ContainerFromItem(comboBox.Items.CurrentItem));

This is because the generation of items for the ComboBox only happens when you open it. So the trick is to open the ComboBox before calling ContainerFromIndex/ContainerFromItem:

GreekGod greekGod = (GreekGod)(comboBox.Items[0]);
comboBox.IsDropDownOpen = true;
ComboBoxItem cbi1 = (ComboBoxItem)(comboBox.ItemContainerGenerator.ContainerFromIndex(0));
ComboBoxItem cbi2 = (ComboBoxItem)(comboBox.ItemContainerGenerator.ContainerFromItem(comboBox.Items.CurrentItem));
comboBox.IsDropDownOpen = false;

WPF Source Code

WPF

UWP/Uno Source Code

UWP

XAML Back to Basics #3: ListBox/ListView Binding

The next post in the series originally written by Beatriz Stollnitz. Original post available on Github. How to get a ListBoxItem from a data bound ListBox Data binding a list box to an enumeration of items could not be easier in WPF: <Window.Resources> <local:GreekGods x:Key=”greekGods”/> <DataTemplate x:Key=”itemTemplate”> <TextBlock Text=”{Binding Path=Name}” /> </DataTemplate> </Window.Resources> <ListBox ItemsSource=”{StaticResource greekGods}” … Continue reading “XAML Back to Basics #3: ListBox/ListView Binding”

The next post in the series originally written by Beatriz Stollnitz. Original post available on Github.

How to get a ListBoxItem from a data bound ListBox

Data binding a list box to an enumeration of items could not be easier in WPF:

<Window.Resources>
    <local:GreekGods x:Key="greekGods"/>
    <DataTemplate x:Key="itemTemplate">
        <TextBlock Text="{Binding Path=Name}" />
    </DataTemplate>
</Window.Resources>

<ListBox ItemsSource="{StaticResource greekGods}" ItemTemplate="{StaticResource itemTemplate}" Name="listBox"/>

The ItemsSource property of ListBox takes an IEnumerable, which is the list of items you want to display. In this case, the GreekGods data source is of type ObservableCollection, which implements IEnumerable. The ItemTemplate property specifies the DataTemplate that will be used to control how the data is displayed. In this case, we will have a TextBlock for each item that will display the GreekGod’s name.

Some of you might find surprising, however, that doing listBox.Items[i] in code returns the data we’re binding to, and not the TextBlock or the ListBoxItem. In my opinion, it is actually pretty cool that retrieving the data in a particular position of the list box is so easy, because most of the time this is exactly what you want.

GreekGod greekGod = (GreekGod)(listBox.Items[0]);

But what about when you want to have access to the actual ListBoxItem generated? This is a bit tricky to discover but can be just as easily done with the following code:

ListBoxItem lbi1 = (ListBoxItem)(listBox.ItemContainerGenerator.ContainerFromIndex(0));

There is also a listBox.ItemContainerGenerator.ContainerFromItem(object item) that returns the ListBoxItem given the corresponding data item. This method is frequently used, for example, to retrieve the ListBoxItem for the current item:

ListBoxItem lbi2 = (ListBoxItem)(listBox.ItemContainerGenerator.ContainerFromItem(listBox.Items.CurrentItem));

I will talk about selection and current item in detail in some other post, but for this sample it is sufficient to know that to keep the selection and current item in sync, I set IsSynchronizedWithCurrentItem=”true” in the ListBox.

WPF Source Code

WPF

UWP/Uno Notes

There are a couple of changes to the code for UWP/Uno:

  • Whilst the ListBox control still exists, it’s more common to use the ListView as it has a nice set of built in styles
  • The listBox.Items collection doesn’t have a CurrentItem property. Instead we can use listBox.SelectedItem
  • Attempting to set the IsSynchronizedWithCurrentItem property on the ListView (or even ListBox) throws an exception and is generally not required since the SelectedItem property is in sync with what is selected in the ListView.

UWP/Uno Source Code

UWP

XAML Note
The structure of the XAML has been left the same from the original post. However, you should really avoid embedding a ListView or ListBox within a StackPanel. This layout will limit the ability of the view to resize without the button being pushed off screen.

XAML Back to Basics #2: Binding Markup

The next post in the series originally written by Beatriz Stollnitz. Original post available on Github. What does “{Binding}” mean? Most Bindings you see in samples have the Source and Path properties set. The Source property specifies the object you’re binding to and the Path specifies a property in that object whose value you’re interested … Continue reading “XAML Back to Basics #2: Binding Markup”

The next post in the series originally written by Beatriz Stollnitz. Original post available on Github.

What does “{Binding}” mean?

Most Bindings you see in samples have the Source and Path properties set. The Source property specifies the object you’re binding to and the Path specifies a property in that object whose value you’re interested in. I’ve seen several people get confused when encountering an empty Binding for the first time – “{Binding}”. It seems at first sight that we’re not giving the Binding enough information to do anything useful. This is not true and I will explain why. If you read my previous post you should understand that it is not necessary to set a Source in a Binding, as long as there is a DataContext set somewhere up in the tree. As for the Path, it should be left out when you want to bind to a whole object, and not only to a single property of an object. One scenario is when the source is of type string and you simply want to bind to the string itself (and not to its Length property, for example).

<Window.Resources>
    <system:String x:Key="helloString">Hello</system:String>
</Window.Resources>

<Border DataContext="{StaticResource helloString}">
    <TextBlock TextContent="{Binding}"/>
</Border>

Another common scenario is when you want to bind some element to an object with several properties.

<Window.Resources>
    <local:GreekGod Name="Zeus" Description="Supreme God of the Olympians" RomanName="Jupiter" x:Key="zeus"/>
</Window.Resources>

<Border DataContext="{StaticResource zeus}">
    <ContentControl Content="{Binding}"/>
</Border>

In this case, ContentControl does not know how to display the GreekGod data. Therefore you will only see the results of a ToString(), which is typically not what you want. Instead, you can use a DataTemplate, which allows you to specify the appearance of your data.

<Window.Resources>
    <local:GreekGod Name="Zeus" Description="Supreme God of the Olympians" RomanName="Jupiter" x:Key="zeus"/>
    <DataTemplate x:Key="contentTemplate">
        <DockPanel>
            <TextBlock Foreground="RoyalBlue" TextContent="{Binding Path=Name}"/>
            <TextBlock TextContent=":" Margin="0,0,5,0" />
            <TextBlock Foreground="Silver" TextContent="{Binding Path=Description}" />
        </DockPanel>
    </DataTemplate>
</Window.Resources>

<Border DataContext={StaticResource zeus}">
    <ContentControl Content="{Binding}" ContentTemplate="{StaticResource contentTemplate}"/>
</Border>

Notice that none of the Binding statements inside the DataTemplate has a Source. That is because a DataContext is automatically set to the data object being templated.

WPF Source Code

WPF

UWP/Uno Source Code

UWP
Uno – Wasm

XAML Back to Basics #1: Data Context

Last year I posted about a series of posts that Beatriz Stollnitz made on WPF/Silverlight that had been moved to GitHub. Unfortunately most of the samples don’t work out of the box with the latest version of Visual Studio but with some minor adjustments they’re easily fixed. I took a fork of the repository and … Continue reading “XAML Back to Basics #1: Data Context”

Last year I posted about a series of posts that Beatriz Stollnitz made on WPF/Silverlight that had been moved to GitHub. Unfortunately most of the samples don’t work out of the box with the latest version of Visual Studio but with some minor adjustments they’re easily fixed.

I took a fork of the repository and have started to a) update the wpf projects to work with VS2019 b) remove the Silverlight content and c) add equivalent examples that work cross platform using UWP and the Uno Platform.

Updated Code Samples

Importantly, as I go through each of the posts I’m going to reprint the majority of the original post with edits to bring them up to date with both WPF and UWP/Uno. I want to make sure the Beatriz is recognised as the original author of this content and that I just want to make sure her contributions live on to benefit the next generation of XAML developers.

Original post available on Github

How should I decide whether to use DataContext or Source?

WPF Source Code

The DataContext is one of the most fundamental concepts in Data Binding.

The Binding object needs to get its data from somewhere, and there are a few ways to specify the source of the data. In this post I talk about setting the Source property directly in the Binding vs inheriting a DataContext from the nearest element when traversing up in the tree. The other two alternatives are setting the ElementName and RelativeSource properties in the Binding object, but I will leave that for a future post.

For example, let’s assume we have the following data sources (GreekGod being a class defined in the code behind):

<Window.Resources>
    <local:GreekGod Name="Zeus" Description="Supreme God of the Olympians" RomanName="Jupiter" x:Key="zeus"/>
    <local:GreekGod Name="Poseidon" Description="God of the sea, earthquakes and horses" RomanName="Neptune" x:Key="poseidon"/>
</Window.Resources>

<StackPanel DataContext="{StaticResource poseidon}">
    <TextBlock TextContent="{Binding Source={StaticResource zeus}, Path=Name}"/>
    <TextBlock TextContent="{Binding Path=Description}"/>
    <TextBlock TextContent="{Binding Path=RomanName}"/>
</StackPanel>

The first TextBlock inherits the DataContext from the parent StackPanel and has a Source set in the Binding object too. In this case, Source takes priority, causing the TextBlock to bind to the Name property of the resource with key “zeus” – this displays “Zeus”.

The second TextBlock does not have a Source set directly in the Binding object, so it inherits the DataContext from the StackPanel. As you might guess, this binds to the Description property of the resource with key “poseidon”, displaying “God of the sea, earthquakes and horses”.

The third TextBlock should be straightforward – it displays “Neptune”.

Most data bound applications I see from users tend to use DataContext much more heavily than Source. My recommendation is to use DataContext when you need to bind more than one property to a particular source. When binding only one property, it may be better to use the Source attribute . The reason for this is ease of debugging – I would rather see all the information about the Binding in one place than search for the nearest DataContext to understand what is going on. In a small sample like the one above there is no big advantage, but in complex applications this could save you some time.

UWP/Uno Notes

UWP/Uno Source Code

In UWP it’s not very common to reference the Windows of the application directly. Instead you can define static resources (in this case the instances of the GreekGod class) at the Application, Page or even Control level. For example, to make the static resource available throughout a given page, we would define them as Page Resources.

<Page.Resources>
    <local:GreekGod Name="Zeus" Description="Supreme God of the Olympians" RomanName="Jupiter" x:Key="zeus"/>
    <local:GreekGod Name="Poseidon" Description="God of the sea, earthquakes and horses" RomanName="Neptune" x:Key="poseidon"/>
</Page.Resources>

Here’s the same sample running on UWP.

DotNetMaui (Xamarin.Forms) is Not a XAML Platform

Yeh I know I’m going to get a ton of abuse about how this title is just click bait but before you start with the comments, hear me out. Firstly, the title is actually just missing a word DotNetMaui is Not JUST a XAML Platform In this post we’ll go through why DotNetMaui/Xamarin.Forms is/is not … Continue reading “DotNetMaui (Xamarin.Forms) is Not a XAML Platform”

Yeh I know I’m going to get a ton of abuse about how this title is just click bait but before you start with the comments, hear me out. Firstly, the title is actually just missing a word

DotNetMaui is Not JUST a XAML Platform

In this post we’ll go through why DotNetMaui/Xamarin.Forms is/is not a XAML platform and discuss the inclusive approach that the team has taken that allows for the inclusive of approaches such as those proposed by Vincent (C# markup) and James (Comet).

I want to start off by saying that Xamarin.Forms is a great cross-platform technology for rapidly building apps that work across iOS, Android, Windows (UWP), MacOS etc. At Built to Roam we continue to deliver apps for customers using this technology. DotNetMaui will continue this trend and will no doubt be a great platform for developers to build apps that work across various operating systems and devices. This post is in no way supposed to be a criticism, rather an objective look at what DotNetMaui/Xamarin.Forms is and is not. This is 100% from my perspective and I respect that other developers are entitled to their opinions (feel free to leave a comment!)

XAML as Declarative Markup

Historically, there have been various technologies/frameworks that have used XAML as the markup language. Typically, we think of the UI frameworks such as Windows Presentation Foundation (WPF), Silverlight, Windows Phone, Windows (UWP) but XAML has been used as the markup language for other non-UI technologies, for example Windows Workflow Foundation. XAML is fundamentally about declarative markup and in the case of UI frameworks it was for describing layout.

For those developers who have built, or are even still building, using Windows Forms, you’ll remember the pain of trying to coerce the designer to behave. The designer was really just a glorified code generator but the frustration came because if you attempted to modify the generated code, when you reopened the designer it would undo or break your changes. With XAML, this shouldn’t happen because, assuming it’s well-formed, it adheres to the schema…. in theory anyhow.

At this point it’s worth pointing out that in some regards XAML is literally just a way of declaring the creation of a hierarchy of objects. In fact, I’ve often added instances of non-UI classes in XAML just so that they’re available as resources that I can reference from XAML or code throughout my application. If you continue down this line of reasoning, it’s no surprise that you can indeed create your UI in markup (a proposition being peddled heavily by Vincent with his contribution to C# markup for Xamarin.Forms).

XAML Databinding

When developers think about XAML, they often package that together with MVVM and/or data binding. This is not unreasonable since the data binding framework was one of the main selling points that Microsoft would talk about when promoting any XAML technology.

Xamarin.Forms does have great support for data binding. Whilst it doesn’t support the x:Bind syntax introduced in Windows (UWP) which uses code generation to make data binding strongly typed, it does support compiled bindings making XAML quite an efficient technique for defining layout and data binding.

Here’s the question though – if the compiler is just going to convert XAML into compiled code, why don’t we just write it in C# and remove the need to learn the XAML abstraction? (i.e. back to Vincent’s point about C# markup!)

Model-View-Update (MVU)

Over the last couple of years there have been a number of new frameworks and technologies that have provided alternative strategies for updating the UI. Web frameworks such as React use a virtual DOM to deliver rendering efficiencies. More recently we’ve seen Flutter take the learnings from building the rendering engine behind Chrome and applying it as an application framework. This has lead to a lot of excitement about MVU (for the moment I’m going to ignore the argument that Flutter, SwiftUI and Comet/DotNetMaui are not MVU as proposed by the Elm architecture).

There have been several spikes on implementing MVU style frameworks for both Xamarin.Forms and Windows/UWP. All of them that I’ve seen focus on replacing XAML with C# code that declares the layout, with some smarts that only push differences to the UI rendering engine.

As Xamarin.Forms evolves to DotNetMaui we’re going to see some changes to the rendering framework. As the work by James Clancey on Comet is integrated there will be changes to the use of platform renderers. Microsoft is taking a gamble that as they make the necessary changes to Xamarin.Forms/Xamarin.iOS/Xamarin.Android/UWP to align with the .NET roadmap, they’ll take this opportunity to address some of the limitations/frustrations felt by developers with the current rendering engine.

Rendering Engine

There is no proposal to fundamentally change the way that Xamarin.Forms/DotNetMaui uses the native platform controls. The premise is that apps should use the controls that have been provided by the platform. This should encourage developers to build apps that belong on each platform. This thinking is dated, and most customers don’t care about platforms details, they just want their apps to look consistent on every platform.

This lead the team to add the Visual attribute and subsequently the Material Visual. If you’re looking to build a great looking app out of the gate, using the Material Visual is the way to go. However, you do have to ask yourself why did they need to introduce the concept of a Visual when we have styles? and can’t we just change the template of controls to change how they look?

Lookless Controls make a XAML Platform

I’ve conceded early in this post that a XAML platform is really any technology that uses XAML as a markup; thus DotNetMaui/Xamarin.Forms is indeed a XAML platform. However, when we consider what we mean by a XAML platform we typically associate it with a number of capabilities (in no particular order, and I’m sure there are other things that I’ve left off the list):

  • Styles and Resources
  • Data binding
  • Data Templates
  • Visual States
  • Control Templates

Further more I would go so far to say that one of the core principles of a XAML platform is that of Lookless Controls (this term seems to have been lost to history somewhat but was heavily used in the context of WPF and the way controls were templated). This is the ability to completely re-template a control without losing the basic functionality. This is something that has never been adopted by Xamarin.Forms and isn’t likely to emerge in DotNetMaui. In order for a platform to support the concept of Lookless Controls, each control needs to have a ControlTemplate that can be used to define both the static look and feel of the control but also the various Visual States (and transitions) of the control.

Developers considering different cross platform technologies might ask what the difference is between the DotNetMaui/Xamarin.Forms approach and that taken by the Uno Platform. Without listing all the differences, one of the core differences is the support for Lookless Controls and control templating. As ControlTemplates and Visual States are fundamental to the delivery of Lookless Controls on the Windows (UWP/WinUI) platform, they are a core part of the way that the Uno Platform renders apps on each platform.

Not JUST a XAML Platform

The last section seems like I’m being overly negative on the DotNetMaui/Xamarin.Forms platform and I must admit for the longest time I was frustrated that the team hadn’t prioritised making it easier for developers to re-template controls. Whilst I still prefer a platform where I’m in control and can change the template of controls as needed, I recognise that in the majority of cases, this isn’t required in order to deliver great looking apps.

If we look at what’s in scope for DotNetMaui we see that there inclusive approach will provide developers with many options to develop and style their application. This will make DotNetMaui a great starting point for developers wanting to build apps using the Microsoft tool chain, and of course provide a great feeder into building apps that connect and work well with Azure.

Picking Your Platform

At this point you might be asking yourself how do you decide which platform to pick for building your next cross platform application. This is a question that I ask daily and the answer is that it really depends on the situation, the customer, the development team etc. So for a minute, lets limit the scope to the Microsoft ecosystem and consider the following three options:

  • Power Apps – I haven’t talked about these in this post but if you’re looking for a minimal code platform that connects to Microsoft 365 and the Power platform, then this is your best option. It’s not really a true cross platform app platform but worth considering as it’s massively powerful for working with enterprise data and workflows
  • DotNetMaui/Xamarin.Forms – If you’re looking to rapidly deliver great looking apps where the designs are derived from the Material design language without wanting to curate animations, effects and styles, then DotNetMaui/Xamarin.Forms is the way to go. Pick the approach (eg MVVM or MVU) and language (eg C# or XAML) that suits your team.
  • Uno Platform/WinUI – If you’re looking for granular control over every aspect of your application then you can’t go past the Uno Platform. This platform is evolving at an amazing pace providing support for Web Assembly, Skia backend and much more. Each control has a ControlTemplate that you can override and customise precisely the way you want it.

Building Messages in Blend for Visual Studio

The other day Martin Zikmond tweeted about a messaging sample app he’d built using the Uno Platform, allowing the same app to run on iOS, Android, Windows, MacOS and Web. Whilst the concept was simple enough, the point was that there was almost no platform specific code and yet the app works and looks virtually … Continue reading “Building Messages in Blend for Visual Studio”

The other day Martin Zikmond tweeted about a messaging sample app he’d built using the Uno Platform, allowing the same app to run on iOS, Android, Windows, MacOS and Web. Whilst the concept was simple enough, the point was that there was almost no platform specific code and yet the app works and looks virtually the same on every platform.

Following my previous post where I did a walk through of some of the Blend for Visual Studio features I use, I thought I’d take the challenge to see how much of Martin’s app I could build using the designer in Blend. The good news is that you can get a long way; The bad news is that there are some features, such as ThemeResources, that seem to cause some issues with the designer – we’ll see this towards the end of the post.

New Project

Ok, so this is pretty self explanatory but Blend has a similar New Project experience to what you see in Visual Studio. Start by searching for the type of project you want to build. So that the designer works, we’re going to select a Blank App (Universal Windows). Once we’ve completed the design work we can copy the files across into our Uno project

Note: At the moment there’s no support for Shared Projects, which the default Uno solution is setup to use. If you follow my post on using multi-targeting with Uno you can use the designer if you load the Windows solution filter.

Give your project a name and location you want it saved.

At this point I typically make sure the application runs and that I’ve upgraded various NuGet packages. Next I’m going to copy in a bunch of the code files from Martin’s project that is available on GitHub. This includes the ViewModels, the SampleData and a couple of Assets.

In the code behind for the MainPage (MainPage.xaml.cs) I’ve added a ViewModel property that returns an instance of the MainViewModel. This will be used to provide both runtime and design time data.

Now let’s get to the designer. From the Assets tool window, search for “split” and drag the SplitView onto the design surface. The SplitView has a Pane, the collapsable panel that shows/hides when you tab the burger menu, and a main Content area. In Martin’s app the list of conversations is in the Pane, whilst the Messages for the selected conversation appear in the Content area.

I wanted to reuse the same color resources that Martin has, so I copied the contents of the App.xaml across to my project – It would be awesome if Microsoft could add back support for managing project resources, which was previously a feature of Blend but got lost in the update to the designer a couple of years ago.

Back to the designer and focus on the SplitView. From the Properties tool window locate the PaneBackground property. Select the Color Resources tab (yeh, Editor and Color Resources are a tab, not that it’s very obvious) and select the NavPaneBackgroundColor (this comes from the resources we added to App.xaml). Whilst we’re here, set the BackgroundSource property to HostBackdrop and TintOpacity to 50%. Check out the docs on Acrylic material for more information on using Windows specific brushes. This is one point where Martin has provided a different background for Windows versus the other platforms – check out the source code to see how he’s done this using platform namespaces.

When you launch the app on Windows you can see the effect of setting the Acrylic background on the pane of the splitview (left side of this image).

Next we’re going to set the Padding on the Grid located inside the Pane, which will inset the list of conversations away from the edge. Rather than just setting the Padding, which can be done using the Properties tool window, I’m going to convert this into a reusable resource. Click the square to the right of the property to display the context menu, then select Convert to New Resource.

Give the resource a name and specify where you want the resource to be saved.

In this case I’m going to go ahead and create a new Resource dictionary by clicking the New… button. Since we’ll use this resource dictionary for styles, templates and other resources, I’ve named it accordingly. When you click the Add button, not only does this resource dictionary get added to your project, it is also wired up as a merge dictionary in App.xaml.

Next we’re going to add a ListView to the Pane to display the list of conversations. Again from the Assets window, drag the ListView across onto the Pane in the designer.

Unfortunately there’s no designer support for working with x:Bind. However, you will get intellisense in the XAML editor to let you know what properties are available for binding to.

Despite setting the ItemsSource property, the ListView still remains empty on the designer – as I mentioned, no designer support for x:Bind. However, with the recently introduced design time data everywhere support that’s been added, you can take advantage of regular data binding at design time.

Firstly, we’ll add an instance of the MainViewModel as design time resource. Note that this is the same as adding normal resources, just with the “d:” prefix. However, be aware that setting attributes at design time will override any runtime values whilst in the designer. This normally isn’t an issue but when specifying resources, it will only pick up the design time resources, rather than attempting to combine the resource dictionaries.

<Grid>
    <d:Grid.Resources>
        <MainViewModel x:Key="DesignViewModel" />
    </d:Grid.Resources>

Note that the XAML editor is able to assist with applying namespaces etc. After adding the MainViewModel resource I get a prompt helping me to setup and use the namespace.

Next, set the ItemsSource property on the ListView, again using the design time prefix (i.e. d:ItemsSource=”{Binding Conversations}” ). Now we should start to see items appear in the ListView.

Let’s go ahead and create a data template for determining how each item will appear. Right-click on the ListView and select Edit Additional Templates, Edit Generated Items (ItemTemplate), followed by Create Empty. Give the DataTemplate a name and specify where you want to save it.

Rather than going through each element in the item template, I’m going to add the DataTemplate from Martin’s code. However, even after adding this data template, there’s no items appearing in the ListView. Again, this is because there’s no designer support for x:Bind. Luckily we can do the same trick, this time for each property we want to data bind. in this example we’re using the design time data binding for the Text property on the TextBlock

Ok, so now we’re starting to look good.

However, notice how the time is appearing at different positions for each item in the list. This is because the list item isn’t spanning the full width of the ListView. I’ve never understood why this is the default behaviour but it dates back as long as I can remember. Luckily there’s an easy fix, which is to set the HorizontalContentAlignment on the ListView to Stretch. However, this needs to be done on the ItemContainerStyle for the ListView. Right-click on the ListView, Edit Additional Templates and then this time select Edit Generated Item Container (ItemContainerStyle), followed by Create Empty.

Give the template a name and location for saving.

From the Properties tool window, search for “horizon” and change the HorizontalContentAlignment to Stretch (far right icon).

Now, the list of conversations is looking much better.

In the main Content area, lets start by creating three rows to host the header, messages and input TextBox. Using the mouse, you can draw the grid rows, and then use the designer to define the height for the rows. The first and third row should both be set to Auto, with the second row set to *.

Let’s add the input TextBox at the bottom of the screen. Click on the chevrons on the left edge of the design to expand out the Assets flyout. Search for textbox and double-click on the TextBox to add it to the designer.

Next, right-click on the newly created TextBox and select Layout, Reset All.

Depending on where the TextBox was added, you’ll probably have to set the Grid.Row to 2 in order for it to appear in the third row. Whilst in the Properties tool window, let’s set the CornerRadius to 12 and the Margin to 20 – again, I would typically extract these to resources so they can be reused across the app (similar to CSS classes if you’re more familiar with styling for the web).

At this point I’m going to grab the rest of the XAML for the Content area from Martin’s source code, rather than walking you through adding each of the controls. There was one exception to this – in Martin’s code he has used an ItemTemplateSelector to switch between two different item templates based on whether the message is from the current user or not. I’m going to take a different approach, so I’ve replaced the ItemTemplateSelector with just a single ItemTemplate. The resulting messages list looks like the following, where it’s no longer possible to distinguish who sent which message.

To fix this issue we’re going to use converters that take a bool value and determine what value to return. The first converter returns a HorizontalAlignment, either Left or Right, depending on the input value. I’ve set it to return Stretch if the input value isn’t a bool, effectively making it the default value.

Next we’re going to make use of this converter in the item template. Here we’ve selected the Border element and are about to click the Create Data Binding menu item.

In the Create Data Binding window, we select the IsFrom property, and then at the bottom of the window, select the BoolHorizontalAlignmentConverter.

If you run the app at this point you’ll see that the messages are aligned left and right according to who sent the message but there’s no difference in style. We can do the same process but this time for setting the Style of different elements in the template; this time using a BoolStyleConverter.

The difference with the BoolStyleConverter is that it needs a TrueStyle and FalseStyle to be defined. For example for the Border we can create two styles and then an instance of the BoolStyleConverter.

And the same for the TextBlock style

And then we apply these to the item template.

And voila, we have a similar layout to what Martin had

Ok, so just in conclusion there’s a couple of points:

  • Firstly, in the style for the Border and TextBlock it’s referencing a ThemeResource to set color. This is important so that you get support for light/dark mode. However, it also seems to break the designer support, resulting in no messages appearing on the screen. I think the resolution might be that the Styles themselves have to be defined as ThemeResources.
  • Secondly, you might be asking why I chose to use converters instead of an ItemTemplateSelector. The answer is that there isn’t any right or wrong answer, I just wanted to demonstrate a different way to appear the problem. In some cases the ItemTemplateSelector is preferred, particularly if there are massive differences between the templates. In this case, I would probably go with converters because you have a single ItemTemplate, so any changes you make will affect both From and To messages, without you having to remember to update both templates.

Hopefully you’ve seen how you can take advantage of Blend for Visual Studio to start building cross-platform experiences; start with Windows (UWP) and apply it to iOS, Android, MacOS and Web using the Uno Platform

Xamarin DevOps Snippets (aka Pipeline Templates)

Louis Matos has put together Xamarin Month with the topic of Code Snippets – Check out Louis’ blog for the full month of snippet. In this post I’m going to cover some code snippets that use Pipeline Templates in order to setup a Azure DevOps pipeline for your Xamarin application. Azure Pipeline Templates are a way … Continue reading “Xamarin DevOps Snippets (aka Pipeline Templates)”

Louis Matos has put together Xamarin Month with the topic of Code Snippets – Check out Louis’ blog for the full month of snippet. In this post I’m going to cover some code snippets that use Pipeline Templates in order to setup a Azure DevOps pipeline for your Xamarin application.

Azure Pipeline Templates are a way to define reusable components of YAML that can be shared across different pipelines and in fact across different repositories. Noting how difficult it was to setup even a basic pipeline to build and deploy a Xamarin application, the Pipeline Templates project was born to publish reusable templates that could easily be dropped into any pipeline.

Getting Started

In order to make use of templates, the first thing you need to do is to reference the pipeline_templates GitHub repository as a resource in you YAML build pipeline.

resources:
  repositories:
    - repository: builttoroam_templates
      type: github
      name: builttoroam/pipeline_templates
      ref: refs/tags/v0.6.1
      endpoint: github_connection

It’s worth noting that the ref attribute is referencing the v0.6.1 release by specifying the tag. Alternatively you could reference any branch simply by changing the ref to a value similar to “refs/heads/nickr/bugfix” where the branch is nickr/bugfix. I would recommend referencing one of the tagged releases for stability of your build pipeline – from time to time we do make breaking changes, so if you’re referencing a branch, your build might start failing.

Build Templates

Android

To build the Android application, use the build-xamarin-android template – simply provide the necessary parameters.

stages:
- template:  azure/stages/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_Android'
    build_android_enabled: true
    # Version information
    full_version_number: '1.0.$(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: 'src/SnippetXamarinApp.sln'
    solution_build_configuration: 'Release'
    # Output information
    artifact_folder: 'artifacts'
    application_package: 'android.apk'

iOS

To build the iOS application, use the build-xamarin-ios template

- template:  azure/stages/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_iOS' 
    build_ios_enabled: true
    # Version information
    full_version_number: '1.0.$(Build.BuildId)'
    # Solution to build
    solution_filename: 'src/SnippetXamarinApp.sln'
    solution_build_configuration: 'Release'
    # Signing information
    ios_plist_filename: 'src/SnippetXamarinApp/SnippetXamarinApp.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: 'artifacts'
    application_package: 'ios.ipa'

Windows

To build the Windows application, use the build-xamarin-windows template. Technically this template should work with any UWP application.

- template:  azure/stages/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_Windows'
    build_windows_enabled: true
    # Version information
    full_version_number: '1.0.$(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: 'src/SnippetXamarinApp.sln'
    solution_build_configuration: 'Release'
    # Output information
    artifact_folder: 'artifacts'
    application_package: 'windows.msix'

Deploy

Of course, once you’re done building your applications, you probably want to deploy the applications for testing. For this you can use the deploy-appcenter template. Note that you need to add a stage for each platform you want to deploy but you can use the same template as it knows how to deploy iOS, Android and Windows applications to AppCenter.

- template:  azure/stages/[email protected]_templates
  parameters:
    # Stage name and dependencies
    stage_name: 'Deploy_Android'
    depends_on: 'Build_Android'
    deploy_appcenter_enabled: true
    environment_name: 'AppCenter'
    # Build artifacts
    artifact_folder: 'artifacts'
    application_package: 'android.apk'
    # 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)

For a more detailed walk through of using the pipeline templates for building cross platform xamarin applications, please check out this post that covers the process end to end.

Call to Action: Contributions

The Pipeline Templates project is an open source project and I would love to get feedback and contributions from the community in order to provide more templates (not just for mobile).

Specifically, if anyone has built out the tasks necessary to deploy applications to each of the three stores, it would be great to create a template similar to the AppCenter template that targets the actual stores.

If anyone is familiar with both GitHub Actions and Azure Pipelines, it would be great to get someone to give me a hand to convert the existing templates for Azure DevOps across to GitHub Actions.

Consuming REST API with Swagger / OpenAPI in Xamarin and Uno Applications

I still recall the simplicity of standing up a SOAP service and adding a service reference via Visual Studio by simply entering the url to the WSDL – this scenario just worked…. until we moved on. There was a rapid progression away from the overly prescriptive XML based world of SOAP to REST based APIs. … Continue reading “Consuming REST API with Swagger / OpenAPI in Xamarin and Uno Applications”

I still recall the simplicity of standing up a SOAP service and adding a service reference via Visual Studio by simply entering the url to the WSDL – this scenario just worked…. until we moved on. There was a rapid progression away from the overly prescriptive XML based world of SOAP to REST based APIs. This was all well and good but there was no longer a standard approach to documenting these APIS. Enter Swagger, and subsequently OpenAPI, as a way to document REST based APIs. Rather than spend time in this post detailing how you can add some Swagger to your web api (look here if you’re interested), I’m going to focus on consuming a REST based API by first importing its Swagger and using that to generate the code for accessing the API.

One of the most important points to note about this process, is that we’ll be working with a .NET Standard 2.0 class library. This means that the same library can be used across any application you might want to build, whether it be a console application, or a Xamarin.Forms app, or a Uno app (go to https://platform.uno/ to learn more about the Uno Platform).

In fact, to illustrate this point, I have a solution which has both a Uno and a Xamarin.Forms application in it. The solution also has a Data project (just a regular .NET Standard 2.0 library), which is where we’ll be importing the Swagger document. The Data project is referenced by each of the head projects for both Xamarin.Forms and Uno.

The REST service that we’re going to call is one that I setup as part of generating the OCS files for Build 2020 (more details here). The swagger documentation can be found at https://build2020.builttoroam.com/swagger/index.html, which is just a pretty version of the actual Swagger/OpenAPI document that’s located at https://build2020.builttoroam.com/swagger/v1/swagger.json

The process for referencing a REST API that has a Swagger or OpenAPI is now one of the global tools that ships with the dotnet cli. High level documentation for the OpenAPI tool can be found on the Microsoft docs website. We’ll step through the process here as it’s relatively simple.

To start with, we need to make sure that the openapi tool has been installed. This can be done by running the following at a command prompt:

dotnet tool install -g Microsoft.dotnet-openapi

Next, navigate to the folder of the project you want to reference the swagger document and call the openapi command, along with the “add url” parameter (and of course the url of the swagger document).

dotnet openapi add url https://build2020.builttoroam.com/swagger/v1/swagger.json

Returning to Visual Studio we can see that a file, swagger.json, has been added to the Data project.

We’ll add a small snippet of code to call one of the REST APIs.

var client = new swaggerClient("https://build2020.builttoroam.com", new System.Net.Http.HttpClient());
var sessions = await client.SessionsAsync();
System.Diagnostics.Debug.WriteLine(sessions.Count);

This is all well and good but what happens if you want to customize the generated code….oh wait, what….generated code… I forgot to mention, as part of building the Data library, the swagger.json document that was added to your project is converted into a set of classes that make invoking the REST APIs super easy (as you saw above in the code example).

If you press F12 when the cursor is located in swaggerClient class name (or just right-click on the swaggerClient class and select Go To Definition) you’ll be taken to the generated swaggerClient.cs file (in the obj folder). Don’t make changes directly to this file as they will get overwritten the next time you build the Data project.

As I was about to say, there are a couple of ways you can customize the generated code. The first way is to adjust the code generation itself. You can do this by supplying options that configure the way the code generation is done. When the “dotnet openapi” command was invoked earlier, in addition to adding the swagger.json file to the project, an entry was added to the csproj file.

<OpenApiReference Include="swagger.json" SourceUrl="https://build2020.builttoroam.com/swagger/v1/swagger.json" />

The OpenApiReference element in the csproj invokes the code generation during the build process by calling out to the popular NSwag command line tool. As such the options that can be used are the same as for the Nswag tool (A full list of options can be found in the NSwag source code here). For example, let’s ensure an interface is generated for the swaggerClient class, we can set the GenerateClientInterfaces property to true.

<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<TargetFramework>netstandard2.0</TargetFramework>
	</PropertyGroup>
	<ItemGroup>
		<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
		<PackageReference Include="NSwag.ApiDescription.Client" Version="13.0.5" />
	</ItemGroup>
	<ItemGroup>
		<OpenApiReference Include="swagger.json" SourceUrl="https://build2020.builttoroam.com/swagger/v1/swagger.json">
			<Options>/GenerateClientInterfaces:true</Options>
		</OpenApiReference>
	</ItemGroup>
</Project>

The other way we can extend the swaggerClient class is by adding the implementation of one or more of the partial methods that has been created. Again, if you go to definition on the swaggerClient class, you can see these partial method declarations.

To implement these partial methods, simply add a partial class called swaggerClient to the project, and add the method you want to implement. For example, adding the PrepareRequest method, you can log out the actual url of each API call.

And there you have it, a super simple way to reference a REST API from a .NET Standard library, which can then be used by your Xamarin.Forms, Uno or even a console app.

Big shout out to Rico Suter for the amazing work he’s done with the NSwag library

Improving Developer Experience with Multi-Targeted Visual Studio Projects

In my previous post on using multi-targeted cross platform projects I showed how you can add additional target frameworks to allow the addition of platform specific code to a library. One of the downsides of this approach (versus perhaps using a shared project) is that the library gets built once for every target framework that’s … Continue reading “Improving Developer Experience with Multi-Targeted Visual Studio Projects”

In my previous post on using multi-targeted cross platform projects I showed how you can add additional target frameworks to allow the addition of platform specific code to a library. One of the downsides of this approach (versus perhaps using a shared project) is that the library gets built once for every target framework that’s specified. In this post I’m going to show you how you can use the combination of a simple script and Solution Filters to optimise both the time it takes to load your solution in Visual Studio and the time it takes to compile your application.

Firstly, we’re going to adjust the Core project so that we can adjust which target frameworks are include based on a solution property called TargetsToBuild. If this is set to All, we’ll simply use the TargetFrameworks we defined in the previous post (i.e. .NET Standard, iOS, Android and Windows (UAP) when building on Windows). However, if TargetsToBuild is not equal to All, we’ll set the TargetFrameworks to be just the specific target framework that we’re interested in.

<Project Sdk="MSBuild.Sdk.Extras">
	<PropertyGroup Condition=" '$(TargetsToBuild)' == 'All' ">
		<TargetFrameworks>netstandard2.0;xamarinios10;monoandroid10.0;</TargetFrameworks>
		<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">uap10.0.16299;$(TargetFrameworks)</TargetFrameworks>
	</PropertyGroup>

	<PropertyGroup Condition=" '$(TargetsToBuild)' != 'All' ">
		<TargetFrameworks Condition=" '$(TargetsToBuild)' == 'Android' ">monoandroid10.0;</TargetFrameworks>
		<TargetFrameworks Condition=" '$(TargetsToBuild)' == 'Windows' ">uap10.0.16299</TargetFrameworks>
		<TargetFrameworks Condition=" '$(TargetsToBuild)' == 'iOS' ">xamarinios10</TargetFrameworks>
	</PropertyGroup>

Note: I quite often will include netstandard2.0 in all of the TargetFrameworks to make sure the .NET Standard 2.0 build doesn’t break as we’re changing the Core library. This is a personal choice and you’ll definitely get better performance out of not having it build every time.

Ok, so the question is, where does TargetsToBuild get set. For this, we’re going to include a file called Directory.build.props in the solution folder. You can think of this as a file where you can define properties and include items and references across the entire solution. In this case, we’re going to set the contents to the following.

<Project>
  <PropertyGroup>
    <TargetsToBuildDeveloperOverride>All</TargetsToBuildDeveloperOverride>                   
  </PropertyGroup>

  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
    <TargetsToBuild>$(TargetsToBuildDeveloperOverride)</TargetsToBuild> 
  </PropertyGroup>

  <PropertyGroup Condition=" '$(Configuration)' != 'Debug' ">
    <TargetsToBuild>All</TargetsToBuild>
  </PropertyGroup>
</Project>

There’s two parts to this:

  • Firstly, if the Configuration is not set to Debug, the TargetsToBuild is always set to All. This is never modified, and is a fail safe to ensure all target frameworks are built when doing a Release build.
  • Secondly, the TargetsToBuild is set to the value held by TargetsToBuildDeveloperOverride when the Configuration is Debug. This variable is currently set to All.

The property TargetsToBuildDeveloperOverride essentially has four possible values: All, Android, iOS and Windows. Changing this value will adjust the target frameworks for the Core project. I would recommend restarting Visual Studio as the support for dynamically switching target frameworks in any given session isn’t there yet and doesn’t work 100% reliably.

The next piece to the puzzle is to use Solution Filters to limit which projects are loaded. By limiting which projects are loaded, your solution will load quicker and will also build faster (depending on what your build configuration settings are). To create a Solution Filter, simply unload the projects you don’t want loaded, then right-click on the solution node in Solution Explorer and click Save as Solution Filter. I’ve created the following solution filters:

  • MultiTargetSample.all.slnf: All projects are loaded
  • MultiTargetSample.Android.slnf: Core and Android head project loaded
  • MultiTargetSample.iOS.slnf: Core and iOS head project loaded
  • MultiTargetSample.Windows.slnf: Core and Windows head project loaded

The solution filter file, SLNF, is just a JSON file that lists which projects should be loaded. For example the MultiTargetSample.Windows.slnf file is as follows:

{
  "solution": {
    "path": "MultiTargetSample.sln",
    "projects": [
      "MultiTarget.Core\\MultiTarget.Core.csproj",
      "MultiTarget.Windows\\MultiTarget.Windows.csproj"
    ]
  }
}

The last piece of the puzzle is to combine the solution filters with the TargetsToBuildDeveloperOverride property. For example, if we want to be working on the Android application, we want to set the TargetsToBuildDeveloperOverride property to Android and then load the MultiTargetSample.Android.slnf solution filter. This is easily done using a couple of simple batch script.

Firstly, a generic script (LaunchVS.bat) that takes a parameter (the platform) and adjusts the TargetsToBuildDeveloperOverride property and then launches the appropriate solution filter.

powershell -Command "(gc Directory.build.props) -replace '[a-zA-Z]*', '%~1 ' | Out-File -encoding ASCII Directory.build.props"
start MultiTargetSample.%~1.slnf

Then, we have a script for each platform (eg LaunchVS.Windows.bat) which simply has the following command

LaunchVS.bat Windows

When you double-click on LaunchVS.Windows.bat in File Explorer, Visual Studio is launched with only the Core and Windows projects loaded. The Core library will only build the UAP target framework. This significantly improves both the solution load time and the build time, making working with MultiTargeted projects much easier.

Important: You might think that this isn’t important when you’re first starting out but I would highly recommend adding this to your solution from the get-go. It will save you hours of time both now and when your solution grows. Visual Studio does not handle large solutions with projects that have many target frameworks. Applying these changes will ensure Visual Studio continues to operate properly

Cross-Platform Libraries with Multi-Targeting for Xamarin, Uno and Windows Development

The story of cross platform development for Microsoft developers has been one marked by iteration after iteration of .NET framework fragmentation. With .NET 5 the promise is that we’ll return to having a single framework that will be used everywhere…. Sorry, what?? when did we ever have a single framework? For as long as I … Continue reading “Cross-Platform Libraries with Multi-Targeting for Xamarin, Uno and Windows Development”

The story of cross platform development for Microsoft developers has been one marked by iteration after iteration of .NET framework fragmentation. With .NET 5 the promise is that we’ll return to having a single framework that will be used everywhere…. Sorry, what?? when did we ever have a single framework? For as long as I can recall, developers working on mobile and/or IoT have always had a different framework to target (.NET CF, .NET MF, WinPhone (whatever that .NET version was called), UWP etc). However, one thing that hasn’t changed is the fact that in order to target different platforms, you inevitably have to write code that’s specific to that platform. Even during the announcement of MAUI, the new single project structure has specific files/code etc for iOS, Android, Windows etc. This problem isn’t going away, so let’s discuss how we deal with this issue today.

The scenario I’m going to cover in the post is relatively simple. I have three “head” or “target platform” projects (Windows, Android, iOS) that are going to reference a common library (Core). Whilst most of the Core library is common code that is compatible with .NET Standard 2.0, there’s some code that is specific to each platform. Rather than create a separate platform specific library, we can include this specific logic by setting up the Core library to be multi-targeted – meaning that a different DLL is generated for each supported platform. Luckily Visual Studio knows how to handle this, so you’re not left working out how to reference each DLL from the head projects.

Project Setup

Let’s get started. First up, we’ll create a blank solution. If you start with one of the head projects your solution will end up being called something list XXX.Windows.

Search for Solution and select Blank Solution
Name your solution

Next up, let’s create the three head projects: Windows (UWP), Android and iOS

Search for UWP and select the Blank App (Universal Windows) template
Name you Windows head project – I like to use the Windows suffix but uwp is also often used
Search for droid and select the Android App (Xamarin) template
Name your Android head project
Search for iOS and select the iOS App (Xamarin) template
Name your iOS head project

At this point, if you’re working on a Mac, you could also add a MacOS project template. For some reason this template isn’t available in Visual Studio for Windows (because no developer would ever want to create the MacOS project when working on windows …. I guess the same (lack of) logic is used on VS for Mac where you can’t create a UWP project).

Next up, we’ll create the common library that each of the head projects will reference.

Search for .net standard and select the Class Library (.NET Standard) template
Name the .NET Standard library

Common Code

So that we can see how this library will work, we’ll simply expose a string constant that can be referenced and displayed in each head project. We’ll rename the Class1.cs file to AppProperties.cs and allow Visual Studio to rename the corresponding class. We’ll then add a string constant as follows:

namespace MultiTarget.Core
{
    public class AppProperties
    {
        public const string AppName = "MultiTarget Sample";
    }
}

In each of the head projects we’ll add an text/label element to the UI and set the value to be AppProperties.AppName. Rather than having to use the Add Reference workflow (i.e. right click on the head project and click Add Reference), we’ll simply type the AppProperties class name and use the prompt in the code editor to Add reference to the MultiTarget.Core project.

Type Ctrl+. to get the code editor suggestions to appear.

Now we can run each of the head projects and see the same output on the screen which should say “MultiTarget Sample” (I don’t have an iOS device/simulator handy, so I don’t have a screenshot of it running on iOS).

Android – MultiTarget Sample
Windows – MultiTarget Sample

Platform Specific Code

The next step is to add platform specific code. In this case we’re going to augment the existing AppProperties class by adding a platform specific partial class. In Solution Explorer we can copy the AppProperties.cs and paste it into the same project. The copied file can then be renamed based on the platform. For example (see image below) the Windows specific code would be in a file called AppProperties.windows.cs.

Note: The actual name of the platform specific files doesn’t have any intrinsic meaning. As we’ll see in a minute, we will use the platform specific suffix to define which files will be included for each platform.

Inside the platform specific file we’ll add another constant called PlatformName. For example, the AppProperties.windows.cs includes a PlatformName constant that returns “Windows.”

using System;

namespace MultiTarget.Core
{
    public partial class AppProperties
    {
        public const string PlatformName = "Windows";
    }
}

We’re also going to update the AppName constant in the AppProperties.cs file to include the PlatformName. We also needed to add the partial keyword to the class definition.

namespace MultiTarget.Core
{
    public partial class AppProperties
    {
        public const string AppName = "MultiTarget Sample - " + PlatformName;
    }
}

After adding files for each platform, if you attempt to build the Core project it will fail with an error indicating that there are multiple definitions of the PlatformName constant. What we need to do is to only include one platform specific file at a time.

MultiTarget Project

Currently the Core project is configured with a single TargetFramework, netstandard2.0. This means that the only API that can be accessed are those defined by netstandard2.0. If we want to include platform specific code, or want to access platform specific APIs, we need to change the project to target multiple frameworks. Here’s the current project XML for the Core library

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
</Project>

In order to target multiple frameworks, we can just change the TargetFramework element to TargetFrameworks, and add multiple frameworks, semicolon delimited (eg <TargetFrameworks>netstandard2.0;net451</TargetFrameworks> ). However, out of the box, this doesn’t support the various target framework monikers that are used across different platforms. To overcome this limitation, we need to change the Sdk attribute from “Microsoft.NET.Sdk” to “MSBuild.Sdk.Extras”. We also need to create a file called global.json in the solution folder with the following version information for the MSBuild.Sdk.Extra nuget package:

{
    "msbuild-sdks": {
        "MSBuild.Sdk.Extras": "2.0.54"
    }
}

Note: Alternatively you can specify the version number in the Sdk attribute (eg Sdk=”MSBuild.Sdk.Extras/2.0.54″ ).

With these changes made, we can update the project file to include multiple target frameworks:

<Project Sdk="MSBuild.Sdk.Extras">
	<PropertyGroup>
		<TargetFrameworks>netstandard2.0;xamarinios10;monoandroid10.0;</TargetFrameworks>
		<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">uap10.0.16299;$(TargetFrameworks)</TargetFrameworks>
	</PropertyGroup>
</Project>

Note: we’ve included a conditional TargetFrameworks element so that the Windows (i.e. UWP) target platform is only include if the host OS is Windows. This is important because the UWP platform can’t be built using Visual Studio for Mac, so making this target framework conditional on the OS ensures the project can be loaded and built using Visual Studio for Mac.

At this point it’s worth noting that by including additional target frameworks, you’re effectively increasing the number of steps in the build. For example, when we build the Core library, we see that four DLLs are built – effectively increasing the built time by four times!!!

The new project format includes all files nested in the project folder by default. This means that we need to change the default behaviour for the platform specific files so that they’re excluded by default. Then we can specifically include files for each platform that is built. The project file now looks like this:

<Project Sdk="MSBuild.Sdk.Extras">
	<PropertyGroup>
		<TargetFrameworks>netstandard2.0;xamarinios10;monoandroid10.0;</TargetFrameworks>
		<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">uap10.0.16299;$(TargetFrameworks)</TargetFrameworks>
	</PropertyGroup>

	<ItemGroup>
		<Compile Remove="**\*.netstandard.cs" />
		<Compile Remove="**\*.droid.cs" />
		<Compile Remove="**\*.ios.cs" />
		<Compile Remove="**\*.windows.cs" />
	</ItemGroup>

	<ItemGroup Condition=" $(TargetFramework.StartsWith('netstandard')) ">
		<Compile Include="**\*.netstandard.cs" />
	</ItemGroup>

	<ItemGroup Condition=" $(TargetFramework.StartsWith('xamarinios')) ">
		<Compile Include="**\*.ios.cs" />
	</ItemGroup>

	<ItemGroup Condition=" $(TargetFramework.StartsWith('monoandroid')) ">
		<Compile Include="**\*.droid.cs" />
	</ItemGroup>

	<ItemGroup Condition=" $(TargetFramework.StartsWith('uap')) ">
		<Compile Include="**\*.windows.cs" />
	</ItemGroup>
</Project>

And that’s it – building the Core library will result in four DLLs, each one having a different value for PlatformName, and thus AppName. We didn’t need to make any changes to the head projects.

This technique for including platform specific code in a common library is used by popular frameworks such as ReactiveUI and MvvmCross, as well as Microsoft libraries such as Xamarin Essentials.

Source code

Microsoft Build 2020: Uno Platform + WinUI = Future of Cross Platform Apps

I’ve been a long time supporter of the work that the team at Uno Platform have been doing. The announcement today of v3.0 of the Uno Platform comes hot on the heals of releases 2.3 and 2.4 that introduced AndroidX and MacOS support. This isn’t just any old announcement, v3 aligns the Uno Platform with … Continue reading “Microsoft Build 2020: Uno Platform + WinUI = Future of Cross Platform Apps”

I’ve been a long time supporter of the work that the team at Uno Platform have been doing. The announcement today of v3.0 of the Uno Platform comes hot on the heals of releases 2.3 and 2.4 that introduced AndroidX and MacOS support. This isn’t just any old announcement, v3 aligns the Uno Platform with the preview of WinUI 3.

At Built to Roam we’ve been building XAML based applications across a variety of technologies: WPF, Silverlight, WinPhone/Win8, UWP. Each iteration of Windows 10, new features were added to UWP but this limited the reach of the application to devices running the latest version of Windows 10. With WinUI 3.0 Microsoft is detaching the XAML platform away from the Windows version – this means that WinUI can iterate faster and bring newer controls and features to developers. In addition, WinUI also brings with it support for Win32 based applications – more on WinUI here.

So, why is it important that Uno Platform supports WinUI 3.0? Well, if you think of WinUI extending UWP to support all Windows 10 devices, then the addition of the Uno Platform takes WinUI across iOS, Android, macOS and Web via WebAssembly #WinUIEverywhere.

For more information, check out the Announcement of Uno Platform 3.0

Pipeline Templates: Templates for Downloading and Caching

Earlier this year I published a pre-release version of an Azure DevOps extension that wrapped the ApkTool for extracting and re-packing Android APK files. Having spent more time working with Azure Pipelines, I realised that most of what I was doing with the extension could actually be done with one or more templates. Today I … Continue reading “Pipeline Templates: Templates for Downloading and Caching”

Earlier this year I published a pre-release version of an Azure DevOps extension that wrapped the ApkTool for extracting and re-packing Android APK files. Having spent more time working with Azure Pipelines, I realised that most of what I was doing with the extension could actually be done with one or more templates. Today I started the process of creating the templates… and unfortunately got side tracked in over engineering a couple of the templates. The upshot is that the Pipeline Templates library now has the following new templates (there’s no release yet but you can reference them off the master branch if you want to use them today).

  • Download-Url-To-File – As the name suggests this template can be used to download a url (source_url) to a specified file (target_path). The target_path parameter can be an absolute or relative path, and you can specify the overwrite_existing parameter if you want to force the url to be downloaded even if the file already exists.
  • Check-File-Exists – Again, the name says what’s in the box – this template checks to see if the specified file (path_to_check) exists. The response can either be an output variable (specified using the file_exists_variable parameter) or an error can be thrown (which will break the current build)
  • Download-And-Cache – Rather than having to download files every time a build is run, the files can be cached and reloaded for each build. This template makes this easy.
  • ApkTool-Install – This downloads and caches the two files required to run the apktool (apktool.jar and apktool.bat) on Windows.

Once I get to the point where I’m able to replicate the functionality of the Apktool extension I’ll publish these updates as a new release. In meantime, if there are any other templates you’d like to see, feel free to create an issue on the Pipeline Templates GitHub repo, or drop me a comment

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: 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.