This series of blog posts describes an approach I've taken recently to implement continuous delivery for cross-platform native mobile apps built using Xamarin Forms and C#, targeting both Android and iOS.
- Part 1 (this post): focuses on putting together a basic Xamarin Forms application with specific patterns to support continuous delivery
- Part 2: focuses on creating a build pipeline that will provide continuous integration for the application source, by building, testing and packaging the code whenever it changes
- Part 3: focuses on creating a release pipeline that will allow staged deployments of app builds to App Center for testing and production release
- The application code should be built and tested whenever commits are made to any branch in the source repository.
- Whenever a build successfully completes on the master branch, the app should be packaged and deployed automatically to the DEV environment. This would normally occur due to a new Pull Request (PR) being merged to the master branch from a feature branch, for example.
- The configuration of the app packages for each platform should be updated as a release is promoted from one stage to the next (eg from DEV -> UAT, or UAT -> PROD), using settings appropriate for the environment associated with each stage.
- Manual testers and operators should be notified by email when a new release is available in App Center.
- I've used Visual Studio 2019 (version 16.7.1) for developing the sample solution, with the Mobile Development for .NET workload installed, which covers Xamarin requirements, but you could also use Visual Studio for Mac
- You will need access to a MacBook or similar MacOS X device for building and signing the iOS project, and a paid Apple Developer account that can be used for generating certificates and provisioning profiles - get started at https://developer.apple.com
- You will need an Azure DevOps account for creating the build and release pipelines used in this series. Source code is hosted in a git repository in Azure Repos, but you could use any other source control provider such as GitHub or BitBucket if you wish - go to https://dev.azure.com to get started
- You will need a Visual Studio App Center account in order to create the platform-specific projects we will need for deploying and testing our app packages - go to https://appcenter.ms/ to get started
Step 1: Create a basic Xamarin Forms app
Let's get started by creating a new Xamarin Forms app that we will be able to build and release through our pipelines into App Center. We don't need anything complicated, as the focus of this post will be on infrastructure in the app required to support our builds and deployments later on, however we will use Prism as the application framework here as this provides functionality we can use to register and inject dependencies into our components, and make our code testable in our unit tests project.
If you would rather clone the completed app to save time, you can access the full source code at https://github.com/sam-piper/xamarin-devops at any point.
First, let's install the Prism Template Pack into Visual Studio so we can easily scaffold a new Xamarin Forms project based on Prism. In Visual studio, go to Extensions > Manage Extensions..., and search for Prism, then install the Prism Template Pack if not already installed. You'll need to close all open instances of Visual Studio before the extension will be installed.
Once the extension has been successfully installed, re-open Visual Studio 2019, and create a new project. Search for Prism, and select the Prism Blank App template for Xamarin Forms as indicated below:
Enter MobileDevOps.SampleApp as the project and solution name, then click Create. In the Prism Project Wizard dialog, uncheck the UWP option, but keep the Android / iOS options checked. Leave the container option at the default of DryIoc, then click Create Project.
It's a good idea to update all NuGet packages in the solution to their latest available versions. You will also need to add the NuGet package for Newtonsoft.Json to the solution, as this will be used later on for loading configuration settings at runtime.
IMPORTANT: before going any further, make sure you can build and run the app for both platforms, using either emulators or real devices.
When developing a .NET Core application, most developers will already be familiar with the appsettings.json file, which is a JSON document used to contain any environment-specific settings loaded at start-up. There is no direct equivalent in a Xamarin Forms application but we can easily simulate the same design pattern with some creative coding.
The goal here is to be able to update a JSON document inside an Android *.apk file or iOS *.ipa file with environment-specific variables, using a standard Azure Pipelines task, so that we can re-configure and deploy an app package to different environments without needing to re-build the packages from source code as well.
First, we need to add a JSON file which will be used to store all the configuration settings needed by the application, such as the base URL of a RESTful API used to send and retrieve data, keys required for App Center SDK integration with platform-specific projects, and so on.
In the Properties window for the file, ensure Build Action is set to None, and that Copy to Output Directory is set to Do not copy.
In order to make this file available at runtime, we need to link it as an asset in the platform-specific projects.
In the Android project, right-click the Assets folder, select Add > Existing Item..., navigate to the shared project folder, select the appsettings.json file you just created, and add the file using the Add As Link option. Also, in the Properties window, make sure that the Build Action of the link is AndroidAsset.
Repeat the same steps for the iOS project, by adding a link to the Resources folder. The Build Action of the link should be BundleResource.
Now you can make changes to the file in one place and it will work for both platforms. Next, we'll add some code that allows us to read this file at runtime. Add a new item to the Services folder in the shared project called IConfigurationFileProvider.cs:
This interface will provide access to the appsettings.json file at runtime. We will need to add platform-specific implementations of this interface next. In the Android project, add a Services folder, then add a new item to the folder called ConfigurationFileProvider.cs:
In the iOS project, do the same as for the Android project, but with this code instead:
Next, we will need to register the platform-specific services with the application's Dependency Injection (DI) container using the platform initializer in each platform-specific project. In the Android project, open the MainActivity.cs file, and update the code for the AndroidInitializer class to match the following:
In the iOS project, open the AppDelegate.cs file, and update the code for the iOSInitializer class to match the following:
Finally, we need to add a service to the shared project that exposes our application settings to higher-level components for consumption. Add a new item to the Services folder of the shared project called IApplicationSettings.cs, and paste in this code:
Then in the same folder, add a new item called ApplicationSettings.cs and paste in this code:
Make sure you've added Newtonsoft.Json as a NuGet reference to all projects as well. We also need to register this service for injection into our view models. Open App.xaml.cs in the shared project, and add this line to the RegisterTypes method:
To complete our sample app for now, we'll inject IApplicationSettings into our base view model and update the main page to display the values from the file, so that we can easily verify that the settings are being picked up correctly from the appsettings.json file.
Open ViewModelBase.cs in the ViewModels folder of the shared project, and paste the code below:
Open MainPageViewModel.cs in the same folder, and update the code to the following:
Open MainPage.xaml in the Views folder, and update the code to the following:
Now build and run the application for both iOS and Android, and ensure that you see the expected application settings displayed in the app. You should see something similar to the following:
Finally, to complete our sample solution, we will add a project to contain unit tests that can be run during builds, to help enforce the runtime quality of our code. This won't be an exhaustive example as we just need something basic that can break our build if needed. I'll be using xUnit as the test framework in my examples, but you can use any other framework you wish (NUnit, MSTest, etc).
Right-click the solution node in Solution Explorer, select Add > New Project..., and create a new xUnit Test Project (.NET Core) with the name MobileDevOps.SampleApp.Tests. Also, update the NuGet packages for this project as well. Add a project reference to the shared project, MobileDevOps.SampleApp.
Note that this xUnit tests project is using the .NET Core 3.1 SDK, and can reference only the shared project using .NET Standard 2.0 - we can't easily reference the platform-specific projects as they run using different target frameworks, so we will just focus on unit testing our shared code for now.
That's all the code changes we will make for now, if you haven't done so already, check in your code to your online repository and in the next post we'll focus on creating a build pipeline that will build, test, and package the app to make it ready for deployment later on.
Thanks for reading! Check back soon for my follow-up posts in this series.