Different app icons for different configurations in Xamarin
When you start sending out versions of your app for testing purposes in a corporate environment, it might be desirable to being able to distinguish them, or maybe even run two version simultaneously. This is just one of the many situations that can be implemented by making use of the MSBuild conditions. In this post I will show you how to include and select different app icons based on the build configuration.
The Why #
There can be many whys. One of them is the situation I have described above, another could be that you maybe want to change the display name of your app depending on a build configuration. You could even change the whole theme altogether!
Since some while now, Xamarin projects use the MSBuild process for building. With the power of MSBuild you can include different files - which hold different content - depending on the build configuration or other conditions.
To read more on the MSBuild conditions, please refer to this documentation page. But don’t worry, I’ll guide you through the basics here. Setting up the actual build configuration can be a very extensive process and is beyond the scope of this post. To learn more, read the Microsoft Docs on this subject.
The How #
Now the question is: how do we implement this?
The implementation per platform differs, because different infrastructure is used for each platform to incorporate icons and reference them. The sample code that I will be using for this post can be found on my GitHub page: https://github.com/jfversluis/DifferentAppIconsSample
While this project is a Xamarin.Forms project, this method works for traditional Xamarin as well. Heck, it works for basically any .NET based project for that matter.
First, let’s have a look at Android.
Android #
Android Manifest #
On Android icons are referenced from the AndroidManifest.xml. Or at least, that is one way to do it. In the below code you can see a sample manifest file.
In the manifest node root you can see the package attribute. This has been postfixed with .dev. This enables you to run multiple instances of the app side-by-side on one device. Another thing to note is the application node. It has a label and icon which also are specific to the dev build.
Now let’s duplicate this file, keep it in the same folder, but name it: AndroidManifestProduction.xml, please note that you want to stay away from hyphens in filenames on Android. They tend to bring trouble.
In the below manifest you will notice a few minor changes. The package identifier is now without the postfix. Also, the label and icon have a different value.
In this scenario I just have a dev and production scenario. In production you probably want to have a non-postfixed package identifier and the app label should not specify the current environment since it is not relevant to the end-user. But of course you can repeat this process for any number of environments that you might have.
Android Project File #
Now that we have this file prerequisite in place, it is time to look at how to select the right file at compile-time. For this, we need to edit the Android project csproj file. This can be done with an external editor like Notepad++ or from Visual Studio directly. I like to do it from an external editor. When you open it up, you will see that the csproj is nothing but a xml file as well.
Typically, you should find a couple of PropertyGroup nodes one for each build configuration. These property groups should contain a AndroidManifest node each. Below you can review what it should look like. These AndroidManifest nodes could not be there right now, you can however just add them yourself.
In this case, distinguishing the different environments is easy, just change the value in the AndroidManifest node to the right manifest filename and you’re already done! When you switch build configurations in Visual Studio, it will automatically select the right manifest file and include that one. Of course you also should have all the different icons and other resource in place that are referenced from the different manifest files.
iOS #
As with many things, for iOS, things are a bit different..
Info.plist File #
If you have worked with iOS before, you will know that the info.plist file is more or less the counterpart for the AndroidManifest.xml file. At least for the functionality that we are after here. For iOS I will be working with asset catalogs.
The main problem for iOS is that the file has to be called info.plist, it can’t be named any other way, we will look at how to solve that in the csproj file. For now, just copy the info.plist file for each separate environment that you want to use. I have named them info.plist and info-Production.plist. Of course, this can be any number of files if you should need it.
Underneath you can review the info.plist file I will use for the development environment.
As you can see, this file is also just an XML file. Mainly have a look at the CFBundleDisplayName, CFBundleName and XSAppIconAssets. Here you will see that I have denoted a few things with ‘dev’. This is where we will make the difference. If you want to be able to run simultaneous builds on one device, also create different CFBundleIdentifier values. For the info-Production.plist change the values accordingly. For the asset catalog copy the AppIcons.appiconset folder, which can be found under Resources/Images.xcassets. Then replace the images in there as needed. We also need to reflect this in the csproj file as we will see in a little bit.
iOS Project File #
Another difference with Android is that the info.plist file doesn’t have its own dedicated tag in the csproj file. And, as we remember from just before, the file always needs to be info.plist.
When we open up the iOS csproj file, we will find a info.plist entry in a None tag.
Copy that None node and change the filename to the production variant. On the None node we can also apply conditions. Note that the regular file is used when the build configuration is Debug, else the production file is used. To account for the filename, add a LogicalName node under the None node. This means the file is outputted with the filename that you specify as a value.
A little further one there is also a ItemGroup that contains all the images in the asset catalog. We need to duplicate the entire ItemGroup with all the images and adapt the filepath to the new images that are to be used in a different environment. Observe this example below:
On these ItemGroup nodes, and most of the other nodes, you can also apply the same condition attribute. This way, your duplicate files will only be included for the right configuration as well!
In Closing #
In this post we have seen how we can make use of different files and values according to our build configuration. While this is a bit tricky to figure out at first, it is very powerful, especially when used in corporate situations where you used an automated pipeline. This way the test users can easily identify different versions of the app. While most of what I have shown here is used for visual changes, you could actually use this for switching out API keys etc. But mind you, there are other, (arguably) better methods to do this.
The code for this post can be found on GitHub: https://github.com/jfversluis/DifferentAppIconsSample
If you have any questions, hit me up on Twitter and don’t forget to check out my YouTube channel (and subscribe!) for some visual tutorials.