
Why migrate from Xamarin to .NET MAUI?
With .NET MAUI, Microsoft is modernizing Xamarin Forms into a better cross-platform UI framework that not only brings architectural and implementation changes, but also allows you to move to other target platforms such as Android, iOS, Mac OS (with Mac Catalyst), and Windows. Full support for Samsung's Tizen is also provided.
Is Xamarin and Xamarin Forms dying? If you think of Xamarin as just a product name, yes. Xamarin as a framework will live on as part of .NET MAUI and .NET for Android/ iOS under new names.
Is it possible to use Xamarin in MAUI?
The short answer is no. The long answer is the MAUI team has integrated much of what Xamarin developers know and love about the framework into .NET MAUI and .NET for Android/ iOS. The compatibility mode in .NET MAUI allows you to continue using most of your Xamarin Forms custom renderers and XAML layouts. However, migration is inevitable as many small things like namespaces have changed along with the project structure.
How to migrate or Upgrade Xamarin to .NET MAUI?
With the following 7 steps, we were able to successfully migrate our .NET MAUI projects away from Xamarin.Forms:
Before starting there are some requirements that should be met:
- I recommend you to upgrade your Xamarin.Forms app to use at least Xamarin.Forms 5.0 because this version is the closest to MAUI.
- Android 5.0 (API 21) or higher
- iOS 10 or higher
- The complete environment setup for .NET MAUI including all necessary workloads must be installed
Convert all projects to .NET SDK Style for .NET MAUI migration
Microsoft has changed the structure of the project and solution file with .NET Core and continuing. This change has not yet been adopted for Xamarin projects and is now being made up with .NET for Android and .NET for iOS as well as for .NET MAUI. To change your project file, open the Xamarin.Forms project csproj file and customize it according to the following example. For better understanding I have added comments to the most important entries.
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Specify which platforms should be supported in your app -->
<TargetFrameworks>net7.0-ios;net7.0-android</TargetFrameworks>
<!-- Distinguishes .NET for Android or iOS from a MAUI project -->
<UseMaui>True</UseMaui>
<OutputType>Library</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Specify which version of a platform should be supported in your app -->
<SupportedOSPlatformVersion Condition="'$(TargetFramework)' == 'net7.0-ios'">15.4</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="'$(TargetFramework)' == 'net7.0-android'">31.0</SupportedOSPlatformVersion>
<!-- Required for C# Hot Reload -->
<UseInterpreter Condition="'$(Configuration)' == 'Debug'">True</UseInterpreter>
</PropertyGroup>
<ItemGroup>
<MauiFont Include="Resources\*" />
</ItemGroup>
<ItemGroup>
<!-- If you used CommunityToolkit -->
<PackageReference Include="CommunityToolkit.Maui" Version="1.3.0" />
<!-- Add NuGet-references of your old project file here -->
</ItemGroup>
</Project>
If you don't want to switch completely to the new "single project" structure at the beginning of the migration, you can keep the existing Android and iOS projects. However, these projects must then also be converted to the SDK style. The structure is the same as above with one minor adjustment - the OutputType value will then change from Library to Exe.
Update NuGet packages
As described before, the NuGet references need to be added. The references to Xamarin.Forms and Xamarin.Essentials are no longer needed.
Now that all package references are combined in one project file, there needs to be a way to distinguish them depending on the platform. To do this, simply add a condition to the ItemGroup for each platform. Using the Google PlayService for Android as an example, this looks like this:
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0-android'">
<PackageReference Include="Xamarin.GooglePlayServices.Maps" Version="118.0.2" />
</ItemGroup>
In my experience, it is usually sufficient to change to newer versions for used NuGet packages. Only very old packages for which new versions are no longer available are problematic. In this case it usually helps to look for suitable alternatives or, if it is an open source project, to make the change yourself.
A word about Xamarin.Essentials and Xamarin.CommunityToolkit. While Xamarin.CommunityToolkit is replaced by CommunityToolkit.Maui, Xamarin.Essentials is no longer supported. Replacing Xamarin.CommunityToolkit is an important step in the migration process, as it ensures that the app can continue to use the toolkit's extensions. To use the CommunityToolkit in MAUI, the UseMauiCommunityToolkit method must be added in the MauiApp builder. More about this later in the following section.
While in Xamarin.Forms we had platform specific projects, the app was launched with the platform specific code through Xamarin.Forms.Init method. In .NET MAUI, there is a single cross-platform app entry point using MauiProgram.cs. Through this static entry point, a MauiApp instance is created and returned in each supported platform. So, we need to add a new file to the .NET MAUI project (or the old Xamarin Forms project) and name it MauiProgram.cs. .NET MAUI uses the builder principle already known from .NET Core, which leads to the below structure of MauiProgram.cs:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseMauiCommunityToolkit()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
return builder.Build();
}
}
Android specific application changes
In order for .NET MAUI to work on Android, three basic changes still need to be made to the old Xamarin Android project.
- Update the AndroidManifest.xml by changing the android:targetSdkVersion to 31.
- Customize the MainActivity in that it now derives from MauiAppCompatActivity. Remove the
OnRequestPermissionsResult
method and delete the call of LoadApplication
method. - MAUI needs an Android application file which is derived from MauiApplication and overrides CreateMauiApp. If your project doesn't have an application file yet, create a new class and name it MainApplication. See the Bitbucket repository: MAUI-Migration for the exact structure.
iOS specific application changes
Some changes also need to be made in the iOS project.
- Update the MinimumOSVersion in your Info.plist to 15.2.
- Adjust your AppDelegate that it derives from MauiUIApplicationDelegate.
- In your AppDelegate we also need to override CreateMauiApp.
- Remove the FinishedLaunching method
.NET MAUI, like Xamarin.Forms, also uses XAML layouts. You can therefore continue to use your existing XAML layouts with .NET MAUI. To do this, you need to replace the XML namespaces as follows:
xmlns="http://xamarin.com/schemas/2014/forms"
becomes xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
.using Xamarin.Forms
becomes either using Microsoft.Maui
or using Microsoft.Maui.Controls
depending of the use case.using Xamarin.Forms.Xaml
becomes using Microsoft.Maui.Controls.Xaml
.
For performance reasons, the Horizontal and VerticalStackLayout was introduced in MAUI. It replaces the often used StackLayout. You should therefore also invest time and optimize your layouts for this. Please have a look at the Microsoft documentation for more information.
There are also other areas where the namespace has to be adapted. Here are some known API changes you could see while migrating:
Colors
moved to Microsoft.Maui.Graphics
Shapes
moved to Microsoft.Maui.Controls
BorderColor
for Frames does not exists in MAUIIcon
for ToolbarItems becomes IconImageSource
Image
for Buttons becomes ImageSource
ForegroundColor
for Span does not exists in MAUI
Once you're done with the steps, you can launch the solution in Visual Studio or Visual Studio for Mac. It is very likely that more errors specific to your project will appear, for example from API changes due to newer NuGet package versions or namespace changes, which you will then have to fix step by step.
Typically, there are messages regarding the AssemblyInfo.cs. You can remove them because most of these properties are now csproj-properties. If you want to learn more on how to set these properties, I suggest to read equivalent-to-assemblyinfo-in-dotnet-core-csproj on stackoverflow.
Depending on the complexity and number of errors, in some cases the easiest way is to create a new MAUI project and then add layouts, services and NuGet references. Then, when your project starts, you can start copying your code piece by piece to better locate the source of the error.
We here at Cayas Software don't know of any Xamarin Forms project in which at least one custom renderer didn't need to be created. Depending on how many are in your project, it's worth converting them to the new handler structure right away. If there are too many after all, you can continue to use your Custom Renderers by updating everything Xamarin.Forms.*
related to Microsoft.Maui.*
and removing the ExportRenderer directives as they are no longer needed. After that, you still need to enable compatibility mode in MauiProgram.cs via the builder using UseMauiCompatibility()
.
// shortened
var builder = MauiApp.CreateBuilder();
.UseMauiCompatibility()
.ConfigureMauiHandlers((handlers) =>
{
handlers.AddCompatibilityRenderer(typeof("Your view"),typeof("Your renderer"));
});
However, I would recommend using the custom handler as it offers better performance and extensibility. In our article on Creating a .NET MAUI Maps Control, we show how you can create a custom handler.
Dependency injection changes in .NET MAUI
In .NET MAUI, it is now much easier to perform dependency injection. MAUI already comes with a container where you can register your services and load them directly into your class using, for example, contructor injection. So you don't need the DependecyService anymore. Just register your service in the MauiProgram.cs as shown below and remove the [assembly: Dependency())]
attribute from your service class.
// shortened
var builder = MauiApp.CreateBuilder();
builder.Services.AddSingleton(typeof(BluetoothService));
To get your service into your class, you just need to pass it through the parameters and then you can use it.
public partial class BluetoothSettingsPage : ContentPage
{
public BluetoothSettingsPage(BluetoothService bluetoothService)
{
InitializeComponent();
var isBluetoothOn = bluetoothService.IsBluetoothOn();
}
}
.NET MAUI project structure updates
To use the new .NET MAUI single project structure, you must perform the following steps. It is optional, but will bring your project closer to the .NET MAUI project structure.
- In the .NET MAUI or the old Xamarin.Forms project, create a folder called
Platforms
with a subfolder each for Android
and iOS
. - In the Android folder, copy your Android-specific files such as the Application class, the MainActivity class, the Services, or the Custom-Randerer.
- To the iOS folder, copy your iOS-specific files such as the AppDelegate class, the Services, or the Custom-Randerer.
- Also, add a
Resources
folder to the Maui project if there isn't one already, and copy all the images there.
After that, you can delete your Forms.iOS and Forms.Android projects.
.NET Upgrade Assistant to automate .NET MAUI conversion
Microsoft provides with the .NET Upgrade Assistant a tool that performs many of the steps described here automatically. The advantage of the .NET Upgrade Assistant is that project files, NuGet packages as well as parts of the source code are updated. At the time of writing, however, many places still need to be manually adjusted. Also the Upgrade Assistant only supports C# and Visual Basic which leaves F# (F-Sharp) behind.
To give it a try to see if it works better in your project, two steps are required:
- Run the following command in the terminal:
dotnet tool update --global upgrade-assistant
. - Run the .NET MAUI Upgrade Assistant with the following command:
upgrade-assistant upgrade <path to sln or csproj> --non-interactive --entry-point *
.
Conclusion
As you can see, migrating a Xamarin.Forms app to .NET MAUI is done in a few steps if you follow the mentioned points. Maybe in the near future the .NET Upgrade Assistant will be able to migrate everything in an automated way and we will have to do very little manual work. Until then, this guide tries to give you a good start. Let me know how your migration went and where there was any problem. I'm sure we'll be able to help you out. You can find the source code in our public Bitbucket repository: MAUI-Migration.