After K2, the Kladde runs in the browser. This course gives it a second interface: an app for phone and desktop, written in C# and XAML
from a single code base. The tool for that is called .NET MAUI. This lesson clarifies what it is and what it isn’t, sets up your
computer, and starts the template on an Android emulator and on Windows. At the end, src/Kladde.App sits next to the projects from
K2, and you’ve seen with Hot Reload how quickly you can change an interface in this type of project.
The course assumes K2: C# and async, the Kladde’s web API with its sign-in, the booking view as a Blazor component, and xUnit. This
course doesn’t repeat any of that. Where it builds on it, you’ll find a reference.
| Module | Lesson | What you build |
|---|---|---|
| W2.1 | W2.1.1 .NET MAUI at a glance | the project Kladde.App, startable on Android and Windows |
| W2.1 | W2.1.2 XAML, controls and layouts | contact list and today’s appointments as pages with Shell |
| W2.2 | W2.2.1 MVVM | the pages as view models, tested without a device |
| W2.2 | W2.2.2 Data: API, offline, stored securely | appointments from the web API, even without a network |
| W2.3 | W2.3.1 Blazor Hybrid | the booking view as a shared component |
| W2.3 | W2.3.2 Testing, shipping, distributing | an Android package and an MSIX package |
What .NET MAUI is
.NET Multi-platform App UI, or .NET MAUI for short, is a framework for native mobile and desktop apps with C# and XAML. From one shared code base you get apps for Android, iOS, macOS and Windows. It’s open source and the successor to Xamarin.Forms, extended from mobile to the desktop, with controls that were built from scratch.
What’s important to understand is what MAUI doesn’t do: it doesn’t draw its own interface that looks the same everywhere. Your code talks to the layer of MAUI controls, and that layer uses the controls of the respective platform. When necessary, your code can also call the platform directly. Underneath is the .NET class library, which is the same for all platforms; that’s why business logic, tests and models can be shared.
| Target platform | How it’s built | Runtime and translation |
|---|---|---|
| Android | .NET for Android | C# is compiled to IL and translated to native code when the app starts (JIT) |
| iOS | .NET for iOS | compiled completely ahead of time (AOT) to native ARM code |
| macOS | Apple’s Mac Catalyst: the iOS app, built with UIKit, runs on the desktop, supplemented by AppKit and platform calls | Mono |
| Windows | Windows UI 3 (WinUI 3) | the .NET runtime (CLR) |
For Android, iOS and macOS the runtime is Mono, according to Learn; for Windows it’s the .NET Core CLR. For this course, it’s enough that you practice on Android and Windows and see the same interface on both.
What MAUI brings
- controls to display data, trigger actions, present collections and choose values,
- a layout engine and several page types, including Shell for navigation,
- data binding, which you need for MVVM in W2.2.1,
- cross-platform interfaces for device features: sensors, network status, device information, clipboard, file picker, securely stored key-value pairs, text to speech, and sign-in flows in the browser that return to an app address,
- a drawing surface for your own graphics,
- one project for all platforms, and Hot Reload.
You use the interfaces for network status and secure storage in W2.2.2.
Who it’s for and what for
Learn gives three reasons for MAUI: you write cross-platform apps in XAML and C# from one code base, you share layout and design, and you share code, tests and business logic. For the Kladde, that means: the rules you already have from K1 and K2 stay C#. What’s new is the interface.
What MAUI isn’t in this course
- Not a school for iOS. iOS needs a Mac on the network; the course describes the path, but the exercises run on Android and Windows.
- Not game development.
- Not an introduction to C# or Entra ID. You bring both from K0 through K2.
- Not a second website. If you want to use web technology in the app, W2.3.1 covers Blazor Hybrid.
Platform limits: what MAUI needs in .NET 10
For .NET 10, Microsoft Learn lists the following minimum requirements for the targets:
| Platform | At least |
|---|---|
| Android | Android 5.0 (API 21) |
| iOS | iOS 12.2 |
| macOS | macOS 12 through Mac Catalyst |
| Windows | Windows 11, or Windows 10 version 1809 or later, through WinUI 3 |
Apps with Blazor in MAUI (W2.3) have stricter limits: Android 7.0 (API 24), iOS 16.4 and macOS 12, plus a current WebView of the platform. Samsung also provides support for Tizen; it isn’t part of this course.
There are two things to take away right now. First: the .NET 10 templates set the value SupportedOSPlatformVersion for Android to 24
instead of 21. API 21 is still supported, but Learn recommends raising existing projects to 24 so that crashes from missing Java
features don’t happen. Second: for .NET 11, Learn names API 24 as the minimum for Android. The course stays with .NET 10; the number is
only a preview.
Which versions of Xcode, Android SDK, JDK and Windows App SDK fit a MAUI version changes with every release. For that, Learn points to the “Release versions” table in the GitHub wiki of dotnet/maui. This course therefore gives no numbers. Look it up there before you set up.
Tools
You have two paths, and both are fine:
| Visual Studio | Visual Studio Code | |
|---|---|---|
| Requirement | Visual Studio 2022 17.12 or later with the “.NET Multi-platform App UI development” workload | Visual Studio Code with the “.NET MAUI” extension |
| Further extensions | none | C# Dev Kit and C#, which the MAUI extension installs itself |
| Account | C# Dev Kit requires signing in with a Microsoft account that has an active Visual Studio subscription; the license questions are in the C# Dev Kit FAQ |
Your operating system determines what you can build (Visual Studio reaches iOS from Windows through a Mac on the network):
| Your operating system | Targets |
|---|---|
| Windows | Windows, Android |
| macOS | Android, iOS, macOS |
| Linux | Android |
You build, sign and install apps for iOS and Mac Catalyst only on a Mac with a matching Xcode version. With an Apple ID and membership in the Apple Developer Program, you can deploy to devices and put apps in the store. For debug builds, there’s “Hot Restart” from Windows to an iOS device; that, too, requires the paid membership. For Mac Catalyst, Learn names only Visual Studio Code as a tool for starting and debugging. Those are the reasons why this course describes iOS but doesn’t practice it.
The workload
In .NET 10, the framework comes as a .NET workload and as several NuGet packages. The advantage: you can tie a project to a specific version and easily try out preview versions. Without Visual Studio, you install it like this:
dotnet workload install maui
On Windows and macOS, the workload maui then appears in the list:
dotnet workload list
If you set it up with the Visual Studio installer, the list shows android, maui-windows, maccatalyst and ios instead. On macOS,
the first command needs sudo; on Linux the workload is called maui-android.
Setting up Android
For Android you need the Java SDK, the Android SDK and an emulator. With Visual Studio, the first start of an Android app takes care of that: it downloads the default SDK, creates a virtual Android device entry (AVD) and asks for the licenses several times. Accept them only if you’ve read them.
With Visual Studio Code there are several ways. The recommended one uses an MSBuild target that installs the dependencies itself once you have a project:
dotnet new maui -n "MyApp"
cd MyApp
dotnet build -t:InstallAndroidDependencies -f:net10.0-android -p:AndroidSdkDirectory="C:\Android\Sdk" -p:AcceptAndroidSDKLicenses=True
Learn shows this example with net9.0-android. For .NET 10, the target framework is called net10.0-android. The path to the SDK should
contain neither spaces nor non-ASCII characters. With -p:AcceptAndroidSDKLicenses=True you declare that you’ve accepted the licenses.
The path C:\Android\Sdk is just an example here.
The Visual Studio Code extension checks the environment with the command .NET MAUI: Configure Android and the choice Refresh
Android environment. It looks for JAVA_HOME and ANDROID_HOME; Learn recommends setting both environment variables. Which version
of the JDK fits isn’t stated consistently in Learn: the installation page names the Microsoft Build of OpenJDK 17, while the page on
what’s new in .NET 10 says that .NET for Android now also builds with JDK 21. The “Release versions” table is authoritative.
An emulator is only fast if hardware acceleration is turned on. Learn explicitly warns that it runs very slowly otherwise. On Windows on ARM (for example, Surface Pro X), the Android emulator doesn’t run because it needs x64; there you use a real device over USB. The emulator has no virtual hardware for Bluetooth, NFC, SD card, headphones and USB.
Windows as a target
Windows as a target needs no emulator: the app runs as a window on the computer you develop on. According to Learn, a new MAUI project is deployed as an unpackaged app by default. For packaged apps (MSIX), Windows Developer Mode must be turned on: Settings, Privacy & security, “For developers” (Windows 11), then “Developer Mode”. Turning it on requires administrator rights and can be blocked on a company’s devices. If Visual Studio asks for Developer Mode while building, follow the link to the settings for developers. The names of the settings pages change between Windows versions.
Creating the template
On the command line, dotnet new maui creates a project with a XAML interface. That’s the shortest way, and it looks the same in both
tools:
dotnet new maui -n Kladde.App -o src\Kladde.App
With Visual Studio, you choose Create a new project, filter for MAUI, take the template .NET MAUI App, enter name and location, and choose the .NET version. With Visual Studio Code, it’s Create .NET Project (or .NET: New Project… in the command palette) and the template .NET MAUI App; the project must be created in an empty folder.
Don’t let the name confuse you: Kladde.App is the project name and also the default namespace of the classes.
What the template creates
Learn names four important code files:
| File | Job |
|---|---|
MauiProgram.cs |
the shared entry point: builds and starts the app |
App.xaml and App.xaml.cs |
resources for the whole app such as colors and styles; the code creates the window |
AppShell.xaml and AppShell.xaml.cs |
the structure of the app: tabs, menu and pages |
MainPage.xaml and MainPage.xaml.cs |
the template’s start page with the button “Click me” |
In addition there are the folders Platforms and Resources. Start with the entry class:
namespace MyMauiApp;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>();
return builder.Build();
}
}
That’s the smallest possible version. The template adds more lines to the builder, for example for fonts. Every platform calls
CreateMauiApp and gets a MauiApp back. So there’s exactly one entry point, even though four platforms start. Registrations for
dependency injection come later, in W2.2.1.
The class App derives from Application and creates the window:
namespace MyMauiApp;
public class App : Application
{
public App()
{
InitializeComponent();
}
protected override Window CreateWindow(IActivationState? activationState)
{
return new Window(new AppShell());
}
}
AppShell is a class derived from Shell that describes the visible structure of the app. The template’s page, MainPage, derives from
ContentPage. At startup, this chain runs: MauiProgram builds the app, the constructor of App is created, CreateWindow creates
AppShell, and AppShell shows MainPage. Its constructor calls InitializeComponent, which creates the objects from the XAML file,
connects them into a tree and sets it as the content of the page.
Before .NET 9,
Apphad an assignment toMainPageinstead. If you find older examples that writeMainPage = new AppShell();, that is the old form. With .NET 10,CreateWindowapplies.
One project, several platforms
MAUI works with one project: an SDK project that targets several platforms (multi-targeting). Learn sums it up like this: one shared project for all platforms, a simplified choice of the target to start, shared resource files, a single manifest with the app’s title, ID and version, access to platform-specific interfaces, and a shared entry point.
The Platforms folder has a subfolder for each platform with the platform-specific resources and the code that starts the app there.
When building for Android, only the files from Platforms\Android go into the package, not those of the other platforms. Where you need
platform-specific code, you use partial classes or conditional compilation:
#if ANDROID
handler.PlatformView.SetBackgroundColor(Colors.Red.ToPlatform());
#elif IOS
handler.PlatformView.BackgroundColor = Colors.Red.ToPlatform();
handler.PlatformView.BorderStyle = UIKit.UITextBorderStyle.Line;
#elif WINDOWS
handler.PlatformView.Background = Colors.Red.ToPlatform();
#endif
This example from Learn sets the background color of a control: each platform has its own call for it. In this course you don’t need code like this; it only shows that the way to the platform stays open.
The Resources folder holds the shared resources. Every image file serves as a source from which the platforms’ resolutions are
generated at build time. Which folders apply is set by the project file; the action follows from the folder:
| Resource | Action in the project file | Folder in the template |
|---|---|---|
| App icon | MauiIcon |
Resources\AppIcon |
| Images | MauiImage |
Resources\Images |
| Fonts | MauiFont |
Resources\Fonts |
| Splash screen | MauiSplashScreen |
Resources\Splash |
| Raw files | MauiAsset |
Resources\Raw |
| CSS files | MauiCss |
any |
<ItemGroup>
<!-- Images -->
<MauiImage Include="Resources\Images\*" />
<!-- Fonts -->
<MauiFont Include="Resources\Fonts\*" />
<!-- Raw assets -->
<MauiAsset Include="Resources\Raw\*" />
</ItemGroup>
XAML files automatically get the action MauiXaml. A platform’s own resource, such as an image in
Platforms\Android\Resources\drawable-xhdpi, wins over the shared one. The app’s title, ID and version are in one place and are merged
with each platform’s manifest at build time; in Visual Studio you find them in the project’s properties under MAUI Shared.
What’s new in .NET 10
The page “What’s new in .NET MAUI for .NET 10” is long. For your work, the following counts. The list also shows what you shouldn’t rebuild from old tutorials:
| Area | Status in .NET 10 |
|---|---|
| Delivery | .NET workload plus NuGet packages; projects can be tied to versions |
| Android | API 36 is the default; the templates set the minimum version 24; you can build with JDK 21 |
| Starting | dotnet run now works for Android, with -p:AdbTarget=-e for the running emulator or -d for a connected device |
| Marshal methods | turned on in .NET 10; problems often show up as a hang at startup, then set AndroidEnableMarshalMethods to false |
| CoreCLR on Android | experimental, not meant for production |
| XAML | a source generator (MauiXamlInflator), more in W2.1.2; global and implicit namespaces as a preview feature |
| Aspire | a template for a service defaults project for MAUI (OpenTelemetry, service discovery) |
| deprecated | ListView and its cells, TableView: use CollectionView instead; DisplayAlert and DisplayActionSheet: use DisplayAlertAsync and DisplayActionSheetAsync instead; FadeTo, ScaleTo and the other animations: use the variants with Async instead; Page.IsBusy: use ActivityIndicator instead |
| removed or internal | ClickGestureRecognizer removed (TapGestureRecognizer); MessagingCenter internal (replacement: WeakReferenceMessenger from CommunityToolkit.Mvvm) |
The row on Android API 36 had a trap in the preview versions: Visual Studio and Visual Studio Code didn’t know the version yet and reported the error XA5207. Whether that still matters with your tool version, you’ll see from the error text.
That also lets you start Android apps on the command line when an emulator is running:
dotnet run -f net10.0-android -p:AdbTarget=-e
Learn shows dotnet run -p:AdbTarget=-e without -f. A MAUI project has several target platforms; -f is the option of dotnet run
that selects one.
Starting: emulator and Windows
Visual Studio. In the toolbar, in the Debug Target menu, choose the Android emulators and an entry, or Windows Machine. On the first Android start, Visual Studio sets up the SDK and emulator (see above). The template has the button Click me; every press increases a counter.
Visual Studio Code. First check the environment (.NET MAUI: Configure Android, then Refresh Android environment). In the
status bar at the bottom, clicking the curly braces { } sets the Debug Target; the commands .NET MAUI: Pick Android Device and
.NET MAUI: Pick Windows Device do the same. You start with F5 or the Run button. If Visual Studio Code asks for a debugger,
choose C# and your project’s launch configuration.
iOS and Mac, described. With Visual Studio on Windows, you pair a Mac as a build host (Pair to Mac). Then you choose an iOS simulator as the target; Visual Studio builds, starts the “Remote iOS Simulator for Windows” and installs the app there. On the Mac itself you work with Visual Studio Code, choose .NET MAUI: Pick iOS Device and start with F5. For a real iOS device you need the Apple ID in Xcode, Developer Mode turned on on the device and, in the Developer Program, the device’s registration in the team.
Common errors
| What you observe | What to check |
|---|---|
| The emulator is very slow | is hardware acceleration turned on (Hyper-V and AEHD)? |
| There is no emulator on Windows on ARM | the emulator needs x64; use a device over USB |
dotnet workload list shows no maui |
was Visual Studio set up with the workload (then android, maui-windows, maccatalyst, ios appear) or was dotnet workload install maui run? |
| Android licenses aren’t accepted | in a terminal with administrator rights, in the folder cmdline-tools/<version>/bin of the Android SDK, run sdkmanager --licenses |
| App hangs at startup on .NET 10 | try AndroidEnableMarshalMethods set to false; if that helps, report it as an issue in the dotnet/android repository |
| Windows asks for Developer Mode | see the section on Windows; according to Learn it isn’t needed for unpackaged apps |
For everything not covered here, the page “Troubleshooting known issues” in the MAUI documentation takes you further.
Hot Reload
Hot Reload is the reason why working on an app can be pleasant. There are two kinds:
- .NET Hot Reload applies changes to C# code while the app is running, without rebuilding and without you having to stop or hit a breakpoint.
- XAML Hot Reload applies saved or typed changes to XAML. The navigation state and the data are kept because the page isn’t rebuilt; only the changed properties are set.
Requirements and limits according to Learn:
| Point | Statement |
|---|---|
| Configuration | the app runs in the Debug configuration with the debugger attached; the configuration must be named exactly that |
| Tool | Visual Studio 2022 and Visual Studio Code |
| Targets | XAML Hot Reload works on Windows for Android, iOS and WinUI, on emulators, simulators and real devices |
| Saving | in Visual Studio, by default every change applies as you type; the option “Apply XAML Hot Reload on document save” switches to saving |
| C# in XAML | XAML Hot Reload does not reload C# code: a new event handler in the code works only after the code is reloaded in the XAML; a new name with x:Name likewise |
| Files | don’t add, remove or rename files or NuGet packages: then rebuild and redeploy |
| Errors | an error in the XAML file, error code XHR…, blocks all changes until it’s fixed |
| Reloading | the Hot Reload button or Alt+F10 reloads the changed page completely; focus and selection may be lost |
| Resources | if you change a resource dictionary, all XAML files are reloaded |
Learn also names a source of errors when Hot Reload does nothing without a message: all .cs files must be saved as UTF-8 with BOM.
Files without a BOM, as many editors on Linux or macOS create them, can cause XAML Hot Reload to fail silently. In Visual Studio Code, you
set this in the status bar with Save with Encoding. The output of the Hot Reload window (in Visual Studio under View, Output,
Hot Reload) shows status messages.
Exercise 1
Start the template. Start the template on the Android emulator and on Windows.
- In an empty folder outside the Kladde, create a project that you’ll delete again after the exercise:
dotnet new maui -n MauiScratch. - Open it in Visual Studio or Visual Studio Code.
- Choose an Android emulator as the target. The first time, your tool sets up the SDK and emulator.
- Start the app and press Click me four times.
- Stop the app, choose Windows as the target (Windows Machine or Pick Windows Device) and start the app again.
How to check: the same page appears on both targets, and the counter is at four after four presses. In the emulator you also find the app in the list of installed apps. If the emulator runs noticeably sluggishly, check whether hardware acceleration is turned on.
Solution 1
All steps are described in the sections above. A command-line way for Android, if the emulator is already running:
cd MauiScratch
dotnet run -f net10.0-android -p:AdbTarget=-e
For Windows you choose the target in your tool; the course starts the Windows app from Visual Studio or Visual Studio Code. If the first
Android start aborts with an error message about licenses, sdkmanager --licenses helps (see the table of errors).
Cleaning up: shut down the emulator when you no longer need it; it takes up memory and disk space until you delete the virtual device (Android Device Manager). There are no costs.
Exercise 2
Try out Hot Reload. Start the template from exercise 1 with the debugger (F5 or the green button, not “without debugging”).
- Open
MainPage.xaml, change the text of a heading and save. The running app shows the new text, and the counter keeps its value. - In the same file, change the background color or the margin of a row.
- Open
MainPage.xaml.cs, find the method that increases the counter when the button is pressed, and change the text it sets. Apply the change (in Visual Studio, the Hot Reload button). - In the XAML file, add a new control with
x:Nameand access it from the code. Watch what happens.
How to check: steps 1 and 2 show the effect without a restart, with the counter unchanged. In step 4, the new name only works after a rebuild, because XAML Hot Reload doesn’t reload the C# code.
Solution 2
Step 3 changes C# code, not XAML; it depends on .NET Hot Reload. Which code changes can be applied without rebuilding the app depends on
the kind of change; on the .NET Hot Reload page, Learn describes what doesn’t work. If nothing shows up, check the points in the table:
Debug configuration, debugger attached, no XHR errors, .cs files with BOM. Step 4: after a rebuild and a restart of the app, the
name applies. Remember the rule for practice: XAML without x:Name and without new events can be changed while the app is running;
everything else needs a rebuild.
Exercise 3
Create Kladde.App. Create the project for the Kladde.
- Change to the main folder of your Kladde (where
Kladde.slnxand the foldersrcare). - Create the project:
dotnet new maui -n Kladde.App -o src\Kladde.App. - Open
src\Kladde.App\Kladde.App.csprojin your tool and start the app on Android and Windows, as in exercise 1. - In the project’s properties (in Visual Studio MAUI Shared, General), set the title to “Kladde”. Leave the ID in the form
com.<your-name>.kladde, with a name of your choice, and the version at 1.0. - Stop the app. Add
src\Kladde.Appto the repository, in a branchfeature/maui-app, with a commit message likefeat(app): MAUI template as Kladde.App.
How to check: the project starts on Android and Windows and shows the template’s page; git status shows src/Kladde.App with the
template’s files, but without the folders bin and obj. If they’re listed, add bin/ and obj/ to the .gitignore.
Solution 3
The command creates a directory src/Kladde.App next to the K2 projects. The template generates namespaces after the project name: your
classes are in the namespace Kladde.App. You don’t add the project to the solution file Kladde.slnx in this module yet: whether and
how the app gets in is decided by W2.2.1, once there are the first tests, because then it has to be settled what a call of dotnet build
or dotnet test should build for the solution. Learn warns about solutions with a MAUI project for a different reason: a
dotnet publish on a solution tries to publish every project individually. So always publish the app project itself.
The app’s ID is a string following the pattern of a reversed domain. It isn’t an address that has to be reachable. Choose it deliberately; later, when you distribute the app, a lot depends on it (W2.3.2).
Cleaning up: delete the folder MauiScratch from exercises 1 and 2.
The Kladde: an app next to the web application
With this lesson, the Kladde has a new project: src/Kladde.App, an app that starts on Android and Windows and still shows the
template’s page. You don’t change the projects from K2 (Kladde.Core, Kladde.Data, Kladde.Web and the others). The app will use the
Kladde’s web API, from W2.2.2, and the Kladde’s rules stay where they are. In W2.1.2 you build the first pages of your own: a contact
list and today’s appointments, still with fixed sample data in the code.
What you can do now
- You explain what .NET MAUI is, how the four platforms are addressed, and what is required at minimum in .NET 10 according to Learn.
- You set up the workload, Android SDK, emulator and Windows as a target, and you know what’s missing for iOS: a Mac on the network.
- You match
MauiProgram,App,AppShell,PlatformsandResourcesto their jobs and know where platform-specific code goes. - You start the app on an Android emulator and on Windows and use Hot Reload, with its limits.
- You know the new features and deprecations of .NET 10 that older tutorials still show differently, and you’ve created
Kladde.App.
Read more
- What is .NET MAUI?: what MAUI is, the architecture and what it offers.
- Supported platforms for .NET MAUI apps: minimum versions per platform.
- Installation: Visual Studio, Visual Studio Code, workload, Android and Apple.
- Build your first app: the template step by step for Android, iOS, Mac and Windows.
- Target multiple platforms from .NET MAUI single project: resources, manifest, Platforms folder, entry point.
- Deploy and debug your .NET MAUI app on Windows: Developer Mode, packaged and unpackaged.
- XAML Hot Reload for .NET MAUI: settings, limits and troubleshooting.
- What’s new in .NET MAUI for .NET 10: all changes and deprecations.
- Release versions: which versions of Xcode, Android SDK, JDK and Windows App SDK belong to which MAUI version.