xamarin

Creating & managing dev, staging and live versions of your Xamarin app

Posted on Updated on

When creating apps of moderate complexity in a team of several members including developers, designers, QA etc. it quickly becomes apparent that having a single version of an app with a single version of each dependent service doesn’t really work.

Today I’m going to share how we’ve dealt with this at JustGiving by creating 3 separate versions of our app. I’ll also show you how we manage this through source control and our continuous deployment system to be able to give everybody in the team ready access to these different versions, each with their own level of expectations around them.

cropped

 

There are two distinct parts to provisioning an app for various environments: 1) compiled configuration settings and 2) managing the iOS info.plist file. Managing compiled configuration settings will be the more familiar solution to regular .NET developers as this will only involve a simple combination of dependency injection and compiler flags. Managing the info.plist is a little more tricky as we’re going to use conventions of the iOS compilation process itself.

Compiled configuration settings

Any moderately complex app will inevitably connect to a variety of third party services for functions like analytics & crash reporting, push notifications, social sharing etc. Most if not all of these providers will have at least Staging and Live equivalents explicitly, or you can create your own (for example having multiple Facebook Apps for staging/live for Login With Facebook functionality). The main dependency that the JustGiving app has is of course the JustGiving Public API, for which we have Development, Staging and Live versions.

Using a simple combination of dependency injection and compilation symbols, we can easily manage a distinct set of configuration settings for each of our development, staging and live app environments.

First, we edit our configuration options of the iOS project to reflect our 3 environments, as depicted in this screenshot:

Solution Configuration Options
Xamarin Studio – Solution Configuration Options

This will allow us to compile the app in local development version (DEBUG), a staging version (RELEASE) and a live version (APPSTORE). You can effectively name these options whatever you like.

Next, we have to have a set of configuration settings that our dependency injection framework can bind differently based on those compilation symbols.

public interface IConfigurationSettings
{
string ThirdPartyApiKey {get;}
string AnotherThirdPartyApiKey { get; }
}

public class DebugConfigurationSettings : IConfigurationSettings
{
public string ThirdPartyApiKey { get; protected set;}
public string AnotherThirdPartyApiKey { get; protected set; }

public DebugConfigurationSettings ()
{
ThirdPartyApiKey = "debugKey";
AnotherThirdPartyApiKey = "otherDebugKey";
}
}

public class ReleaseConfigurationSettings : IConfigurationSettings
{
public string ThirdPartyApiKey { get; protected set;}
public string AnotherThirdPartyApiKey { get; protected set; }

public ReleaseConfigurationSettings ()
{
ThirdPartyApiKey = "stagingKey";
AnotherThirdPartyApiKey = "otherStagingKey";
}
}

public class AppStoreConfigurationSettings : IConfigurationSettings
{
public string ThirdPartyApiKey { get; protected set;}
public string AnotherThirdPartyApiKey { get; protected set; }

public AppStoreConfigurationSettings()
{
ThirdParyApiKey = "liveKey";
AnotherThirdParyApiKey = "otherLiveKey";
}
}

Now, deep in the setup code of the app, we have the following code to bind a different concrete implementation of our ConfigurationSettings class to our interface IConfigurationSettings, based on the relevant compilation symbol. Note that we are using MvvmCross, and so are using it’s own dependency injection framework, but the concept is the same regardless of what DI framework you will be using.

 #if DEBUG
 Mvx.LazyConstructAndRegisterSingleton<IConfigurationSettings, LocalDebugConfigurationSettings>();
 #elif RELEASE
 Mvx.LazyConstructAndRegisterSingleton<IConfigurationSettings, TestFlightReleaseConfigurationSettings>();
 #elif APPSTORE
 Mvx.LazyConstructAndRegisterSingleton<IConfigurationSettings, AppStoreConfigurationSettings>();
 #endif

So that sorts the configuration settings side of things out. Now let’s delve into the info.plist.

The iOS info.plist file can be thought of by .NET web developers as the equivalent of web.config. All manner of iOS-relevant configuration options appear in the info.plist including version information, supported orientations, iOS versions, the names of your image asset catalogues, etc. Additionally, some iOS 3rd party libraries require that you include certain values in this file (for example the Facebook SDK requires your Facebook App ID).

We originally had one info.plist file, and then our Jenkins build server would use plistbuddy to change individual values (like the Facebook App ID’s above) at build time depending on the environment being built for. However this meant the values were being stored by Jenkins rather than source control with everything else, and that felt rather opaque to the developers and not easy to lookup or quickly change.

We quickly refactored to simply having multiple versions of the info.plist file, one for each environment.

An info.plist for every environment
An info.plist for every environment

As with the compiled configuration settings before, this now allows you to easily manage 3 completely distinct build-time configuration option sets. A few of the more useful info.plist properties to differentiate between environments are:

CFBundleVersion
We have ‘Development’ or the Jenkins build number here. This appears in the Settings screen of the app, which means any team members (or indeed users) unsure of the version on which they’re on can quickly look it up.

CFBundleIdentifier
Things can get messy if you don’t vary the BundleId, which you can think of as the main identifier of your app. Many user-facing functions depend on BundleIds being congruent across services. For example, our Azure Mobile Services staging app for push notifications is provisioned with a APNS ‘Test’ certificate generated for an app with a BundleId of ‘com.justgiving.app.staging’. This same BundleId appears in our (staging) Facebook App iOS settings as well. As iOS devices can only have a single version of a single app (as identified by the BundleId) installed at once, having a different BundleId for each environment also allows users to have all 3 versions on their devices at the same time.

XSAppIconAssets
Using Asset Catalogues became the preferred way to manage and ship your assets from iOS 7 onwards. The good news is that if you’re doing it this way, it is easy relatively easy to vary your app icons by environments. This is obviously very useful to those members of your team likely to have multiple versions of the app on their phones at any one time!

We have a separate App icon asset catalogue for each environment:

An Asset Catalog per environment
An Asset Catalog per environment

And then the relevant value in each info.plist file:

Dev environment info.plist

This results in our beautifully illustrative and differing app icons:

App per environment

The final question is, how do we use these different info.plist files? Well, Jenkins (app ‘Release’ build job) simply deletes the info.plist on disk (which is the development version) and renames the ‘Info.Release.plist’ to ‘Info.plist’ prior to compiling the app as normal. Similarly, a step in the ‘AppStore’ Jenkins job renames ‘Info.AppStore.plist’ to ‘Info.plist’.

Jenkins renames the relevant info.plist files
Jenkins renames the relevant info.plist files

Simple but effective!

Keeping separate apps for each of your environments not only avoids confusion when your internal users are using your app, but also allows solid and predictable testing for your QA team and allows you to diagnose issues with more confidence. Using the two tricks described above, you can achieve this variance effortlessly and be on your way to further scaling up your app development!

Mark
Lead Developer, Mobile Platforms
JustGiving – The world’s social platform for giving