Resolving the .NET MAUI “VersionCode 1.0 is invalid. It must be an integer value.” error when updating Visual Studio 2022 Preview

I installed the Visual Studio 2022 Preview 6 this evening. I had been using Preview 4. I installed Preview 5, but didn’t have a chance to play with it. I have a simple demo app that I have working with, a basic stopwatch type of app. That app had been created with Preview 4 and it more or less worked fine (unless you counted Mac Catalyst and Windows). After I installed Preview 6, I tried to run the project on Android. It failed to compile with the following error message:

Severity	Code	Description	Project	File	Line	Suppression State
Error	XA0003	VersionCode 1.0 is invalid. It must be an integer value.
Parameter name: VersionCode	StopwatchMaui	....\StopwatchMaui\obj\Debug\net6.0-android\android\AndroidManifest.xml

As a test, I created a new .NET MAUI app from Preview 6. It compiled and ran just fine.

So who now, what now? When I first saw the error, I didn’t pay too much attention to the full path, just the file name. With .NET MAUI, there is an AndroidManifest.xml in the android platform folder.

And we take a look at the file, it’s pretty standard, pretty boring AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
	<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="31" />
	<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

No versionCode there. What’s going on? So I went back and actually read the error message and it was complaining about a version of AndroidManifest.xml located in obj\Debug\net6.0-android. That little fellow looks like this:

<?xml version="1.0" encoding="utf-8"?>
<!--
    This code was generated by a tool.
    It was generated from ....\StopwatchMaui\Platforms\Android\AndroidManifest.xml
    Changes to this file may cause incorrect behavior and will be lost if
    the contents are regenerated.
    -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1.0" package="com.companyname.StopwatchMaui" android:versionName="1.0.0">
  <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="31" />
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true" android:name="crc64c1104ba8f6ea44b3.MainApplication" android:label="StopwatchMaui" android:debuggable="true" android:extractNativeLibs="true">
    <activity android:configChanges="orientation|smallestScreenSize|screenLayout|screenSize|uiMode" android:theme="@style/Maui.SplashTheme" android:name="crc64c1104ba8f6ea44b3.MainActivity" android:exported="true">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
    <receiver android:enabled="true" android:exported="false" android:label="Essentials Battery Broadcast Receiver" android:name="crc64192d9de59b079c6d.BatteryBroadcastReceiver" />
    <receiver android:enabled="true" android:exported="false" android:label="Essentials Energy Saver Broadcast Receiver" android:name="crc64192d9de59b079c6d.EnergySaverBroadcastReceiver" />
    <receiver android:enabled="true" android:exported="false" android:label="Essentials Connectivity Broadcast Receiver" android:name="crc64192d9de59b079c6d.ConnectivityBroadcastReceiver" />
    <activity android:configChanges="orientation|screenSize" android:name="crc64192d9de59b079c6d.IntermediateActivity" />
    <provider android:authorities="com.companyname.StopwatchMaui.fileProvider" android:exported="false" android:grantUriPermissions="true" android:name="xamarin.essentials.fileProvider">
      <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/xamarin_essentials_fileprovider_file_paths" />
    </provider>
    <activity android:configChanges="orientation|screenSize" android:name="crc64192d9de59b079c6d.WebAuthenticatorIntermediateActivity" />
    <service android:name="crc64396a3fe5f8138e3f.KeepAliveService" />
    <provider android:name="mono.MonoRuntimeProvider" android:exported="false" android:initOrder="1999999999" android:authorities="com.companyname.StopwatchMaui.mono.MonoRuntimeProvider.__mono_init__" />
  </application>
</manifest>

If we look at line 8, we see the culprit

<manifest 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    android:versionCode="1.0" 
    package="com.companyname.StopwatchMaui" 
    android:versionName="1.0.0">

In the new app that was created in Preview 6, the same file had the following line:

<manifest 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    android:versionCode="1" 
    package="com.companyname.StopwatchMaui" 
    android:versionName="1.0.0">

So why is the first one bad and the second one good? In the wacky world of Android, android:versionCode has to have an integer value. This is documented here. So now we know what is the actual error, the next question is why that error occurred.

We can’t just edit the obj\Debug\net6.0-android\AndroidManifest.xml file and call it a day. The next time you rebuild the app, that file gets generated from Platforms\Android\AndroidManifest.xml. And apparently it pulls in information from somewhere else as well.

So I took a look at the .csproj files for the working and non-working apps. In the .csproj file generated by Preview 4, the version information was defined with the following two lines

<!-- Versions -->
<ApplicationVersion>1.0</ApplicationVersion>
<AndroidVersionCode>1</AndroidVersionCode>

With the new project freshly generated by Release 6, the same two lines were now a single line, with the ApplicationVersion now set with an integer value.

<!-- Versions -->
<ApplicationVersion>1</ApplicationVersion>

When I changed the “Versions” lines .csproj to match the single line used in the new .csproj Preview 6, the app compiled and deployed to Android. My best guess is that AndroidVersionCode was being used in Preview 4 and sometime after that, they made the breaking change to ApplicationVersion and jettisoned the AndroidVersionCode setting. It’s a preview release of Visual Studio and they are still baking .NET MAUI. This kind of stuff happens and the end result is a better product.

Add a POSH ADB to your Windows Terminal

Windows Terminal is so close to being out of beta. It’s been my default CLI on Windows for about a year. I still don’t think in PowerShell, but I try to use PowerShell as my default shell. I just love how you can configure Terminal.

The default shell for me is PowerShell Core, aka Powershell 7.0. Out of the box, it doesn’t have ADB on the path. When I’m doing Android stuff, I want the ADB. But I don’t want it to be on the path by default. Just like Visual Studio has the “Android ADB Command Prompt” menu option, I wanted to add shell option to Terminals so I can spin up a new PowerShell, but with ADB support. This is what I ended up with:

PowerShell with ADB goodness.

The first thing I did was to create a PowerShell script that just adds the ADB tooling to the path. My first attempt was:

$NeedsAdb = $true

foreach ($p in $env:Path.Split(";"))
{
    if ($p -match "android-sdk\\platform")
    {
        $NeedsAdb = $false
        break
    }
}

if ($NeedsAdb)
{
    write-host "Adding Android SDK Platform tools to path"
    $env:Path += ";C:\Program Files (x86)\Android\android-sdk\platform-tools"
}

It walks through the path and checks to see the folder with the ADB bits is already there.  If not, it gets added. That worked but seemed like a lot. I did a quick refactor and got it down to this

$NeedsAdb = $true

$env:Path.Split(";") | ForEach-Object {
    if ($_ -match "android-sdk\\platform") {
        $NeedsAdb = $false
        break
    }
}

if ($NeedsAdb) {
    write-host "Adding Android SDK Platform tools to path"
    $env:Path += ";C:\Program Files (x86)\Android\android-sdk\platform-tools"
}

A little cleaner, but still too much.  I was taking the path, splitting it up into an array of strings, and then testing each string.  But wait, the path is a string.  That made it simpler.  Instead of doing a string match on each folder the path, I can make one string match against the whole thing.

if ($env:Path -NotMatch "Android\\android-sdk\\platform")
{
    write-host "Adding Android SDK Platform tools to path"
    $env:Path += ";${env:ProgramFiles(x86)}\Android\android-sdk\platform-tools"
}

So I called that one add-adb.ps1 and saved it to a common folder that I put scripts in.  Next, I wanted a cosmetic tweak so that I knew which shell has the power of ADB.  I went to materialdesignicons.com and did a search on “Android”.  I found an icon named “android-debug-bridge”, which was perfect.  I downloaded the .svg version of the icon and then made a .png file out of it with PhotoShop.  I named it android-debug-bridge.png.  Then I made a smaller version to be the icon. You can grab the images and the .ps1 file from the following Gist link: https://gist.github.com/anotherlab/364e3805d9ea56b574b394127acc9aa6

Now that I had the script and the images, it’s time to add a new shell profile.  From within Windows Terminal, select “Settings” from the drop-down menu.  You can also press the CTRL+, shortcut.  This will load the settings.json file in the text editor of your choice.  I have Visual Studio Code registered as the default app for JSON files.  You can use a lesser editor, but that’s on you.

There will be an array of objects named “list”.  These objects are the different shells that can be run from within Windows Terminals.  I added the following item to the list array

 

"list":
[
    {
        "guid": "{50caca3f-bff1-4891-b7f7-e3a05c040003}",
        "fontFace":  "Cascadia Code PL",
        "backgroundImage": "d:/grfx/android-debug-bridge.png",
        "backgroundImageStretchMode": "uniform",
        "backgroundImageOpacity": 0.15,
        "hidden": false,
        "name": "ADB PowerShell",
        "icon": "d:/grfx/adb-32.png",
        "commandline": "pwsh.exe -noe -c D:/scripts/add-adb.ps1"
    },
]

Let’s go over this line by line.

FieldValue
guidIt wants a GUID, so just get one.  I used guidgenerator.com, but any GUID generator will do.
fontFaceI’m using the Powerline version of Cascadia.  More on that in a bit
backgroundImageGrab it from my Gist or use your own.
backgroundImageStretchMode Size the image to fit the Shell window
backgroundImageOpacity I set it to be mostly transparent
hiddenIf you want to hide this from the list of shells, just set it to True
nameCall it what you want
iconThe tab icon is optional, but you can grab it from my Gist.
commandlineThis is what gets launched. The “-c” option says to run the next parameter and the “-noe” says not to exit after running the command

And that lets me spin up a new PowerShell with ADB on the path.  Mixing PowerShell with ADB makes it easier to do ADB commands that would normally be clunky.  For example, I want to test some Android code that would access the photo gallery.  When you new up a new Android emulator, there are no images.   ADB lets copy files to the emulator’s filesystem, but it doesn’t do wild cards.

You may have notice the “/” slash being used instead of “\” for file paths. Windows Terminal lets you use the “/” as the directory separator and this avoids having to use “\\” to get a single “\” in.

I have a folder with a bunch of images that I wanted to copy to an Android Emulator image.  I then run the following command from PowerShell:

Get-ChildItem .\*  -Include *.jpg,*.png | Foreach-Object {adb push $_.Name /sdcard/Pictures}

Get-ChildItem gets a directory listing and I use the -Include parameter to only include the files that have the .jpg and .png extension. If I didn’t need to filter the files list, I could the aliases for Get-ChildItem of gci or ls. I then pipe the results into Foreach-Object. This will execute everything in the {} block for each item. $_.Name is the name of the file and that gets passed to adb push to copy that file to /sdcard/Pictures in the emulator. There’s a bit of typing, but it does save time when trying to copy a set of files over to Android.

About that “Cascadia Code PL” font face.  I’m running a theming engine inside PowerShell called “Oh-my-posh”.  I’ll do a longer post on it in the future, but the short story is that it makes the PowerShell prompt contain the current git status for the current folder.  Read about it and get it here.  On the Mac, I use Oh-my-zsh to get a souped-up zsh shell.  Scott Hanselman did a good write up of Oh-my-posh here.

Oh-my-posh uses Powerline Glyphs (originally defined here) as part of the status display.  So you’ll need a font that includes the Powerline Glyphs.  Microsoft’s Cascadia Code font has a version with Powerline and you can get it here.  Here’s what that looked like when I was adding the files to the Gist

You can see the color and text information change as I used git to add the icon to the Gist. When working with git, it’s very handy to easily see which branch you are working with and the current status of that branch.