White-labeling Xamarin iOS apps

When developing mobile apps (or any other kind of software for that matter) we usually have multiple different environments that we want them to run on. Mobile applications might have some sort of backend API server, analytics service, crash reporting, social integration and huge variety of other services they rely on. Most of those services use some sort of unique ID number so that our app could connect and be uniquely identified by the provider. API servers might be accessed with URLs while other services require us to use access tokens, connection strings, API keys/tokens or something similar. The problem is that we want to use different versions of those services depending on the environment that our app runs on. For example we don’t want our crash reporting data do be polluted with crashes from debug/simulator builds and we don’t want to charge actual users credit cards just because we messed something up while debugging or testing out new features. Usually this problem is solved using build configurations and by defining custom symbols. Then we can use #if directive to conditionally compile our code including only necessary parts for that particular configuration. But by doing this, another problem arises: how do we maintain all of those different versions installed on our devices at the same time? Once we ship our app we and our testers would like to have production build that our users are using and latest staging build to test out new features. And we obviously want both of those versions to be installed side-by-side on the same device. You might say this is simple, just change app id and you will be able to install multiple version side-by-side. We are going to do just that but in an automated way 🙂 Another issue we might encounter is lack of ability to distinguish versions once they are already installed since names and icons are exactly the same. So we are going to switch app icons on build time again in an automated way.

In this post I will show you how to configure Xamarin.iOS project so that appropriate app id and app icons would be included in bundle at build time depending on build configuration. We will be writing a short shell script so it will only work on Mac. Though it shouldn’t be too hard to convert it to run on Windows.

Configuration

Fist of all lets take a look at build configurations that I will be using for my sample app. I will be using 3 different environments in this example:

  • Development: where API server is running in my local dev machine.
  • Staging: where API server is running on environment that is as close to production as possible but no production services/databases are used.
  • Production: API server environment that actual users are using.

The list of configurations might be different depending on project of course. You may not always be in control of backend API in which case first environment wouldn’t be used. You might also want to have different configurations for other environments. Like for running UITests for example, where services are mocked out. Also AppStore configuration would probably be used in a real app to configure correct provisioning files.

And of course we still want to have different compiler/linker settings for debug/release configurations and device/simulator platforms. So to get all configurations we compute all possible combinations using this template: <Debug/Release>_<Environment> | <Platform>.

All possible build configurations

Then we define appropriate symbols for each environment so that we could conditionally compile necessary constants using compiler directives.

Conditional Compilation

Generating Info.plist file

First of all we should open Assets.xcassets folder in VS for Mac and create as many icon sets as we need. In my case I will have different set for each configuration.

XCAssets

Then we have to create a placeholder file for auto generated Info.plist. I called it Info.Generated.plist. It can be empty because its content will be generated automatically on build time.

Once that’s done we can open up our iOS project file (.csproj). We will have to edit its content manually because it isn’t possible to change everything in UI.

What we have to do here is include only Info.plist file if we are building a production build, and include only Info.Generated.plist file for every other build (dev, staging). As far as I’m aware Info.plist has to be named exactly like that so we provide logical name (Info.plist) for Info.Generated.plist. And since we will never have to edit Info.Generated.plist file manually we hide it from solution explorer in VS for Mac using Visible=“false" property.

So to recap we include either Info.plist or Info.Generated.plist depending on build configuration. Info.plist will be used for production builds and its content will not be changed.

[code language=”xml” gutter=”false”]
<ItemGroup>
<None Include="Entitlements.plist" />
<None Include="Info.plist" Condition="’$(Configuration)’ == ‘Release_Production’ Or ‘$(Configuration)’ == ‘Debug_Production’" />
<None Include="Info.Generated.plist" Condition="’$(Configuration)’ != ‘Release_Production’ And ‘$(Configuration)’ != ‘Debug_Production’" Visible="false">
<LogicalName>Info.plist</LogicalName>
</None>
</ItemGroup>
[/code]

Now we have to make sure the content of Info.Generated.plist file is generated on build time! Luckily MSBuild allows us to execute custom commands in multiple build stages. We can open project settings -> Build -> Custom Commands section and add new command that will be executed before build.

Custom commands in VS for Mac

We will have to create file GenerateInfoPlist. Then set working directory to ${ProjectDir} so we can access both .plist files. And in Command field enter the name of newly created executable file  and pass build configuration ${ProjectConfigName} as an argument. Don’t forget to run chmod +x on the file to make sure it is executable: chmod +x /path/to/GenerateInfoPlist

GenerateInfoPlist will contain a simple shell script that will copy the content of original Info.plist file to Info.Generated.plist file, append appropriate postfix to CFBundleIdentifier and replace XSAppIconAssets value depending on configuration passed as an argument.

[code language=”bash” gutter=”false”]
#!/bin/sh

# Get content of original Info.plist file
originalFileContent="$(cat Info.plist)"

# Save build configuration passed by IDE
buildConfiguration=$1

# Get CFBundleIdentifier value from original Info.plist file
bundleId=$(defaults read $(pwd)/Info CFBundleIdentifier)

# Overwrite Info.Generated.plist with content of original file
echo $originalFileContent > Info.Generated.plist

# Append appropriate prefix to CFBundleIdentifier
# and replace iconAssets value
if [ $buildConfiguration = "Debug_Dev" ] || [ $buildConfiguration = "Release_Dev" ]; then
bundleId+="-development"
iconAssets="Assets.xcassets/AppIcon-Dev.appiconset"
elif [ $buildConfiguration = "Debug_Staging" ] || [ $buildConfiguration = "Release_Staging" ]; then
bundleId+="-staging"
iconAssets="Assets.xcassets/AppIcon-Staging.appiconset"
else
iconAssets="Assets.xcassets/AppIcon-Production.appiconset"
fi

# replace CFBundleIdentifier
plutil -replace CFBundleIdentifier -string $bundleId Info.Generated.plist

# Use command below if binary .plist format is needed
#defaults write $(pwd)/Info.Generated CFBundleIdentifier $bundleId

# replace XSAppIconAssets
plutil -replace XSAppIconAssets -string $iconAssets Info.Generated.plist

# Use command below if binary .plist format is needed
#defaults write $(pwd)/Info.Generated XSAppIconAssets $iconAssets
[/code]

Now the only thing left to do is to make sure this custom command is added to each required configuration. In my case I want this script to execute on all dev and staging builds. I think it is a little bit easier to do that by manually editing .csproj file, not using UI in VS for Mac.

We could very easily extend this script to modify/add more values like name of the app for example. As mentioned previously this is shell script thus it will not run on windows. The script uses 2 command line utilities (plutil and defaults) to read and modify values of .plist file so make sure they are available on your build machine. They were either installed by default or came with Xcode development tools. Either way I didn’t have to install them separately.

So now before every dev or staging build content for Info.Generated.plist is generated based on values of Info.plist file. Generated file is included in build with logical name of Info.plist and is used in a build process. And as a result by simply switching build configuration we get an app that has different app id (CFBundleIdentifier) and different set of app icons (XSAppIconAssets). And no manual changing of .plist file is needed.

Working projects for both iOS and Android demonstrating this solution is available in my Github page.

And that’s it! We have successfully white-labeled Xamarin iOS app! If you want to find out how to do the same thing on Android you are in luck because I’ve covered that in another blog post. If you have any questions or comments please leave a comment below.

Hi, my name is Andrius. Welcome to my blog! I'm freelance mobile (C# Xamarin) developer currently learning Swift. I also do some ASP.NET Core Web API work as well. I blog mostly about work related subjects. If you would like to contact me for any reason shoot me an email at andrius [at] applications.lt

Site Footer