Ported mobile apps to .NET MAUI 8!

Check out Airport Guides and Baseball Odds for iOS and Android*!

I don’t remember exactly why I embarked on the project to upgrade my apps to use .NET 8 from .NET 6. It is true that .NET 6 is pretty old at this point, and I think Microsoft has dropped support for it, so…maybe that’s why? But I suspect .NET 6 can’t target newer versions of iOS/Android, and either Apple or Google is requiring that for app updates.

Anyway, after porting them from Xamarin.Forms to MAUI just over a year ago, my hope was that I was on the happy path and upgrading would be easy. There’s even a handy guide for upgrading from .NET 6 to 7 and from .NET 7 to 8!

Sadly, no. Here is roughly what happened in chronological order:

  • At some point, I ignored the upgrade guide and used a hard coded version of Microsoft.Maui.Controls instead of the $(MauiVersion) that the upgrade guide from .NET 7 to 8 told me to do. I don’t know why I did this, but things broke in surprisingly weird ways.
  • Somehow ImplicitUsings stopped working so every single code file was broken and I had to add a bunch of using statements. Annoying but reasonably easy to fix once I figured out what was going on. (it helps that these apps are fairly small)
  • I got a bunch of errors about invalid paths for images – I tried removing some duplicate images that weren’t used but still had a few of these. I tried some of the suggestions in this GitHub issue and this documentation to no avail.
  • At some point I had the bright idea to create an entirely new .NET MAUI 8 app and look at what it was doing in its .csproj file. This gave me a pointer to adding <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" /> which helped with some problems.
  • Out of desperation, I started to delete the bin/ and obj/ directories after trying stuff instead of just doing a clean build, and frustratingly this helped with some problems. This meant that trying stuff out took even longer because I’d have to do this all the time 🙄

I got stuck here for a while and was feeling despair about all this. It seems like every few years I have to rewrite these dang apps to use the newest app framework (see a history here), and I don’t have a lot of free time or energy.

But taking a break gave me an idea about a new approach. I decided to:

  • Make a new .NET MAUI 8 project from a template
  • Ensure that it builds (it did!)
  • Add the handful of NuGets that I use
  • Ensure that it still builds (it did!!)
  • Instead of copying all my code into this new project, look carefully at the resulting .csproj and make my existing .csproj match as closely as possible

And this worked shockingly well! For what it’s worth, a lot of what I changed was getting rid of Compile, DependentUpon, and MauiXaml tags – I guess these are mostly implicit now?

I did hit a few more gotchas. One was this error when building Android:

1>C:\Users\greg\.nuget\packages\xamarin.build.download\0.11.0\buildTransitive\Xamarin.Build.Download.targets(60,4): error MSB4064: The “AndroidFixManifests” parameter is not supported by the “XamarinDownloadArchives” task loaded from assembly: Xamarin.Build.Download, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null from the path: C:\Users\greg\.nuget\packages\xamarin.build.download\0.11.4\buildTransitive\Xamarin.Build.Download.dll. Verify that the parameter exists on the task, the <UsingTask> points to the correct assembly, and it is a settable public instance property.

1>C:\Users\greg\.nuget\packages\xamarin.build.download\0.11.0\buildTransitive\Xamarin.Build.Download.targets(52,3): error MSB4063: The “XamarinDownloadArchives” task could not be initialized with its input parameters.

This is one of the errors that deleting the bin/ and obj/ directories would usually fix. Later I upgraded this NuGet to 0.11.4 and I think that made this go away for good, as per this Stack Overflow post.

Another problem was Could not find workload 'ios' extended by workload 'maui-ios' in manifest 'microsoft.net.sdk.maui' which I could fix with the tips on this GitHub issue.

Once I got to the point of building the iOS app, I quickly discovered that the Mac Mini I had (from 2016) was too old to install the needed version of Xcode. I knew this day was coming eventually, as the Mac Mini predated the switch to ARM, and was also painfully slow, so I bought an Mac Mini M1 off of eBay. And it is so much faster than the old one! And it seems like some of the more annoying issues have gone away.

The last problem I ran into was that the app would crash on launch in some Admob ads code. The packages I had been using for Admob stuff dated back to the Xamarin days and I wasn’t sure if they were expected to work or what. And debugging that stuff is especially painful. I slowly convinced myself that spending a bit of money on the MtAdmob plugin for MAUI was worth it, and when I downloaded the free version and it worked on the first try I was sold. All told I spent more money than I had hoped, but the apps do all right and should pay for both of those purchases in six months or so.

So now my apps are fully up to date, and maybe I’ll spend a little time actually improving them instead of fighting with app frameworks! (although apparently Apple has a privacy manifest that I need to be using to make any updates past May 1st? sigh…)

* technically Airport Guides for Android isn’t live yet, but it hopefully will be in a day or so, and I’m writing this now to get some catharsis!

New project: simple crossword puzzle solver!

Here’s the new crossword puzzle solver!

Since writing my Wheel of Fortune solver two years ago, it’s now the fifth most popular page on my website, which is neat and also a little surprising! (how many people are playing Wheel of Fortune out there?) This project uses the same word list and just gets rid of the rule that once you’ve used a letter you know you have all of those letters revealed. It’s very mechanical; it doesn’t take into account the crossword puzzle clue itself, just the pattern of letters you know.

(for the record, the most popular website pages over the last month are:)

How to improve iOS app launch time by 3x with one incredibly obvious trick (and: done with MAUI ports!)

This week I finished porting Baseball Odds (MAUI edition) to iOS! After the tribulations of porting Airport Guides to MAUI on iOS, it was relatively straightforward.

While doing that, I found the solution to a few annoyances I had run into porting Airport Guides to MAUI on iOS. So I decided to fix those small things and release a new version of Airport Guides, which would presumably be very quick to do.

As a reminder, I had to disable LLVM optimizations for the release build in order for my underpowered Mac Mini to be able to build without timing out. After doing this, I saw that the MAUI version of Airport Guides had a significantly larger install size (122 MB vs 57 MB) than the previous non-MAUI version, and the MAUI launch time was ~2.5 seconds as compared to under a second for non-MAUI. I shrugged and figured this was a combination of MAUI overhead and not being able to use optimizations and that there wasn’t much I could do about it.

Welllllll, it turns out during my efforts to get things building I had somehow turned off debugging for Debug builds and turned on debugging for Release builds. And submitted it to the App Store that way! And it got approved!

So, the latest release of Airport Guides has a more reasonable install size (82 MB) and is back to launching in under a second. And I feel just a bit dumber 🙂

Airport Guides iOS: MAUI edition is live!

After the breakthrough last time, I’m very excited to have the MAUI version of Airport Guides for iOS (version 1.1.41) live in the App Store!

I had to deal with the following things to get it in shipping condition:

  • Actually making the splash screen in Inkscape. Which worked pretty well, except the .svg is square and obviously the phone’s screen is rectangular and I think the font was a little different too, so it doesn’t look quite right. But, you know, better than nothing!
  • The list of airport delays did some weird things – sometimes it would be shifted halfway across the screen, sometimes it would be very very narrow. Some quick searching turned up this GitHub issue with an easy workaround of inserting a Grid between the RefreshView and the CollectionView. (these are the kinds of Xamarin/MAUI issues I’m used to working around, which is why the splash screen thing was so frustrating!)
  • Trying to send email was failing with an exception, which turned up this GitHub issue saying that sending email doesn’t work if the user doesn’t have their email set up in the Apple email client or something? That seems pretty bad to me; I want to try a simpler mailto: link but the functionality isn’t that important and I really wanted to get this release out the door, so it can wait until next time.
  • I spent an entire evening (well, one of my evenings, meaning 2 hours) trying to build a release build, and could not do it. It kept getting errors doing the build in ways I hadn’t seen before, which is saying something since I am becoming quite the connoisseur of ways for a Visual Studio Pair to Mac build to fail! It seemed to be a timeout, though. Eventually I guessed that maybe my 10 year old extremely underpowered Mac Mini just might not be able to finish a build in time for some internal timeout. So I looked into the options I had investigated before (under “Getting a Mac”), and while MacInCloud now only costs $20/month instead of $35/month if you only use it four days during the month (or something like that? It was confusing), that was still a lot.

    I hadn’t had this problem at all with debug builds, so I decided to try turning off using LLVM optimizations for the release build, and that was enough to do the trick. I’m not thrilled about shipping this way, but the app isn’t particularly complex, and the other options were worse!
  • I got everything working and submitted the build, and got back an email from Apple with a lot of errors about “missing purpose strings” in Info.plist for a bunch of permissions I didn’t think I was requesting. Some searching found this StackOverflow thread, which suggested that turning on some linking could help, so I did that and no more missing purpose strings!

    …except now the app crashed on launch. Luckily I could reproduce it in a debug build; I was getting a NotImplementedException from the in-app billing library I’m using saying “This functionality is not implemented in the portable version of this assembly.” Some more searching (sensing a theme here!) turned up a few issues like this with Android, but nothing with iOS. I suspected that maybe the linking was stripping out some necessary part of the library, and wondered whether there was a way to turn off linking for a particular assembly. It turns out there is, so I made sure the whole in-app billing library would get preserved. Sadly, that did not help, but while digging around in the in-app billing NuGet I noticed that it seemed to only have a version for iOS >= 16.0, while my app targets iOS >= 15.4. Reverting to an earlier version of the NuGet fixed the crash!
  • Another submission got failed because of some stuff with App Tracking Transparency that I have to support now since I’m targeting a newer iOS version. This took me a few tries – I had to add a purpose string, then later I had to move the request to the OnActivated() method. (thanks Stack Overflow!) This caused a few rejections from the App Store until I got it sorted out.

Another irritating thing is that, like last time, sometimes a full rebuild was necessary to make my changes get picked up. Which is the kind of thing that made me want to do a full rebuild every time, which is painful because full rebuilds are like 15 minutes long.

One bright spot is that I finally got TestFlight set up for Airport Guides; not sure why I didn’t do this before, because it was really easy to do (even from the iOS Connect app!) and was helpful for testing some launch stuff I was worried about.

Anyway, this is the kind of stuff that, while satisfying to fix, is just generally frustrating and not that interesting to work on. I’m hoping now that I have the app in a good state maybe I can actually improve it instead of chasing the next app framework, at least for a few years!


1 – The versioning on my apps are pretty arbitrary, but you can get a hint at some of my troubles by the fact that I thought 1.1.0 would be the version that got published 🙂 (back)