johnnys.newsjohnnys.news

by Joachim Leonfellner ✌️ i'm an indie dev in ❤️ with side-projects

Oct 17, 20231943 words in 13 min


Dots 2.0 - Farewell .NET MAUI 👋 Hello Avalonia

TL;DR

The frustration with .NET MAUI was too big, so I decided to rewrite Dots with Avalonia, which brought back the joy of development. It’s now available for Windows, macOS and will soon come to Linux. You can get the app from the https://github.com/nor0x/Dots/releases

2.0 ?!?

Where is 1.0? Well, there is no 1.0. I started working on Dots in December 2022 and released the first version in January 2023. It started as a holiday hack, but it turned out that not only I was interested in such a tool. I collected some feedback and feature requests and started working on an updated version. If you take a close look at the repository, you might noticed that there is no 1.x version. The initial version of Dots was based on .NET MAUI which made it not only impossible to run on Linux, but also made publishing for Windows a pain (it’s really a nightmare). Also, I was hoping that .NET MAUI would become more stable and feature complete while I was working on Dots, but it didn’t. When I updated from .NET 7 to .NET 8 the app would crash immediately. Turns out the BindingContext datatype of CollectionView was broken - lol. Also, MAUI lacks support for unpackaged app publishing which blocked a proper release of Dots from the beginning.

MAUI is currently in RC state and I didn’t expect it to still be this bad regarding stability and performance. But more on that in a separate post. Or just take a look at the post about publishing for Windows with .NET MAUI. It hasn’t really improved since then and I hope to be proven wrong, but currently I don’t see a bright future for MAUI. From the outside it looks like the project is totally mismanaged and clearly the community is not having a good time developing with MAUI - too many regressions, bugs and missing features. This all paired with stupid decisions like announcing the retirement of Visual Studio for Mac without a working alternative right now 🤦 But let’s not rant about MAUI, there are better places on the internet for that.

Avalonia

When my decision about a complete rewrite was made, I explored different options. The nice thing about MAUI of course is that the cross-platform API maps the native controls of the platforms, but I thought for this project this aspect is not that critical. I wanted to use a cross-platform UI framework that is stable, feature complete and has a good community. Of course it would be based on .NET since I already had the business logic abstracted from the UI and planned to reuse as much of that code as possible. I have worked with Uno Platform before and already shipped a big app with it. It’s a great project has an active community and is always up-to-date, so it would definitely be a great choice for Dots. But I was in the mood of trying something new and I had heard a lot of good things about Avalonia. I had never worked with it before, so I decided to give it a try. And I was not disappointed. The approach of the framework is different from .NET MAUI since it’s not based on native controls - instead every UI element is drawn and rendered consistently across all platforms. It also allowed me to port Dots to Linux with zero effort regarding UI. I just had to make sure that the business logic is working on Linux and that’s it. I was really impressed by the performance and stability of Avalonia. One thing that I couldn’t get to work was the XAML hot reload feature. Which is not that critical for me since their drawing technology enables an interactive previewer inside Visual Studio.

Avalonia Previewer

One more word about Avalonia vs. MAUI. Avalonia is just plain .NET you can use the standard cli commands to get things done - also no need to deal with workloads and stuff. Just compare the command for a simple start of the app (which is currently totally fails for MAUI). It’s astonishing how unnecessarily complex, unstable and slower the MAUI command is compared to the Avalonia one.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# Avalonia
dotnet run

# starts the app


# .NET MAUI
dotnet build -t:Run -f net8.0-windows10.0.19041.0

# output fails with:
MSBuild version 17.8.0-preview-23418-03+0125fc9fb for .NET
Determining projects to restore...
All projects are up-to-date for restore.
Unhandled exception. System.TypeInitializationException: The type initializer for '<Module>' threw an ex
ception.
---> System.TypeInitializationException: The type initializer for 'WinRT.ActivationFactory`1' threw an
exception.
---> System.Runtime.InteropServices.COMException (0x80040154): Class not registered (0x80040154 (REGDB_
E_CLASSNOTREG))
at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(Int32 errorCode)
at WinRT.BaseActivationFactory..ctor(String typeNamespace, String typeFullName)
at WinRT.ActivationFactory`1..ctor()
at WinRT.ActivationFactory`1..cctor()
--- End of inner exception stack trace ---
at WinRT.ActivationFactory`1.ActivateInstance[I]()
at Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentInitializeOptions..ctor()
at Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentManagerCS.AutoInitialize.get_Option
s() in C:\Users\i\.nuget\packages\microsoft.windowsappsdk\1.3.230724000\include\DeploymentManagerAut
oInitializer.cs:line 44
at Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentManagerCS.AutoInitialize.AccessWind
owsAppSDK() in C:\Users\i\.nuget\packages\microsoft.windowsappsdk\1.3.230724000\include\DeploymentMa
nagerAutoInitializer.cs:line 30
at .cctor()
--- End of inner exception stack trace ---
C:\Program Files\dotnet\sdk\8.0.100-rc.1.23455.8\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.targets(
1040,5): error MSB3073: The command "C:\maui_project\bin\Debug\net8.0-windows10.0.19041.0\win10-x64\MauiApp.exe " exited with code -532462766. [C:\maui_project\MauiApp.csproj::TargetFramework=net8.0-windows10.0
.19041.0]

Build FAILED.

C:\Program Files\dotnet\sdk\8.0.100-rc.1.23455.8\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.targets(
1040,5): error MSB3073: The command "C:\maui_project\bin\Debug\net8.0-windows10.0.19041.0\win10-x64\MauiApp.exe " exited with code -532462766. [C:\maui_project\MauiApp.csproj::TargetFramework=net8.0-windows10.0
.19041.0]
0 Warning(s)
1 Error(s)

Time Elapsed 00:00:02.90

dotnet run in MAUI land

The new Dots

From the outside the new Dots looks pretty much the same as the old one, with some UI tweaks. But on first launch you will notice a significant performance boost in the SDK list. Not only scrolling, but selection and filtering feels much more responsive.

Dots 2.0

In general, the code in Dots is structured in a MVVM pattern with services for the business logic. On top of the view models, I was already using the CommunityToolkit.Mvvm package which generates boilerplate MVVM code via source generators - this still works just fine in Avalonia. In the .NET MAUI based app there was some code in the code-behind of the views - this is still the case in the Avalonia version - and I’m totally fine with putting some UI related code there.

Maui.Essentials

Essentials are still one of the most convenient ways to access platform specific features in a cross-platform way. These are also the most stable APIs in .NET MAUI and I’m was using it for various things in Dots. There are currently various efforts to enable Essentials for Avalonia (for example https://github.com/AvaloniaUI/Avalonia.Essentials and https://github.com/AvaloniaUI/AvaloniaMauiHybrid), but at the time of this writing none of them is complete and works for Windows, macOS and Linux. I keep an eye on the Pull Request for Windows support in AvaloniaMauiHybrid. https://github.com/AvaloniaUI/AvaloniaMauiHybrid/pull/8. For now I had to change the implementation of some Essentials APIs to get Dots working on all platforms.

Preferences

Preferences was one of the most used APIs in Dots to quickly store and retrieve settings from the local storage. I replaced it with Akavache which is an awesome library based on SQLite3 for data in a key-value based format - works just fine with Avalonia.

Just an example of how I ported the code from Preferences to Akavache:

1
2
3
4
5
//Preferences
Preferences.Set(Constants.InstalledSdkSKey, JsonSerializer.Serialize(sdkData));

//Akavache
await BlobCache.UserAccount.InsertObject(Constants.InstalledSdksKey, JsonSerializer.Serialize(sdkData));

FileSystem

I was using FileSystem only as an easy way to get the apps data directory. I replaced it with the Environment.SpecialFolder.LocalApplicationData path which is working just fine on all platforms.

Browser

The Browser API was used to open the GitHub repository of the selected SDK or link to the release notes online. I replaced it with a simple Process.Start call which is working just fine on all platforms.

FileExplorer

Very similar to the Browser API a simple Process.Start call can be used to open the file explorer to a specific path on the three platforms.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
using var proc = new Process { StartInfo = { UseShellExecute = true, FileName = $"explorer", Arguments = path } };
proc.Start();

return;
}

if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Process.Start("xdg-open", path);
return;
}

Process.Start("open", path);

ObservableView

I’m using ObservableView to filter and query the SDK list in the application. In .NET MAUI I could just reference the NuGet package to get the functionality working. With Avalonia however I had to copy the source code of the library into my project and - otherwise it would not compile. The build would break with the following error:

1
MSBUILD: Avalonia error AVLN:9999: Member 'System.Collections.ObjectModel.ObservableCollection`1' is declared in another module and needs to be imported

Once I copied the source code into my project it compiled just fine. I’m not sure if this is a bug in Avalonia or if it’s because of the Shared Project architecture in ObservableView. As soon as I have some time, I will investigate this further and create a PR for ObservableView with a fix.

Linux

As mentioned before, Avalonia enabled me to port Dots to Linux with very little effort. The UI and most of the SDK interaction is working fine. But there are also some things that need to be changed in the DotnetService regarding installation and uninstallation of SDKs. I will track the Linux support in a separate issue on GitHub for full transparency because I’m not really a Linux guy so maybe there are some things missing.

Usage

You can still of course just clone the repository and build the app yourself. I also provide a prebuilt version for Windows, macOS and soon Linux. You can get the latest release from GitHub releases or from the Dots website https://h3y.studio/dots.

Conclusion

The move from .NET MAUI to Avalonia was a good decision. I’m really happy with the result and I’m looking forward to adding more features to Dots in the future. It’s again a joy to work on this project and I hope you like it too. If you have any feedback or feature requests, please let me know on GitHub