MAUI Sounds Amazing. Is It?
You're ready for .NET MAUI, but is it ready for you? In this post, I share my real-world experience with Microsoft's latest write-once deploy-anywhere solution.
Technology vendors have promised write-once, deploy-everywhere solutions to developers since time immemorial.
It seems to be an especially hard problem for user interfaces, particularly when accounting for both mobile and desktop platforms.
For instance, remember JavaFx? It was a Java-based solution that was supposed to deploy to Windows, Mac, Linux, Symbian, and Windows Mobile. I recall seeing that technology fail on virtually every platform in a live demo at a keynote at the JavaOne conference in 2009. Ouch.
Is MAUI any different?
For one thing, the MAUI demos consistently look strong and the "File->New" experience works well. But I've long since learned to never trust even the best demo or File->New experiences.
Fortunately, over the last few weeks, I've had the pleasure of working professionally on a .NET MAUI project and I think I've seen enough to help answer the question of whether .NET MAUI is ready for your next project.
What is MAUI?
First of all, in case you aren't familiar, the .NET Multi-platform App UI (MAUI) is the successor to Xamarin that allows writing one UI that can deploy to Mac, Windows, Android, and IOS. There's no real Linux support yet (unless you count Tizen), but with Microsoft and Canonical being BFFs I bet it won't be long.
MAUI is much more. First of all, it natively deploys to both desktop and mobile. Even better, MAUI renders as native controls on each platform. That means your app looks and behaves like a native Mac app on Mac (via AppCatalyst), a native Android app on Android, a native Windows app on Windows, etc.
But that's not all. While the native experience is the default option, MAUI also offers a Blazor control whereby you can embed some or all of your app in a web browser. Using that option you'd get an identical experience across iOS, Mac, PC, and Android and could even deploy the same app to the web too!
Even cooler you can mix and match native controls with the Blazor control for hybrid UI solutions. A single UI and back-end codebase in C#, the best language ever: it sounds too good to be true.
What follows are three takeaways from the real world for how it actually stands up.
Takeaway #1: It Actually Works!
Before I go any further, let me just say MAUI largely works as promised. Our project had a custom UI deploying to Mac and Windows. We had a dev team with a mix of Windows and Mac developers. Each app looked and felt pretty native with the exact same UI codebase.
Unfortunately, this app didn't need to target Android, iOS, or Web platforms, so I can't confidently comment on how well MAUI works on mobile just yet. But Xamarin has worked fine on mobile in the past, and the File->New experience works well, so I'm confident mobile works just as well as desktop.
The fact that it works on both PC and Mac though is awesome enough that any subsequent griping feels like bikeshedding by comparison. That said I'm going to share some of my initial frustrations anyway, to hopefully help others avoid some of my pain.
Takeaway #2: Desktop UI Not Fully Baked
It's not just how a button looks that makes a native app native: it's also the little things like keyboard shortcuts, animations, and design paradigms. For instance, a material design UI with a big floating plus button just doesn't look right on iOS. MAUI solves some of these problems, but it also misses the mark some, particularly on its newer platforms: Windows and Mac.
For one thing, I discovered it was nearly impossible to get the cursor to change to a hand on mouseover of selectable elements. Giving users a hint about interactive elements is important, but it didn't work on Mac or PC. That's probably because MAUI has its roots in mobile.
Another issue is that I found it extremely challenging to fire off an animation when users click a button but before they navigate, particularly when using MVVM architecture (more on that later). These features would be easy or free in a native app (e.g., Win UI), but the desktop platforms just felt less fully baked in the current version (MAUI 7).
The solutions undoubtedly involve a custom renderer or handler, but the problems weren't quite big enough to warrant that investment just yet. After all, a subsequent version of MAUI will probably fix these issues. But attempting to solve it did give me the opportunity to fiddle with platform-specific code, which is a great strength.
If you aren't familiar with the location of the platform-specific elements in MAUI is awesome and a huge improvement over Xamarin.
This folder-based approach to platform-specific code is particularly awesome because Xamarin used to require a separate project for each platform.
While that approach is helpful, it wasn't enough to solve my problems easily. So, keep in mind if you're looking for a pixel-perfect truly native app, it may be a large time investment, and you might even be better off writing your app from scratch for each platform. If you're ok with a good enough UI: MAUI (version 7) is for you.
Takeaway #3: Quirky
In my experience, Xamarin was pretty quirky. MAUI is kinda green, so I expected it to be no less so. I wasn't disappointed. Here's a list of 3 quirks to be aware of if you're starting a new project.
Dependency Injection Helps and Hurts
I love how dependency injection is a first-class citizen in MAUI. Unfortunately, if there are any problems with DI, MAUI falls over on startup without any warning of what went wrong.
On Mac it just fails. It looks like this in Visual Studio on Windows:
This program has exited with code 3221226107 (0xc000027b).
These can usually be quickly solved with something like:
// don't forget to register both the page _and_ the ViewModel builder.Services.AddTransient<MainPage>(); builder.Services.AddTransient<IMainPageViewModel, MainPageViewModel>();
MauiProgram.cs. That first line is a mild gotcha. It's because MAUI requires registering pages to get DI working, even though they worked fine prior to using DI. Unhelpful errors, but recognizable once you get used to them.
More Unhelpful Error Messages
Continuing the trend of unhelpful error messages there's a checkbox on MAUI's file->new project dialog that if you click it will ruin your day. That button accidentally got clicked at some point and it started our project with the solution and the project in the same directory.
Placing the solution and the project in the same directory worked out fine at first, but when we later went to add a library, we temporarily put that library in a child directory until the team got to a good stopping point for the big move refactor of the UI project.
Except it failed. We got tons of unhelpful errors like this:
IDE1100 Error reading content of source file 'C:\dev\Lprichar.MyMauiApp\Lprichar.MyClassLibrary\obj\Debug\net7.0\Lprichar.MyClassLibrary.AssemblyInfo.cs' -- 'Could not find file 'C:\dev\Lprichar.MyMauiApp\Lprichar.MyClassLibrary\obj\Debug\net7.0\Lprichar.MyClassLibrary.AssemblyInfo.cs'.'.
Eventually, we figured out the problem and sucked it up and immediately moved the MAUI and class library projects into sibling folders.
You probably won't run into this exact problem but just remember MAUI is a new platform with some sharp and painful edge cases, so reserve plenty of time to solve inexplicable and unhelpfully worded errors.
CommunityToolkit.Mvvm + Testing
If your project will have any complexity, I strongly recommend the MVVM (Model View ViewModel) pattern and the MVVM Community Toolkit (big thank you to Steve Maier for introducing the Community Toolkit to our project and putting up with my initial grumbling about it 🙃).
Without getting into too much detail, MVVM will allow a cleaner architecture and a better separation of responsibilities, and the MVVM Community Toolkit will significantly simplify the work required to accomplish that cleaner architecture.
However, if you have both a Maui App and a Library project, and you want to test elements from both in a unit test project then you're liable to end up with a ton of warnings like this:
Warning CS0436 The type 'ColorAnimationExtensions_Button' in 'CommunityToolkit.Maui.SourceGenerators\CommunityToolkit.Maui.SourceGenerators.Generators.TextColorToGenerator\ButtonTextColorTo.g.shared.cs' conflicts with the imported type 'ColorAnimationExtensions_Button' in 'Lee.MyMauiApp, Version=18.104.22.168, Culture=neutral, PublicKeyToken=null'. Using the type defined in 'CommunityToolkit.Maui.SourceGenerators\CommunityToolkit.Maui.SourceGenerators.Generators.TextColorToGenerator\ButtonTextColorTo.g.shared.cs'.
If that happens, I'd recommend taking the approach recommended by IRT employee Ed Snider in his book Mastering Xamarin.Forms, and move the ViewModels into the library. Any UI or platform-specific code can be implemented in the UI project and dependency injected by interface into the library. Then you can remove the reference to the UI project entirely from the test project. That removes warnings, simplifies the architecture, and keeps business logic separate from user interface code.
So, is .NET MAUI ready for the real world on your next project? If you need a cross-platform UI solution that can run on any or all of Android, iOS, Mac, Windows, and potentially the Web, and you're prepared for a bit of a bumpy ride then I would say absolutely yes.
I'd keep a sharp eye on the next release, but I can confidently say that the fundamentals of MAUI are strong, and it actually solves an extremely hard problem. Once you get used to the quirks, you'll appreciate that it does what it promises. Ultimately, it's ready for you, if you're ready for it.