White-labeling Xamarin Android apps

In my previous post I showed how to white-label Xamarin iOS applications. I also covered the importance of white-labeling and different configurations that my sample app has. In this post I’ll do the same but for Xamarin Android. The process is actually very similar. We are going to conditionally include the correct set of launcher icons and write a simple bash script to update application id (package) inside AndroidManifest.xml file on build time. So without any further ado lets jump to the method 🙂

Configurations

My sample app has 3 different environments: Dev, Staging and Production. I want my app to have different icons and application id (package) so all of them could be installed on the same device side-by-side and it would be easy to distinguish one from the other.

If you have different set of build configurations in your app you would have to update the script but it shouldn’t be too hard.

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

Dealing with icons

Dealing with launcher icons is a bit easier on Android because we don’t have to generate any files on build time. In my case I will define launcher icon in an attribute of my MainActivity and Xamarin will generate required manifest attributes at build time:

[Activity(Label = "Whitelabeled", MainLauncher = true, Icon = "@mipmap/icon")]

public class MainActivity : Activity

{
}

So now we have to add each icon in each Resources/mipmap-{density} folder:

Icons in resources

and conditionally include the correct ones based on build configuration. We can do that by manually editing .csproj file like so:

<ItemGroup>
  <AndroidResource Include="Resources\layout\Main.axml" />
  <AndroidResource Include="Resources\values\Strings.xml" />

  <AndroidResource Include="Resources\mipmap-hdpi\Icon_Dev.png" Condition="'$(Configuration)' == 'Release_Dev' Or '$(Configuration)' == 'Debug_Dev'">
    <LogicalName>mipmap-hdpi\Icon.png</LogicalName>
  </AndroidResource>
  <AndroidResource Include="Resources\mipmap-mdpi\Icon_Dev.png" Condition="'$(Configuration)' == 'Release_Dev' Or '$(Configuration)' == 'Debug_Dev'">
    <LogicalName>mipmap-mdpi\Icon.png</LogicalName>
  </AndroidResource>
  <AndroidResource Include="Resources\mipmap-xhdpi\Icon_Dev.png" Condition="'$(Configuration)' == 'Release_Dev' Or '$(Configuration)' == 'Debug_Dev'">
    <LogicalName>mipmap-xhdpi\Icon.png</LogicalName>
  </AndroidResource>
  <AndroidResource Include="Resources\mipmap-xxhdpi\Icon_Dev.png" Condition="'$(Configuration)' == 'Release_Dev' Or '$(Configuration)' == 'Debug_Dev'">
    <LogicalName>mipmap-xxhdpi\Icon.png</LogicalName>
  </AndroidResource>
  <AndroidResource Include="Resources\mipmap-xxxhdpi\Icon_Dev.png" Condition="'$(Configuration)' == 'Release_Dev' Or '$(Configuration)' == 'Debug_Dev'">
    <LogicalName>mipmap-xxxhdpi\Icon.png</LogicalName>
  </AndroidResource>

  <AndroidResource Include="Resources\mipmap-hdpi\Icon_Staging.png" Condition="'$(Configuration)' == 'Release_Staging' Or '$(Configuration)' == 'Debug_Staging'">
    <LogicalName>mipmap-hdpi\Icon.png</LogicalName>
  </AndroidResource>
  <AndroidResource Include="Resources\mipmap-mdpi\Icon_Staging.png" Condition="'$(Configuration)' == 'Release_Staging' Or '$(Configuration)' == 'Debug_Staging'">
    <LogicalName>mipmap-mdpi\Icon.png</LogicalName>
  </AndroidResource>
  <AndroidResource Include="Resources\mipmap-xhdpi\Icon_Staging.png" Condition="'$(Configuration)' == 'Release_Staging' Or '$(Configuration)' == 'Debug_Staging'">
    <LogicalName>mipmap-xhdpi\Icon.png</LogicalName>
  </AndroidResource>
  <AndroidResource Include="Resources\mipmap-xxhdpi\Icon_Staging.png" Condition="'$(Configuration)' == 'Release_Staging' Or '$(Configuration)' == 'Debug_Staging'">
    <LogicalName>mipmap-xxhdpi\Icon.png</LogicalName>
  </AndroidResource>
  <AndroidResource Include="Resources\mipmap-xxxhdpi\Icon_Staging.png" Condition="'$(Configuration)' == 'Release_Staging' Or '$(Configuration)' == 'Debug_Staging'">
    <LogicalName>mipmap-xxxhdpi\Icon.png</LogicalName>
  </AndroidResource>

  <AndroidResource Include="Resources\mipmap-hdpi\Icon.png" Condition="'$(Configuration)' == 'Release_Production' Or '$(Configuration)' == 'Debug_Production'" />
  <AndroidResource Include="Resources\mipmap-mdpi\Icon.png" Condition="'$(Configuration)' == 'Release_Production' Or '$(Configuration)' == 'Debug_Production'" />
  <AndroidResource Include="Resources\mipmap-xhdpi\Icon.png" Condition="'$(Configuration)' == 'Release_Production' Or '$(Configuration)' == 'Debug_Production'" />
  <AndroidResource Include="Resources\mipmap-xxhdpi\Icon.png" Condition="'$(Configuration)' == 'Release_Production' Or '$(Configuration)' == 'Debug_Production'" />
  <AndroidResource Include="Resources\mipmap-xxxhdpi\Icon.png" Condition="'$(Configuration)' == 'Release_Production' Or '$(Configuration)' == 'Debug_Production'" />
</ItemGroup>
<ItemGroup>
  <Folder Include="Resources\mipmap-hdpi\" />
  <Folder Include="Resources\mipmap-mdpi\" />
  <Folder Include="Resources\mipmap-xhdpi\" />
  <Folder Include="Resources\mipmap-xxhdpi\" />
  <Folder Include="Resources\mipmap-xxxhdpi\" />
</ItemGroup>

So now the correct icons will be included and their names will be changed to Icon.png because we added LogicalName element. Because of LogicalName element in .csproj we don’t have to edit c# attribute that is decorating MainActivity class.

We can now run the app and see that indeed launcher icon is different based on build configuration. That’s great but we still want to change application id so we can have multiple versions installed side-by-side. Keep on reading!

Updating AndroidManifest.xml file

Now we are going to write a simple bash script that will change application id in AndroidManifest.xml based on build configuration. It appears that it isn’t possible to have multiple manifest files and conditionally include only one of them (like we did with Info.plist on iOS). I suspect it is because Xamarin generates parts of AndroidManifest.xml on build it self. I tried to do that without any luck but if you now a way of how to accomplish this please let me know 🙂 Because of this limitation we will have to update the content of main manifest file. Note that as I mentioned we will write bash script so it will not run on Windows, but I think it shouldn’t be too hard to port it over.

First create new file called GenerateAndroidManifest and run chmod +x on it to make it executable. You can add this file in Finder not through IDE because it is not necessary to include it to a project. This is the content of newly crated file:

#!/bin/sh

# Save build configuration passed by IDE
buildConfiguration=$1

# Get package value from original AndroidManifest.xml file
applicationId=$(xmllint --xpath "string(//manifest/@package)" AndroidManifest.xml)

# newApplicationId is original application id for now
newApplicationId=$applicationId

# declare all possible postfixes
developmentPostfix="_development"
stagingPostfix="_staging"

# remove previously added postfix
if [[ $newApplicationId == *$developmentPostfix ]]; then
	newApplicationId=${applicationId%$developmentPostfix}
elif [[ $newApplicationId == *$stagingPostfix ]]; then
	newApplicationId=${applicationId%$stagingPostfix}
fi

# Append appropriate postfix to applicationId
if [[ $buildConfiguration = "Debug_Dev" ]] || [[ $buildConfiguration = "Release_Dev" ]]; then
	newApplicationId+=$developmentPostfix
elif [[ $buildConfiguration = "Debug_Staging" ]] || [[ $buildConfiguration = "Release_Staging" ]]; then
	newApplicationId+=$stagingPostfix
fi

# Replace all occurences of application id in AndroidManifest.xml
sed -i '' -e "s/$applicationId/$newApplicationId/g" AndroidManifest.xml

The script will append a postfix of either _development or _staging to the original application id depending on the configuration.

We want this script to run before every build regardless of the configuration so we can manually add a custom command near the top of our .csproj file:

<CustomCommands>
  <Command>
    <type>BeforeBuild</type>
    <command>/bin/sh GenerateAndroidManifest ${ProjectConfigName}</command>
    <workingdir>${ProjectDir}/Properties</workingdir>
  </Command>
</CustomCommands>

This should go into PropertyGroup without any conditions because again we want this to run on every configuration.

We could very easily extend this script to modify/add more values like name of the app for example.

So now before every build all occurrences of application id (package) in AndroidManifest.xml will be replaced based on build configuration passed as an argument and we don’t have to edit manifest file manually.

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 Android app! If you want to find out how to do the same thing on iOS you are in luck because I’ve covered that in my previous 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

2 comments On White-labeling Xamarin Android apps

Leave a reply:

Your email address will not be published.

Site Footer

Sliding Sidebar

About Me

About Me

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

Social Profiles