Dynamic App Ids & supporting multiple side-by-side release streams

Release Streams

A lot of the times I'm developing mobile applications (iOS/Android) I feel the need to support several release "streams." In most of my cases this usually means having a stream for "dev/nightly" (for the dev team & unit tests), "QA" (for the QA testers), and "AdHoc" (for testing against a production mirror before release). I find that most people across teams would like to have side-by-side installs of these "streams" and I've seen people ask about this a lot on the Xamarin Slack Channel so I'm finally taking some time to write about how I manage this requirement. It's not very difficult or time consuming to set up and I haven't had any issues with maintenance so it's a solution I like -- we're basically going to dynamically generate the Info.plist of an iOS project and the AndroidManifest.xml for Android. Here we go!

Step 1 -- gitignore

It may seem kind of strange but you're going to need to add your Info.plist and AndroidManifest.xml to your gitignore file (assuming you're using git). Go ahead and do that. Make sure that your project still references these files even if they won't be there on a fresh clone or clean of the repo -- we're going to generate these as a pre-build script.

Step 2 -- template files

Next thing to do is create some template files. Copy/paste what you have so far for your Info.plist and AndroidManifest.xml into a new text file. Your conventions at this point are up to you -- I personally create files in the same directory that the original files were in (so for a Xamarin project the root iOS project directory for the .plist on iOS and the Properties folder for the AndroidManifest) and name them with a .template file name ending. It's really up to you but this is how I do it currently. I also personally do not add these files to my .csproj files to reduce bloat, but there's nothing stopping you from including these temp files in your project if you prefer to have them available in your IDE.

Step 3 -- placeholders

At this point I take those .template files and add placeholders for the bundle/app ids. It looks something like this (note: I'm cutting these files down a lot just to show what we're trying to accomplish, here):


You should see there are a few "markers" here for us to replace dynamically. Ideally these markers should be the same for each project especially if your doing Xamarin development to make the automation easier. The markers to look for here are {APP_ID}, {APP_NAME}, and potentially {BUILD_NUMBER} which is something I use to increment the build no. of an app dynamically. We won't be getting into the build number stuff here, but I felt it was worth pointing out that that's something I've added onto this process for myself -- hoping this will show how easy it is to bend this process to your needs.

Step 4 -- populating placeholders

Use whatever scripting language you prefer but I used python to do all the string replacement of those placeholder we put in earlier:

You can see we're just using python to use sed to replace those placeholder values and write new files to the appropriate directory (in this case my python script is sitting at the root of my project, your paths may be different so adjust as necessary). This script will dynamically change the appId, appName, and buildNumber on those crucial platform config files.

Step 5 -- pre-build script

Next I set up my Xamarin project to run the python script as a pre-build step with the appropriate Build Configuration argument. Here's an example in Xamarin for a Debug build (I have this set up for each platform project):

This will generate a new config file for each project before every build, which is what we want.

That's really it! Now we have a dynamically generated AppId, name, and in my case build number for every build based on the BuildConfiguration.

Epilogue --

A few things to look out for & consider:

Build machines

I've had a few issues with different CI frameworks not running the pre-build scripts when using msbuild for Xamarin projects. In that case I have added a step to my CI builds to run this script before building the project so the expected config files are present & populated appropriately


I didn't include a few things here for the sake of brevity but in practice I have extended these scripts to also create proper Firebase config files, build numbers, and other little things here & there. I haven't found it too tedious to maintain and add on to the dynamic generation of important files for side-by-side app installs per release stream so by all means add more! This was meant to be a quick primer on how I solved this problem in case you're looking for a solution. Hope it helps!