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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Pipeline Templates: Runtime Parameters in Azure DevOps Pipelines

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

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

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

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

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

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

Another useful feature in the Run dialog is the Resources section

The Resources section lists referenced templates.

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

Pipeline Templates v0.6.0

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

Breaking Changes:

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

Other Changes:

  • none

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

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

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

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

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

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

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

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

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

Step through the iTunes installer.

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

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

Next, sign into your Apple Developer account.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Pipeline Templates v0.5.2

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

Breaking Changes:

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

Other Changes:

  • none

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

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

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

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

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

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

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

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

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

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

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

Xamarin.Forms – Which tools do you use?

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

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

Resharper

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

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

MFractor

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

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

XamRight

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

XamlStyler

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

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

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

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

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

Secure Files

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

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

Variable Groups

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

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

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

Pipeline

Here’s the entire pipeline:

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

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


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

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

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

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


Pipeline Templates v0.5.0

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

Breaking Changes:

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

Other Changes:

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

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

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

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

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

Creating a Uno Application

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

Next, enter a name and location for your new application

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

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

Build Configuration

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

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

Building Uno Applications

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

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

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

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

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

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

Android – Build

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

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

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

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

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

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

Android – Deploy

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

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

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

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

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

The other properties you need to check are:

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

iOS – Build

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

certificate

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

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

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

Application Identifier

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

Select App IDs and then Continue

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

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

Provisioning Profile

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

Select the Ad Hoc option under Distribution and then Continue.

Select the App ID that you previously created, then Continue

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

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

Give the provisioning profile a name and then click Generate.

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

Ok, we’re now ready for the build stage

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

iOS – Deploy

The deploy stage is relatively simple

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

Windows – Build

The build stage for Windows (UWP) is

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

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

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

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

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

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

Windows – Deploy

The deploy stage for Windows (UWP) is

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

Pipeline Templates v0.3.0

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

Breaking Changes:

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

Other Changes:

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

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

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

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

Pipeline Templates v0.2.0

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

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

Deploy to AppCenter

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

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

Using the AppCenter Template

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

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

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

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

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

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

Manual Approval with Environments

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

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

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

Create an Azure Pipelines Environment

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

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

Adding Manual Approval to the Environment

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

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

Select Approvals, and then click Next

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

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

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

Running the Build and Deploy Pipeline

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

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

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

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

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

Summary

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

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

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

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

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

Background

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

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

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

Pipeline Templates

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

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

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

Creating the Azure Build Pipeline

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

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

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

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

Connecting to a GitHub Resource

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

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

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

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

Replace the contents of the yaml file with the following:

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

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

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

Xamarin.Forms Build Pipeline

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

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

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

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

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

Full Example

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

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

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

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

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

Summary

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

Multiple Environments Using ApkTool Extension for Azure DevOps

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

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

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

Build the Android APK

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

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

Release Pipeline

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

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

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

My release pipeline looks like the following.

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

Unpack with the ApkTool Task

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

Updating App Configuration

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

Packing with the ApkTool Task

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

Signing and Distributing to AppCenter

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

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

Multiple Environments

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

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

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

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

Scripting the Repacking of Android Apps for a Release Pipeline

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

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

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

Repacking and Resigning an Android APK to Target Different Environments

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

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

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

Basic App Structure

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

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

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

    loadConfig(context);
  }

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

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

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

Release Ready APK

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

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

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

Repackaging to Change App Configuration

The basic process we want to follow is:

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

Unpacking the APK

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

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

   apktool d app-release.apk -o extracted_apk

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

Modify the Config.txt

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

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

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

Repack the APK

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

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

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

Sign the APK

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

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

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

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

Repackaging Apps During Release Process

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

Visual State Management with BuildIt.States and Uno

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

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

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

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

Thinking about Visual States

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

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

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

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

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

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

LoadingStates

  • NotLoading
  • Loading

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

DataStates

  • NoData
  • Data
  • DataFailedToLoad

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

Visual States in XAML

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

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

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

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

Testing the Visual States

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

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

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

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

ViewModel States

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

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

public enum LoadingStates
{
    Base,
    NotLoading,
    Loading
}

public enum DataStates
{
    Base,
    Data,
    DataFailedToLoad
}

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

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

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

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

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

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

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

    public MainPage()
    {
        this.InitializeComponent();

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

Connecting ViewModel to Visual States

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

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

    public MainPage()
    {
        this.InitializeComponent();

        DataContext = new MainViewModel();

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

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

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

How to Support Multiple Environments in your Mobile Application?

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

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

Configuration Files

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

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

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

Packaged Configuration Files

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

Build Configuration Constants

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

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

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

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

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

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

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

Copy and Replace

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

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

Mobile Build Tools

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

Build v Release Tasks for Multiple Environments

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

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

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

Running Android Device Tests for Xamarin.Essentials on Windows

As a developer working with Xamarin or Xamarin.Forms you should be aware of the Xamarin.Essentials package that Microsoft have been developing that “provides developers with cross-platform APIs for their mobile applications”. If you haven’t taken the time to look through the source code, it’s well worth cloning the repository and taking a look. Not only … Continue reading “Running Android Device Tests for Xamarin.Essentials on Windows”

As a developer working with Xamarin or Xamarin.Forms you should be aware of the Xamarin.Essentials package that Microsoft have been developing that “provides developers with cross-platform APIs for their mobile applications”. If you haven’t taken the time to look through the source code, it’s well worth cloning the repository and taking a look. Not only is this a great example of how to do multi-targeting, they’ve also taken the time to invest in device specific tests. In this post we’re going to look at how to run the device tests for Android in the same way that it’s run as part of the Azure Pipelines build script.

The first thing to do is of course clone a copy of the repository and then launch the solution in Visual Studio – before we try to run the device tests we’d better make sure everything is able to be built and run. Unless you’ve been using the same machine for a long time, you’re unlikely to have all the difference MonoAndroid SDKs installed. When you take a look at the Dependencies for Xamarin.Essentials in the Solution Explorer window it’s likely to look similar to what mine did.

The good news is that if you force a build, some of these are likely to go away. For example I actually did have 8.0 and 8.1 installed, so after the build they were resolved and the warning indicators disappeared. Unfortunately, the bad news is that I still had a couple of missing versions.

Luckily this is easily resolved using the Android SDK Manager (launch this from Tools > Android > Android SDK Manager from within Visual Studio), where you can install the missing SDK Platforms.

You’ll need to accept the license terms.

Installing the missing Android SDKs should resolve the Android dependencies. If you’re still seeing a warning for the UAP dependency, you need to verify that you’ve got the Windows SDK v 10.0.16299 installed.

The build process for Xamarin.Essentials is driven from the azure-pipeline.yml file in the root of the repository. This YAML file defines various jobs, one of which is the devicetests_android, which as the name suggests, runs the device tests. Actually this job invokes build.sh, specifying the test-android-emu target. The build.sh is actually just a proxy for running build.cake.

I wanted to run the device tests on Windows, so I’m going to be invoking build.ps1, which is the Powershell equivalent for invoking build.cake. The following command invokes Powershell, passes the build.ps1 as the Powershell script to invoke, and then I’ve included the other parameters that were specified in the azure-pipeline.yml.

powershell -f build.ps1 --target=test-android-emu --settings_skipverification=true --verbosity=diagnostic

Note: You need to invoke this command from the DeviceTests folder.

Unfortunately, simply running the Powershell script from a command prompt isn’t sufficient. There are a few steps you’ll need to jump through in order to get the script to run correctly. Without making any changes, if you run the Powershell script you’ll most likely see an error similar to the following image.

Whilst the error is highlighted in red, the actual cause of the error is in light grey font and indicates that JAVA_HOME hasn’t been defined. Easily fixed by using the set command (make sure the path to the JDK matches where it is on your computer).

set JAVA_HOME=C:\Program Files\Android\Jdk\microsoft_dist_openjdk_1.8.0.25

Also, whilst we’re setting environment variables, check that ANDROID_HOME and ANDROID_SDK_ROOT are set to the root folder of the Android SDK installation (typically C:\Program Files (x86)\Android\android-sdk on Windows if installed via Visual Studio installer)

Suggestion: You’ll find that invoking the Powershell script can be quite time consuming because Xamarin.Essentials has to be built (once for each supported platform) and then the emulator needs to be created and launched. To accelerate this process, after running the script the first time, you can temporarily disable the build by commenting out the “.IsDependentOn("Build-Android")” line in the build.cake file.

Now, when you run the Powershell script, you’re likely to see a different error. Again, it’s not the highlighted error that contains the useful information. Looking a few lines earlier you can see that there is an error relating the system image that the script attempts to use.

Again, fixing this issue is relatively easy. You just need to pick one of the existing system images (listed alongside the error) and use it to set the ANDROID_EMU_TARGET variable.

set ANDROID_EMU_TARGET=system-images;android-29;google_apis_playstore;x86

The next error you’ll see is at the point where the script attempts to launch the emulator. It will fail, indicating that it can’t find the file specified.

It would appear that the build script, build.cake, attempts to call “emulator.bat”. However, this file doesn’t exist. Instead, there is an emulator.exe – we just need to adjust the build.cake to use .exe instead of .bat when locating the emulator command.

Unfortunately there’s also an issue with the search logic, resulting in using a copy of emulator.exe that doesn’t work. After making the above change to .exe, you’ll see that it attempts to launch the emulator but comes up with an error “PANIC: Missing emulator engine program for ‘x86’ CPU”.

Again, this issue is easily fixed with a small change to build.exe to get it to search for the emulator.exe in the correct folder. In the following code, we’ve adjusted the search logic to look in only the emulator folder on Windows.

if (ANDROID_HOME != null) {
        var andHome = new DirectoryPath(ANDROID_HOME);
        if (DirectoryExists(andHome)) {
            if(IsRunningOnWindows()){
                emulatorPath = MakeAbsolute(andHome.Combine("emulator").CombineWithFilePath("emulator" + emulatorExt)).FullPath;
                if (!FileExists(emulatorPath))
                    emulatorPath = "emulator" + emulatorExt;
            }
            else{
                emulatorPath = MakeAbsolute(andHome.Combine("tools").CombineWithFilePath("emulator" + emulatorExt)).FullPath;
                if (!FileExists(emulatorPath))
                    emulatorPath = MakeAbsolute(andHome.Combine("emulator").CombineWithFilePath("emulator" + emulatorExt)).FullPath;
                if (!FileExists(emulatorPath))
                    emulatorPath = "emulator" + emulatorExt;
            }
        }
    }

Finally, when you run the script now, it will run through without error. However, you will see a Windows Security Alert requesting a rule be added to the firewall. You’ll need to click the Allow access button in order for the emulator to talk back to the build script – it uses a TCP listener to retrieve the results from the device tests.

After granting permissions, you should see the following output – if you want more details on the device test output, there’s an xml file that is returned which has the full details of the test execution.

One last comment: If you have the emulator running when you run the device test script, you’ll find that it generates an error at the end of running the script. When the script attempts to launch the emulator, emulator.exe will detect the running emulator and will exit immediately. At the end of the script it attempts to terminate the emulator.exe process – since this process has already ended, it throws and error. You can ignore this error as the device tests will still have executed correctly.

Do Uno Mvvm?

Last week was a huge week for the Uno platform with their inaugural Uno conference, #UnoConf. As the technology continues to mature, I’ve no doubt that Uno will become a viable solution for building applications to target all sorts of markets. This includes support being progressively added by the various Mvvm frameworks. Following my previous … Continue reading “Do Uno Mvvm?”

Last week was a huge week for the Uno platform with their inaugural Uno conference, #UnoConf. As the technology continues to mature, I’ve no doubt that Uno will become a viable solution for building applications to target all sorts of markets. This includes support being progressively added by the various Mvvm frameworks.

Following my previous posts (MVVM Navigation with Xamarin.Forms Shell and MVVM Navigation with Xamarin.Forms Shell – Part II) where I discussed a simple approach to Mvvm with Xamarin.Forms, I figured I’d so something similar with Uno.

Mvvm with Uno

Let’s get on with it and start with a new Uno project – Download and install the Uno Platform Solution Templates extension from the Visual Studio marketplace, if you haven’t already. In Visual Studio, create a new project based on the Cross-Platform App (Uno Platform) project template. I’m going to call the app DoUnoMvvm.

Creating a Class Library

We’re going to separate out our viewmodels and services into a separate library, so add a new project, DoUnoMvvm.Core, based on the Class Library (.NET Standard) project template. Delete the class1.cs and then add a reference to the class library to each of the head projects (i.e. Droid, iOS, UWAP and Wams).

Adjusting NuGet Package References

Right-click on the solution node in the Solution Explorer window and select Manage NuGet Packages for Solution. Go to the Updates tab, check the Include prerelease option and then check the box alongside the packages Uno.Wasm.Bootstrap, Uno.UI, Microsoft.NETCore.UniversalWindowsPlatform and Uno.Core (don’t check either the Logging packages). Click Update to get the latest version of the packages that are checked.

From the Browse tab on the NuGet-Solution window used in the previous step, enter BuildIt.General.Uno into the search box. Select BuildIt.General.Uno and install the packages into all five of the projects.

Mvvm Basics with ViewModelLocator

Now we should be ready to start writing some code. We’re going to keep it simple with the following steps:

  • Create ViewModelLocator class – used for serving up viewmodels and creating services as required
  • Create an instance of ViewModelLocator in App Resources, making it accessible as a static resource in XAML
  • Create MainViewModel class – the viewmodel for the existing MainPage
  • Update ViewModelLocator with a property Main that returns an instance of the MainViewModel class
  • Set the DataContext of the MainPage to use the Main property on the ViewModelLocator
  • Run the application and show data is being served by the MainViewModel.

Here we go…. firstly a new ViewModelLocator class, which is added to the DoUnoMvvm.Core project

public class ViewModelLocator
{
}

Update App.xaml to create an instance of the ViewModelLocator class

<Application
    x:Class="DoUnoMvvm.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:DoUnoMvvm"
    xmlns:core="using:DoUnoMvvm.Core"
    RequestedTheme="Light">
  <Application.Resources>
    <core:ViewModelLocator x:Key="ViewModelLocator" />
  </Application.Resources>
</Application>

Now to create the MainViewModel, also in the DoUnoMvvm.Core project. We’ll create a property, WelcomeText, that will return some data to be displayed on MainPage.

public class MainViewModel
{
    public string WelcomeText => "How well do Uno Mvvm?";
}

We need to update the ViewModelLocator class to include the Main property

public class ViewModelLocator
{
    public MainViewModel Main => new MainViewModel();
}

And use this property when setting the DataContext for MainPage. I’ve also updated the TextBlock to be data bound to the WelcomeText property.

<Page
    x:Class="DoUnoMvvm.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:DoUnoMvvm"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    DataContext="{Binding Main, Source={StaticResource ViewModelLocator}}"
    mc:Ignorable="d">

  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <TextBlock Text="{Binding WelcomeText}" Margin="20" FontSize="30" />
  </Grid>
</Page>

Run the application and there we have it, our first data bound page

Quick Navigation using Event Mapping

That’s pretty much the basics of Mvvm. However, following my previous posts discussing navigation, I just want to demonstrate how to abstract navigation away from both the page and the viewmodel – this allows for more independent testing of viewmodels as there’s no interdependency between viewmodels. Here’s the basic process:

  • Add a new page, SecondPage, that we’re going to navigate to
  • Add a corresponding viewmodel, SecondViewModel, and property, Second, on the ViewModelLocator
  • Update SecondPage to set the DataContext to be bound to the Second property on the ViewModelLocator
  • Add a Button to MainPage that invokes a method, Next, on the MainViewModel
  • Add an event, Complete, to MainViewModel, and raise it from the Next method.
  • Add a mapping to the App.xaml.cs that navigates to SecondPage when the Complete method is raised.

And here’s the code. I’m not going to show you the initial SecondPage as it’s just generated from the template and you’ll see it later anyhow. Instead, we’ll jump to the SecondViewModel (if you’re following along you still need to add the SecondPage to the DoUnoMvvm.Shared project in the Pages folder).

public class SecondViewModel
{
    public string ProgressText => "Now you know how to navigate....";
}

Add the Second property to the ViewModelLocator

public class ViewModelLocator
{
    public MainViewModel Main => new MainViewModel();
    public SecondViewModel Second => new SecondViewModel();
}

Now back to the SecondPage and I have set the DataContext and bound the TextBlock.

<Page
    x:Class="DoUnoMvvm.Shared.Pages.SecondPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:DoUnoMvvm.Shared.Pages"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    DataContext="{Binding Second, Source={StaticResource ViewModelLocator}}"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
  <Grid>
    <TextBlock Text="{Binding ProgressText}" />
  </Grid>
</Page>

Now a Button to invoke the transition from MainPage to SecondPage

<Page
    x:Class="DoUnoMvvm.MainPage" ...
    DataContext="{Binding Main, Source={StaticResource ViewModelLocator}}" >
  <StackPanel>
    <TextBlock Text="{Binding WelcomeText}" Margin="20" FontSize="30" />
    <Button Content="Go to Second Page" Click="GoNextClick" />
  </StackPanel>
</Page>

Here we’re simply using a code behind but you could easily use a command. Unfortunately x:Bind doesn’t appear to be working with Uno yet, so you can’t simply bind the Click method to a method on the viewmodel.

public void GoNextClick(object sender, RoutedEventArgs e)
{
    (DataContext as MainViewModel)?.Next();
}

The Next method simply raises the Complete event

public class MainViewModel
{
    public event EventHandler Complete;

    public string WelcomeText => "How well do Uno Mvvm?";

    public void Next()
    {
        Complete?.Invoke(this, EventArgs.Empty);
    }
}

The final step is to add the mapping to App.xaml.cs to define what happens when the Complete event is triggered on the MainViewModel. Add the following property and method to App.xaml.cs, and update the App class to implement the IApplicationWithMapping interface (which comes from the BuildIt.General.Uno library that you should have added earlier)

public IDictionary<Type, IEventMap[]> Maps { get; } = new Dictionary<Type, IEventMap[]>();
private void MapPageTransitions()
{
    Maps.For<MainViewModel>()
        .Do(new EventHandler((s, args) => (Windows.UI.Xaml.Window.Current.Content as Frame)?.Navigate(typeof(SecondPage))))
        .When((vm, a) => vm.Complete += a, (vm, a) => vm.Complete -= a);
}

Invoke the MapPageTransitions method immediately after the Window.Current.Content property has been set equal to a new Frame. In order for the events to get correctly wired up you also need to update both MainPage and SecondPage to inherit from the MappingBasePage class.

Now when you run the application, MainPage will appear with a Button that you can click to navigate to the SecondPage.

Uno How to Mvvm!

You might be thinking…. you’ve just shown me how to do a bunch of UWP code… and that is EXACTLY the point. If you switch to the Droid or iOS or Wasm target, you can run the same application on each of those platforms with NO further code changes required. The Uno platform is about leveraging the skills you already have as a UWP (or as a Xamarin.Forms) developer, allowing you to build rich, high-quality applications for iOS, Android and Web.

Link to the source code

Creating a Flutter App for Web

I’ve covered this topic previously in my post Create, Build and Publish a Flutter Web App but things have changed a little now as web support has been merged, making it easier to build a single application that runs on iOS, Android and Web. To find out more you can check out the Flutter docs … Continue reading “Creating a Flutter App for Web”

I’ve covered this topic previously in my post Create, Build and Publish a Flutter Web App but things have changed a little now as web support has been merged, making it easier to build a single application that runs on iOS, Android and Web. To find out more you can check out the Flutter docs for web and Building a web application with Flutter. In this post, I’m going to cover my experience and talk a little about the debugging experience in Visual Studio Code.

With the release of Flutter 1.9 I took a read through the announcement from the Google Developer Days in China and I was initially a little thrown because it indicated that Flutter’s web support has been integrated into the main Flutter repository. I assumed this meant that if I upgraded to 1.9 I would be able to immediately create a Flutter app that targets the mobile and web platforms.

Switching Channel

After upgrading to 1.9 and trying to create a new Flutter project, I quickly realised that I had misunderstood the announcement and that I still needed to switch to a different channel in order to get the integrated experience for Flutter web. So I ran the following command

flutter channel master

After switching to the master channel, I ran:

flutter doctor
flutter upgrade
flutter config --enable-web

New Flutter Project With Web

Once you’ve run these methods, the next thing to do is to create a new Flutter project in Visual Studio code. Press Ctrl+Shift+P to bring up the Command Palette, type Flutter and click Flutter: New Project. After your project is created you’ll notice the addition of a web folder.

Launching Flutter for Web

When you go to launch your Flutter app from within Visual Studio Code you’ll need to decide whether to launch on iOS or Andoird, or the newly supported web. In the lower right corner of Visual Studio Code you can see the current debugging target – in this case it’s already set to use Chrome.

If you tap the debugging target you’ll be prompted to pick an alternative debugging target.

After setting the debugging target, if you press F5 your Flutter app will launch on the appropriate device or emulator. Note that since web support is in preview, there’s no step-through debugging support; you can set breakpoints etc, you just can’t step through the code and inspect variables.

Summary

This was just a short post to draw your attention to the need to switch to the master branch in order to try out the Flutter for web support.

MVVM Navigation with Xamarin.Forms Shell – Part II

Following my previous post on Mvvm Navigation with Xmarin.Forms Shell there were a few things that I felt I hadn’t addressed adequately. Loading Data on Appearing The first thing is how to load data when navigating to a page, and perhaps to do something when the user navigates away from the page. Since it’s the … Continue reading “MVVM Navigation with Xamarin.Forms Shell – Part II”

Following my previous post on Mvvm Navigation with Xmarin.Forms Shell there were a few things that I felt I hadn’t addressed adequately.

Loading Data on Appearing

The first thing is how to load data when navigating to a page, and perhaps to do something when the user navigates away from the page. Since it’s the responsibility of the viewmodel to provide the data (i.e. via data binding), we have to invoke a method on the corresponding viewmodel when arriving at a page. In Xamarin.Forms the navigation to/from a page invokes the OnAppearing and OnDisappearing methods, which we can use to request that the viewmodel loads data.

The simplest approach is that for each page that needs to load data, the developer can override the OnAppearing method and simply call a method, for example LoadData, on the corresponding viewmodel. Since most pages are likely to have to load some data, this will quickly become a bit of a drag and something we can easily optimise. We’ll introduce an interface, INavigationViewModel, that when implemented by a viewmodel will define methods OnAppearing and OnLeaving. Then in our BasePage (which we introduced in the previous post) we simply need to check to see whether a viewmodel implements the interface before invoking the appropriate method.

public class BasePage : ContentPage
{
    protected override void OnAppearing()
    {
        base.OnAppearing();

        var vm = this.BindingContext as BaseViewModel;
        if (vm!=null && AppShell.Maps.TryGetValue(vm.GetType(), out var maps))
        {
            foreach (var map in maps)
            {
                map.Wire(vm);
            }
        }

        (vm as INavigationViewModel)?.OnAppearing();
    }

    protected override void OnDisappearing()
    {
        base.OnDisappearing();

        var vm = this.BindingContext as BaseViewModel;
        if (vm!=null && AppShell.Maps.TryGetValue(vm.GetType(), out var maps))
        {
            foreach (var map in maps)
            {
                map.Unwire(vm);
            }
        }

        (vm as INavigationViewModel)?.OnLeaving();
    }
}

Note: If you’re going to be adapting some of this code for your project, you might want to consider making OnAppearing/OnLeaving return a Task that can be awaited.

Passing Parameter

The next point that we need to cover is how to pass a parameter from one page to the next. In our example, we have a list of items and when the user taps on an item the app navigates to the ItemDetailPage to view the details of the item. This requires some information about the selected item to be passed to the ItemDetailPage.

I’ve seen all sorts of mechanisms for passing data between pages across various application platforms. Some platforms only allow a simple query string to be passed as part of navigating between pages, whilst others allow you to pass entire objects. In the case of Xamarin.Forms the default navigation pattern doesn’t provide for a mechanism to pass data. However, because you create the instance of the new page before navigating to it, there’s a perfect opportunity to pass data from the existing page to the new page.

The following code is adapted from the code in the previous post to illustrate wiring up navigation to the ItemDetailPage based on the SelectedItemChanged event on the ItemsViewModel. Note that the selected item, passed into the event handler as the variable args, is set on the Item property of the ItemDetailViewModel.

// When the SelectedItemChanged event is raised on the ItemsViewModel
// navigate to the ItemDetailPage, passing in a new ItemDetailViewModel
// with the selected item
Maps.For<ItemsViewModel>()
    .Do(new EventHandler<Item>(async (s, args) =>
    {
        if (args == null)
        {
            return;
        }
        var page = new ItemDetailPage();
        if (page.BindingContext is ItemDetailViewModel vm)
        {
            vm.Item = args;
        }
        await Navigation.PushAsync(page);
    }))
    .When((vm, a) => vm.SelectedItemChanged += a, (vm, a) => vm.SelectedItemChanged -= a);

Note: Setting a property on the viewmodel will occur before the OnAppearing method is invoked (see previous discussion regarding loading data) which means the viewmodel can make use of whatever data you pass in when loading data. In this case we could have simply passed in the Id of the item we want to display and have the viewmodel load the rest of the data related to that item.

Returning a Parameter

One thing I’ve seen in a number of MVVM frameworks is the ability to navigate to a new page with the expectation that the page will return data at some point in the future. Whilst there are cases where this is convenient (eg prompting the user for some data) this pattern introduces a very heavy dependency between the lifecycle of two pages and their corresponding viewmodels.

An alternative is to assume that each page/viewmodel is independent and that when you arrive at a page any data that is displayed on the page should be refreshed. Of course, if you’ve got a long list of items that the user has scrolled mid-way down and you reload the list, it’s going to return to the top of the list, making the user experience quite nasty. Furthermore, if the list of items is coming from a service, you don’t want to be reloading that data every time the user goes back and forth to a details page.

Most of the above issues can be handled with appropriate caching of data in a service or manager class. If the user navigates to an item, makes changes and saves that item, the cached data in the service would be updated (along with any call required to any backend services). When the user returns to the list of items, the service would simply return the latest cached data. This addresses the latency issue of having to fetch data from a backend service but how do we address the scroll position issue?

One way is to only update the items in the list that have changed (ie catering for add, edit, delete of items). However, having to write this code for every page is again tiresome. In the past, I investigated a possible workaround for this issue when I discussed immutable data – check out posts I, II and III on working with immutable data

Summary

Again, the disclaimer here is that this isn’t a library that you can just drop in and use in your application. However, feel free to take/leave what code you find useful.

Get the latest source code

Flutter: Text Widget

In this post we’re going to look at the Text widget in Flutter and some of the options you can tweak when displaying text within your app. If you’re after a more detailed discussion of strings, characters and how they’re displayed, you should check out the post, Mastering Styled Text in Flutter. To get into … Continue reading “Flutter: Text Widget”

In this post we’re going to look at the Text widget in Flutter and some of the options you can tweak when displaying text within your app. If you’re after a more detailed discussion of strings, characters and how they’re displayed, you should check out the post, Mastering Styled Text in Flutter. To get into it, we’re going to start off with a new Flutter project, which already displays text to indicate how many times the button has been pressed. The text is broken into two Text widgets to allow for the styling of the actual counter value to be different from the preceding text.

Text Constructor

This code snippet shows us a couple of things: firstly, that the first parameter of the Text constructor is the string to be displayed; secondly, we can override, or set, the style of the text using the style parameter. This, of course, prompts the question as to what parameters are there on the Text widget and what do they mean.

Let’s take a look at the Text constructor – select the Text widget and press F12 in Visual Studio Code to be taken to the actual code for the Text class and its constructor:

class Text extends StatelessWidget {
  const Text(
    this.data, {
    Key key,
    this.style,
    this.strutStyle,
    this.textAlign,
    this.textDirection,
    this.locale,
    this.softWrap,
    this.overflow,
    this.textScaleFactor,
    this.maxLines,
    this.semanticsLabel,
    this.textWidthBasis,
  })

As we can see from the constructor definition, there is a single data parameter, followed by a list of optional parameters. Let’s step through these parameters and take a look at what they do.

Text.style

The first optional parameter of the Text widget is “style” which is of type TextStyle. As you’d imagine the TextStyle class can be used to specify the foreground and background color, the font size and weight, letter and word spacing, locale, shadows and much more.

What’s interesting is that the first Text widget shown above, we didn’t specify a TextStyle, and yet the text was still displayed on the screen with a basic style applied. This is because when the style property isn’t set, the Text widget will search the widget tree looking for style information to use. To demonstrate this, try passing a Text widget into the runApp method.

void main() => runApp(Text("test"));

This will actually fail to run stating that RichText widgets require a Directionality widget ancestor. We can fix this easily by wrapping the Text widget with a Directionality widget as follows:

void main() => runApp(Directionality(textDirection: TextDirection.ltr, child: Text("test")));

After doing this, what we’re left with on-screen is a very unstyled piece of text situated in the top left corner of the screen.

Returning now to our application, the question becomes, where does the default styling for Text come from. The answer to this question can be found in the documentation for the style property (press F12 in VS Code to navigate to the property definition where you can find the relevant documentation). The documentation states that the style property will be merged with the style associated with the closest enclosing DefaultTextStyle (assuming the inherit property on the style is set to true). What this means is that the Text widget will traverse up the widget hierarchy looking for a DefaultTextStyle widget on which to base the style of the text on.

So the next question you’re probably going to ask is where is the DefaultTextStyle widget being added to the hierarchy because it doesn’t appear anywhere in the app that was generated when we created the new project. Well, it might surprise you to know that the DefaultTextStyle widget appears at least twice, being added by both the MaterialApp and Scaffold widgets respectively. The following diagram illustrates just part of the widget hierarchy that shows the DefaultTextStyle appearing below the AnimatedDefaultTextStyle node.

Coming back to our Text widget, if we specify a value for the style property, it will be merged with the style of the nearest DefaultTextStyle widget. For example, let’s change the colour of the text to purple.

Text(
  'You have pushed the button this many times:',
  style: TextStyle(color: Colors.purple),
),

Alternatively, as we saw in the second Text widget in the initial example, the style of the Text widget can be set based on the current Theme. The Theme defines a number of different text styles that can be used – check out the Flutter documentation for more information on the individual text styles. In the following code, the display1 style is applied to the Text widget.

Text(
  '$_counter',
  style: Theme.of(context).textTheme.display1,
),

Sometime you’ll want to use one of the theme text styles but you want to adjust one or more attributes. For this, you can use the apply method as in the following example that sets the colour of the text to purple.

Text(
  '$_counter',
  style: Theme.of(context).textTheme.display1.apply(color: Colors.purple),
),

Since we’ve now set the colour of both Text widgets to purple, it would be good to be able to extract this so that it’s only applied once and will affect both Text widgets. This comes back to what I was saying about the DefaultTextStyle widget and how the style property merges with the nearest DefaultTextStyle widget – in order to set an attribute that should apply to all Text widget, we just need to add a DefaultTextStyle widget to the hierarchy, setting the appropriate attribute.

child: DefaultTextStyle(
  style: TextStyle(color: Colors.purple),
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      Text(
        'You have pushed the button this many times:',
      ),
      Text('$_counter', style: Theme.of(context).textTheme.display1),
    ],
  ),
),

Note that the DefaultTextStyle widget also merges its style with the style of the nearest parent DefaultTextStyle widget.

Text.strutStyle    

Setting the strutStyle property gives you the ability to fine-tune the separation between rows of text. For example, if you have a number of Text widgets that have differing font style and sizes, you can specify the strutStyle to ensure the same spacing between each row.

Text(
  'This is a very long piece of text designed to wrap over multiple lines. This is a very long piece of text designed to wrap over multiple lines.',
  style: TextStyle(fontSize: 12),
  strutStyle: StrutStyle(fontSize: 13),
),
Text(
  'This is a very long piece of text designed to wrap over multiple lines. This is a very long piece of text designed to wrap over multiple lines.',
  style: TextStyle(fontSize: 14),
  strutStyle: StrutStyle(fontSize: 13),
),
Text(
  'This is a very long piece of text designed to wrap over multiple lines. This is a very long piece of text designed to wrap over multiple lines.',
  style: TextStyle(fontSize: 12),
  strutStyle: StrutStyle(fontSize: 13),
),

As you can see from the following image, despite the text in the middle section being a slightly larger font, the line separation is uniform across all lines of text.

Further information on using the strutStyle property can be found at the StrutStyle documentation

Text.textAlign and Text.textDirection

I’ve grouped the textAlign and textDirection properties together as they are related as the textDirection determines how some textAlign values control the layout of text in the Text widget. In the following example there are five Text widgets, with different combination of textAlign and textDirection property values.

Text(
  'JUSTIFIED - This is a very long piece of text designed to wrap over multiple lines. This is a very long piece of text designed to wrap over multiple lines.',
  textAlign: TextAlign.justify,
),
Text(
  'LEFT (LTR) - This is a very long piece of text designed to wrap over multiple lines. This is a very long piece of text designed to wrap over multiple lines.',
  textAlign: TextAlign.left,
  textDirection: TextDirection.ltr,
),
Text(
  'LEFT (RTL) - This is a very long piece of text designed to wrap over multiple lines. This is a very long piece of text designed to wrap over multiple lines.',
  textAlign: TextAlign.left,
  textDirection: TextDirection.rtl,
),
Text(
  'START (LTR) - This is a very long piece of text designed to wrap over multiple lines. This is a very long piece of text designed to wrap over multiple lines.',
  textAlign: TextAlign.start,
  textDirection: TextDirection.ltr,
),
Text(
  'START (RTL) - This is a very long piece of text designed to wrap over multiple lines. This is a very long piece of text designed to wrap over multiple lines.',
  textAlign: TextAlign.start,
  textDirection: TextDirection.rtl,
),

When we run this code we can see that the textAlign property does what you’d expect it to – the text is justified for the TextAlign.justify value and aligned left for TextAlign.left. What’s interesting is that TextAlign also includes values start and end which are important if you’re considering supporting RTL languages within your application. As you can see from the following image with textAlign set to TextAlign.start, if we adjust the textDirection between LTR and RTL we can see that the text changes from left aligned to right aligned.

Text.locale

One of the most common reason for explicitly setting the locale for a Text widget is to adjust the text that is being rendered. Adjusting the locale will change the way certain unicode characters are displayed.

Text.softWrap

Setting the softWrapp property to false will disable wrapping, causing the text to be truncated by the right edge of the parent container.

Text.overflow

The overflow property controls what happens when there is more text than will fit in the space available. For example you can use the predefined value TextOverflow.ellipsis for Flutter to insert … when there isn’t sufficient space. Note that ellipsis will disable wrapping, regardless of how much space there is available (see maxLines discussion below).

Another possible overflow value is TextOverflow.fade

Container(
  height: 30,
  child: Text(
    'This is a very long piece of text designed verylongwordwithnospaces to wrap over multiple lines. This is a very long piece of text designed to wrap over multiple lines.',
    overflow: TextOverflow.fade,
  ),
),

Using TextOverflow.fade in conjunction with a fixed height on the parent Container rejusts in the following effect where the first Test widget fades out before the second Test widget.

Text.textScaleFactor

The textScaleFactor can be set to apply an arbitrary scaling to the text being displayed by the Text widget. Note that this will override any textScaleFactor applied by the current MediaQuery, so may result in incorrect layouts on devices where the textScaleFactor isn’t null or 1.0.

Text.maxLines

Where the text being displayed stretches over multiple lines, the maxLines property defines the maximum number of lines that will be displayed.

When the overflow property is set to TextOverflow.ellipse the maxLines property can be set to increase the number of lines of text that will be displayed before the ellipses are appended.

Text.semanticsLabel    

You can use the semanticsLabel property to improve the way that screen reader and assistive technologies work with your application. Check out this post on Semantics for further information.

Text.textWidthBasis

The textWidthBasis property can control how the width of the Text widget is defined. More information is available via the documentation.

Summary

In this post, I’ve walked through the use of the Text widget. I would highly recommend pressing F12 on a Text widget and go explore the source code. It’s worth to note that other classes, such as RightText and TextSpan, that exist – I’ll leave it to the reader to drill into these and understand how they’re related.