Introducere: De Ce Migrarea Nu Mai Este Opțională
Suportul oficial Microsoft pentru Xamarin s-a încheiat pe 1 mai 2024. N-a fost o surpriză — anunțul a venit cu ani înainte — dar realitatea din 2026 e mult mai dură decât anticipau mulți. Aplicațiile construite cu Xamarin.Forms nu mai primesc actualizări de securitate, nu mai sunt compatibile cu cele mai noi SDK-uri iOS și Android, iar magazinele de aplicații au început să respingă update-urile.
Să fim concreți: Google Play Store cere acum ca aplicațiile să targeteze cel puțin Android 14 (API level 34+), iar Xamarin suportă maxim Android 13. Apple App Store necesită Xcode 16+, incompatibil cu toolchain-ul Xamarin. Practic, dacă aplicația ta încă rulează pe Xamarin.Forms, nu mai poți publica actualizări. Punct.
Vestea bună? .NET MAUI (Multi-platform App UI) este succesorul direct al Xamarin.Forms, construit pe aceeași fundație conceptuală dar cu o arhitectură radical îmbunătățită. Odată cu .NET 10 (lansat în noiembrie 2025 ca versiune LTS), framework-ul e matur, stabil și gata de producție — nu mai ești cobai.
În acest ghid, vom trece prin întregul proces de migrare: de la evaluarea inițială și pregătirea proiectului, la conversia codului, migrarea renderer-elor personalizate la handler-e, actualizarea dependențelor NuGet și testarea finală. Fiecare secțiune include cod funcțional pe care îl poți aplica direct.
Ce S-a Schimbat: Xamarin.Forms vs .NET MAUI
Înainte de a începe migrarea, trebuie să înțelegi diferențele fundamentale. Nu e vorba doar despre un update de namespace-uri — arhitectura s-a schimbat semnificativ, și dacă intri nepregătit, o să te lovești de surprize neplăcute pe parcurs.
Structura Proiectului: De la Multe Proiecte la Proiect Unic
În Xamarin.Forms, o soluție tipică avea minim 4 proiecte separate: unul partajat (.NET Standard) și câte unul pentru fiecare platformă (Android, iOS, UWP). În .NET MAUI, toate platformele sunt consolidate într-un singur proiect, cu foldere dedicate în directorul Platforms/.
// Structura Xamarin.Forms (veche)
MySolution/
├── MyApp/ # Proiect partajat (.NET Standard)
├── MyApp.Android/ # Proiect Android separat
├── MyApp.iOS/ # Proiect iOS separat
└── MyApp.UWP/ # Proiect UWP separat
// Structura .NET MAUI (nouă)
MySolution/
└── MyApp/ # Proiect unic
├── Platforms/
│ ├── Android/
│ ├── iOS/
│ ├── MacCatalyst/
│ └── Windows/
├── Resources/
├── MauiProgram.cs
└── MyApp.csproj
Namespace-uri Noi
Toate referințele Xamarin.Forms devin Microsoft.Maui. Iată maparea principalelor namespace-uri:
// Înainte (Xamarin.Forms)
using Xamarin.Forms;
using Xamarin.Essentials;
using Xamarin.Forms.Xaml;
// După (.NET MAUI)
using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;
using Microsoft.Maui.Devices; // pentru Xamarin.Essentials
using Microsoft.Maui.Storage; // pentru SecureStorage, Preferences
using Microsoft.Maui.ApplicationModel; // pentru AppInfo, Browser
Dependency Injection Nativ
Xamarin.Forms folosea DependencyService pentru rezolvarea dependențelor — un mecanism simplu, dar sincer destul de limitat când proiectele creșteau în complexitate. .NET MAUI adoptă containerul nativ Microsoft.Extensions.DependencyInjection, același folosit în ASP.NET Core. Diferența e enormă în ceea ce privește testabilitatea și flexibilitatea.
// Înainte (Xamarin.Forms) — DependencyService
// Înregistrare prin atribut:
[assembly: Dependency(typeof(FileService))]
// Rezolvare:
var fileService = DependencyService.Get<IFileService>();
// După (.NET MAUI) — Microsoft.Extensions.DependencyInjection
// Înregistrare în MauiProgram.cs:
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>();
// Înregistrarea serviciilor
builder.Services.AddSingleton<IFileService, FileService>();
builder.Services.AddTransient<MainViewModel>();
builder.Services.AddTransient<MainPage>();
return builder.Build();
}
// Rezolvare prin constructor injection:
public class MainViewModel
{
private readonly IFileService _fileService;
public MainViewModel(IFileService fileService)
{
_fileService = fileService;
}
}
Handler-e în Loc de Renderer-e
Aceasta e probabil cea mai importantă schimbare arhitecturală. Renderer-ele din Xamarin.Forms erau puternic cuplate de controalele cross-platform, ceea ce genera overhead de memorie și complexitate (cine n-a înjurat la un memory leak misterios venind dintr-un renderer?). Handler-ele din .NET MAUI sunt decuplate, mai ușoare și semnificativ mai performante. Vom detalia conversia mai jos.
Pregătirea pentru Migrare: Checklist Esențial
Nu te arunca direct în cod. O migrare reușită începe cu pregătirea — am văzut proiecte care au eșuat nu din cauza complexității tehnice, ci pentru că cineva a sărit direct la treabă fără să auditeze dependențele. Iată ce trebuie verificat înainte de a scrie prima linie de cod nou:
1. Actualizează Xamarin.Forms la Versiunea 5.0
Dacă proiectul tău e pe o versiune mai veche de Xamarin.Forms, actualizează mai întâi la versiunea 5.0. Aceasta a introdus multe API-uri care sunt compatibile cu .NET MAUI, simplificând procesul ulterior. .NET Upgrade Assistant cere minim versiunea 4.8, dar 5.0 e recomandată.
2. Auditează Dependențele NuGet
Verifică fiecare pachet NuGet din proiect. Întreabă-te: are pachetul o versiune compatibilă cu .NET MAUI? Dacă nu, există o alternativă? Iată o listă cu pachete comune și echivalentele lor:
Xamarin.Forms → Microsoft.Maui.Controls
Xamarin.Essentials → Integrat în MAUI (Microsoft.Maui.Essentials)
Xamarin.CommunityToolkit → CommunityToolkit.Maui
Xamarin.CommunityToolkit.Markup → CommunityToolkit.Maui.Markup
Rg.Plugins.Popup → CommunityToolkit.Maui (Popup) sau Mopups
FFImageLoading → FFImageLoading.Maui sau ImageSource nativ
Prism.Forms → Prism.Maui
ReactiveUI.XamForms → ReactiveUI.Maui
Sharpnado.Tabs → Sharpnado.Tabs.Maui
SkiaSharp.Views.Forms → SkiaSharp.Views.Maui.Controls
3. Elimină Codul Depreciat
Înainte de migrare, curăță codul existent. Elimină referințele la API-uri depreciate în Xamarin.Forms 5.0, cum ar fi Device.BeginInvokeOnMainThread (înlocuiește cu MainThread.BeginInvokeOnMainThread) sau Device.RuntimePlatform (înlocuiește cu DeviceInfo.Platform).
4. Creează un Branch Dedicat și Backup
Sună evident, dar trebuie spus: creează un branch Git dedicat migrării. Nu lucra direct pe main. Și dacă proiectul nu e în Git — pune-l în Git. Acum. Serios.
Metoda 1: Migrare cu .NET Upgrade Assistant
.NET Upgrade Assistant e instrumentul oficial Microsoft pentru migrarea automată. Nu face totul perfect, dar acoperă o mare parte din munca repetitivă — schimbarea namespace-urilor, actualizarea fișierelor .csproj și conversia structurii proiectului.
Instalarea și Rularea
# Instalare ca tool global
dotnet tool install -g upgrade-assistant
# Rulare pe soluția Xamarin.Forms
upgrade-assistant upgrade ./MySolution.sln
Asistentul va analiza soluția și va propune un plan de upgrade. Poți alege între două moduri:
- In-place: Modifică proiectul existent direct. Rapid, dar mai riscant.
- Side-by-side: Creează o copie a proiectului și lucrează pe copie. Recomandat pentru proiecte mari.
Ce Face și Ce Nu Face Upgrade Assistant
Upgrade Assistant se ocupă de:
- Conversia fișierelor
.csprojla formatul SDK-style - Actualizarea namespace-urilor din fișierele C# și XAML
- Migrarea la structura de proiect unic
- Actualizarea referințelor NuGet compatibile
Dar nu se ocupă de:
- Conversia renderer-elor personalizate la handler-e
- Înlocuirea pachetelor NuGet incompatibile cu alternative
- Migrarea logicii complexe specifice platformei
- Actualizarea pipeline-urilor CI/CD
Practic, după rularea asistentului, vei avea un proiect care compilează (sau aproape compilează), dar va necesita intervenție manuală pentru tot ce e specific proiectului tău. Gândește-te la el ca la un punct de pornire decent, nu ca la o soluție completă.
Metoda 2: Migrare Manuală (Proiect Nou)
Pentru proiecte mari sau complexe, recomand abordarea manuală: creezi un proiect .NET MAUI nou și muți codul treptat. Pare mai lent — și la început chiar e — dar oferă control total și oportunitatea de a curăța codul vechi acumulat în ani de zile.
Pasul 1: Creează Proiectul .NET MAUI
# Creează un proiect nou .NET MAUI
dotnet new maui -n MyApp -f net10.0
cd MyApp
# Verifică structura
ls -la Platforms/
Pasul 2: Configurează MauiProgram.cs
Acesta e noul punct de intrare al aplicației, echivalentul combinat al MainActivity.cs, AppDelegate.cs și App.xaml.cs din Xamarin. Configurează totul aici:
using CommunityToolkit.Maui;
using Microsoft.Extensions.Logging;
namespace MyApp;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseMauiCommunityToolkit()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
// Înregistrare servicii (înlocuiește DependencyService)
builder.Services.AddSingleton<IDataService, DataService>();
builder.Services.AddSingleton<INavigationService, NavigationService>();
// Înregistrare ViewModels
builder.Services.AddTransient<MainPageViewModel>();
builder.Services.AddTransient<SettingsViewModel>();
// Înregistrare Pages
builder.Services.AddTransient<MainPage>();
builder.Services.AddTransient<SettingsPage>();
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}
Pasul 3: Migrează Modelele și Serviciile
Modelele de date și serviciile de business logic se migrează cel mai ușor — de obicei, singura schimbare necesară e actualizarea namespace-urilor. Copiază-le în noul proiect, actualizează referințele și compilează. Dacă ai urmat principiile SOLID, aceste clase n-ar trebui să aibă nicio dependență de Xamarin.Forms.
Pasul 4: Migrează ViewModels
ViewModels necesită puțin mai multă atenție. Dacă folosești un framework MVVM (Prism, MvvmCross, FreshMvvm), verifică dacă există o versiune MAUI. Dacă nu, recomand trecerea la CommunityToolkit.Mvvm — e oficial Microsoft, lightweight și folosește source generators pentru a elimina boilerplate-ul (și e o plăcere să scrii mai puțin cod de infrastructură):
// Înainte (Xamarin.Forms cu implementare manuală INotifyPropertyChanged)
public class MainPageViewModel : INotifyPropertyChanged
{
private string _title;
public string Title
{
get => _title;
set
{
_title = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Title)));
}
}
public ICommand LoadCommand { get; }
public MainPageViewModel()
{
LoadCommand = new Command(async () => await LoadDataAsync());
}
public event PropertyChangedEventHandler PropertyChanged;
private async Task LoadDataAsync()
{
Title = await _dataService.GetTitleAsync();
}
}
// După (.NET MAUI cu CommunityToolkit.Mvvm)
public partial class MainPageViewModel : ObservableObject
{
private readonly IDataService _dataService;
[ObservableProperty]
private string _title;
public MainPageViewModel(IDataService dataService)
{
_dataService = dataService;
}
[RelayCommand]
private async Task LoadDataAsync()
{
Title = await _dataService.GetTitleAsync();
}
}
Observă cât de mult cod a dispărut. Source generator-ul din CommunityToolkit.Mvvm generează automat proprietatea publică Title, notificarea PropertyChanged și comanda LoadDataCommand. Clasa trebuie să fie partial pentru ca acest mecanism să funcționeze.
Pasul 5: Migrează Paginile XAML
Paginile XAML necesită actualizarea namespace-urilor XML și, uneori, a numelor de controale. Iată un exemplu complet:
<!-- Înainte (Xamarin.Forms) -->
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Views.MainPage">
<StackLayout Padding="20">
<Label Text="{Binding Title}" FontSize="24" />
<Button Text="Încarcă" Command="{Binding LoadCommand}" />
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Name}" Detail="{Binding Description}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
<!-- După (.NET MAUI) -->
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Views.MainPage">
<VerticalStackLayout Padding="20" Spacing="16">
<Label Text="{Binding Title}" FontSize="24" />
<Button Text="Încarcă" Command="{Binding LoadDataCommand}" />
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate>
<VerticalStackLayout Padding="8">
<Label Text="{Binding Name}" FontSize="16" FontAttributes="Bold" />
<Label Text="{Binding Description}" FontSize="14" TextColor="Gray" />
</VerticalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
</ContentPage>
Câteva diferențe importante de reținut:
- Namespace-ul XML s-a schimbat de la
xamarin.com/schemas/2014/formslaschemas.microsoft.com/dotnet/2021/maui StackLayoutdevineVerticalStackLayoutsauHorizontalStackLayout(mai performante, fără ambiguitate de orientare)ListViewdevineCollectionView(mult mai performant, virtualizare nativă)- Proprietatea
Spacinge disponibilă direct pe layout-uri
Migrarea Renderer-elor Personalizate la Handler-e
Aceasta e partea pe care majoritatea dezvoltatorilor o consideră cea mai dificilă. Și sincer, la prima vedere chiar pare intimidantă. Dar odată ce înțelegi conceptul, conversia devine aproape mecanică.
De Ce Handler-ele Sunt Superioare
Renderer-ele din Xamarin.Forms aveau o problemă fundamentală: fiecare renderer deținea o referință directă la controlul cross-platform și se abona la evenimentele acestuia. Rezultatul era un coupling strâns și un overhead semnificativ de memorie.
Handler-ele din .NET MAUI rezolvă asta prin intermediul mapper-elor — dicționare care mapează proprietățile cross-platform la acțiuni native, fără referințe directe. Rezultatul: performanță mai bună, mai puțină memorie consumată și personalizare mai simplă.
Opțiunea 1: Reutilizarea Renderer-elor (Temporar)
.NET MAUI oferă shim-uri de compatibilitate care permit reutilizarea renderer-elor existente fără rescriere completă. Aceasta e o soluție de tranziție — funcționează, dar nu beneficiezi de îmbunătățirile de performanță ale handler-elor.
// Renderer-ul existent (adaptat minimal pentru .NET MAUI)
// Elimină [assembly: ExportRenderer(...)] — nu mai este necesar
// Actualizează namespace-urile
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Compatibility;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
// sau .Platform.iOS pentru iOS
public class CustomEntryRenderer : EntryRenderer
{
public CustomEntryRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.SetBackgroundColor(Android.Graphics.Color.LightGray);
}
}
}
// Înregistrare în MauiProgram.cs
builder.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<Entry, CustomEntryRenderer>();
});
Opțiunea 2: Conversia Completă la Handler (Recomandat)
Pentru o migrare corectă pe termen lung, convertește renderer-ele la handler-e. Iată un exemplu complet — un Entry cu fundal personalizat:
// Pasul 1: Definește controlul cross-platform (dacă ai nevoie de proprietăți noi)
public class CustomEntry : Entry
{
public static readonly BindableProperty CustomBackgroundColorProperty =
BindableProperty.Create(
nameof(CustomBackgroundColor),
typeof(Color),
typeof(CustomEntry),
Colors.Transparent);
public Color CustomBackgroundColor
{
get => (Color)GetValue(CustomBackgroundColorProperty);
set => SetValue(CustomBackgroundColorProperty, value);
}
}
// Pasul 2: Creează handler-ul cu PropertyMapper
public partial class CustomEntryHandler : EntryHandler
{
public static new IPropertyMapper<CustomEntry, CustomEntryHandler> Mapper =
new PropertyMapper<CustomEntry, CustomEntryHandler>(EntryHandler.Mapper)
{
[nameof(CustomEntry.CustomBackgroundColor)] = MapCustomBackgroundColor
};
public CustomEntryHandler() : base(Mapper) { }
// Metoda de mapping va fi implementată per platformă
public static partial void MapCustomBackgroundColor(
CustomEntryHandler handler, CustomEntry entry);
}
// Pasul 3: Implementare Android (Platforms/Android/)
public partial class CustomEntryHandler
{
public static partial void MapCustomBackgroundColor(
CustomEntryHandler handler, CustomEntry entry)
{
handler.PlatformView.SetBackgroundColor(
entry.CustomBackgroundColor.ToPlatform());
}
}
// Pasul 3b: Implementare iOS (Platforms/iOS/)
public partial class CustomEntryHandler
{
public static partial void MapCustomBackgroundColor(
CustomEntryHandler handler, CustomEntry entry)
{
handler.PlatformView.BackgroundColor =
entry.CustomBackgroundColor.ToPlatform();
}
}
// Pasul 4: Înregistrare în MauiProgram.cs
builder.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<CustomEntry, CustomEntryHandler>();
});
Personalizare Rapidă fără Handler Personalizat
Pentru personalizări simple, nici nu ai nevoie de un handler complet. Poți modifica direct mapper-ul handler-ului existent:
// În MauiProgram.cs — personalizare globală pentru toate Entry-urile
Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping(
"CustomBackground", (handler, view) =>
{
#if ANDROID
handler.PlatformView.SetBackgroundColor(
Android.Graphics.Color.ParseColor("#F0F0F0"));
#elif IOS || MACCATALYST
handler.PlatformView.BackgroundColor =
UIKit.UIColor.FromRGB(240, 240, 240);
#elif WINDOWS
handler.PlatformView.Background =
new Microsoft.UI.Xaml.Media.SolidColorBrush(
Microsoft.UI.Colors.WhiteSmoke);
#endif
});
Această abordare e ideală pentru ajustări rapide — nu trebuie să creezi clase noi, doar adaugi câteva linii în configurare.
Migrarea Navigării: De la NavigationPage la Shell
Dacă proiectul tău Xamarin.Forms folosea NavigationPage cu PushAsync/PopAsync, ai două opțiuni în .NET MAUI: păstrezi navigarea clasică (funcționează identic) sau treci la Shell. Recomandarea mea: adoptă Shell. E mai modern, mai curat și suportă deep linking nativ — și odată ce te obișnuiești cu stilul de navigare bazat pe rute, n-o să mai vrei să te întorci la stiva clasică.
// Definirea rutelor în AppShell.xaml.cs
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
// Înregistrare rute pentru navigare programatică
Routing.RegisterRoute(nameof(DetailPage), typeof(DetailPage));
Routing.RegisterRoute(nameof(SettingsPage), typeof(SettingsPage));
}
}
// Navigare cu parametri (trim-safe cu IQueryAttributable)
await Shell.Current.GoToAsync($"{nameof(DetailPage)}?id={itemId}");
// Primirea parametrilor în pagina destinație
public partial class DetailPage : ContentPage, IQueryAttributable
{
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
if (query.TryGetValue("id", out var id))
{
// Folosește id-ul primit
LoadItem(id.ToString());
}
}
}
Important: Folosește întotdeauna IQueryAttributable în loc de atributul [QueryProperty]. Primul e compatibil cu NativeAOT și IL trimming, al doilea nu este.
Actualizarea Pipeline-urilor CI/CD
Pipeline-urile construite pentru Xamarin nu vor funcționa cu .NET MAUI — trebuie actualizate pentru noul toolchain. Iată un exemplu de workflow GitHub Actions pentru build-ul unei aplicații .NET MAUI pe Android și iOS:
name: Build MAUI App
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET 10
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Install MAUI Workload
run: dotnet workload install maui-android
- name: Build Android
run: dotnet build -f net10.0-android -c Release
build-ios:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- name: Setup .NET 10
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Install MAUI Workload
run: dotnet workload install maui-ios
- name: Build iOS
run: dotnet build -f net10.0-ios -c Release
Testarea după Migrare
Migrarea nu e completă până când n-ai testat temeinic. Nu e momentul să te grăbești — tocmai ai schimbat infrastructura întregii aplicații. Iată un plan de testare structurat:
1. Teste Unitare
Dacă ai teste unitare existente (și sper că ai), ar trebui să funcționeze cu modificări minime — actualizarea namespace-urilor și, eventual, a referințelor la mock-uri. Configurează un proiect xUnit separat:
# Creează proiect de teste
dotnet new xunit -n MyApp.Tests
cd MyApp.Tests
dotnet add reference ../MyApp/MyApp.csproj
dotnet add package Moq
2. Teste de Regresie Vizuală
Compară screenshot-uri ale aplicației vechi (Xamarin) cu cea nouă (MAUI). Diferențe subtile în randare pot apărea, mai ales cu fonturi, spațiere și comportamentul ScrollView.
3. Teste pe Dispozitive Fizice
Nu te baza doar pe emulatoare. Testează pe dispozitive fizice Android și iOS — comportamentul gesturilor, performanța animațiilor și integrarea cu funcționalitățile hardware (cameră, GPS, biometrie) pot diferi semnificativ față de ce vezi în simulator.
4. Teste de Performanță
Compară metricile cheie înainte și după migrare:
- Timp de pornire: Ar trebui să fie mai mic cu .NET MAUI, mai ales cu NativeAOT pe iOS
- Consum de memorie: Handler-ele ar trebui să reducă utilizarea memoriei
- Dimensiunea APK/IPA: Cu IL trimming activat, dimensiunea ar trebui să scadă
Erori Frecvente și Cum Să Le Rezolvi
Din experiența migrărilor multiple, iată cele mai comune probleme pe care le vei întâlni — și mai ales, ce faci cu ele:
Eroare: XamlCompilationException la Namespace-uri
Error XFC0000: Cannot resolve type "http://xamarin.com/schemas/2014/forms"
Cauza: Un fișier XAML are încă namespace-ul vechi Xamarin.
Soluție: Înlocuiește xmlns="http://xamarin.com/schemas/2014/forms" cu xmlns="http://schemas.microsoft.com/dotnet/2021/maui" în toate fișierele XAML.
Eroare: MissingMethodException la DependencyService
System.MissingMethodException: Method not found: Xamarin.Forms.DependencyService.Get
Cauza: Codul încă folosește DependencyService.Get<T>() din Xamarin.
Soluție: Migrează la constructor injection prin DI container. Înregistrează serviciile în MauiProgram.cs și injectează-le prin constructor.
Eroare: Handler Not Found la Controale Personalizate
System.InvalidOperationException: No handler could be found for view type
Cauza: Controlul personalizat nu are un handler înregistrat.
Soluție: Înregistrează handler-ul în MauiProgram.cs folosind ConfigureMauiHandlers.
Eroare: Incompatibilitate NuGet
NU1202: Package Xamarin.Forms.Maps is not compatible with net10.0-android
Cauza: Pachetul NuGet e pentru Xamarin.Forms și nu are versiune .NET MAUI.
Soluție: Înlocuiește cu echivalentul MAUI. Pentru Maps, folosește Microsoft.Maui.Controls.Maps.
Strategia de Migrare Incrementală
Pentru proiecte enterprise cu zeci de ecrane, o migrare „big bang" e riscantă — am văzut echipe care au blocat dezvoltarea luni întregi încercând să migreze totul dintr-o dată. Recomand o abordare incrementală:
- Faza 1 — Infrastructura (1-2 săptămâni): Creează proiectul MAUI, configurează DI, navigarea Shell și pipeline-ul CI/CD. Nu migra nicio pagină încă.
- Faza 2 — Servicii și Modele (1 săptămână): Migrează toate serviciile, modelele și ViewModels. Verifică cu teste unitare.
- Faza 3 — Ecrane principale (2-4 săptămâni): Migrează paginile cele mai importante. Testează fiecare pagină pe toate platformele.
- Faza 4 — Renderer-e și Efecte (1-3 săptămâni): Convertește renderer-ele personalizate la handler-e. Aceasta e de obicei partea cea mai consumatoare de timp.
- Faza 5 — Ecrane secundare și finisare (1-2 săptămâni): Migrează paginile rămase, curăță codul, optimizează performanța.
- Faza 6 — Testare și lansare (1-2 săptămâni): Teste de regresie complete, teste pe dispozitive fizice, build-uri de release.
Total estimat: 6-14 săptămâni, în funcție de complexitatea proiectului. Un proiect cu 5-10 ecrane fără renderer-e personalizate poate fi migrat în 2-3 săptămâni.
Instrumente AWS Transform (2026)
O opțiune nouă în 2026 e AWS Transform, un instrument care poate porta automat aplicații Xamarin.Forms la .NET MAUI direct din Visual Studio. Transformarea acoperă stratul UI XAML, implementările specifice platformei și chiar conversia renderer-elor personalizate la handler-e MAUI.
Câteva considerații importante:
- E încă în preview și disponibil doar în regiunea AWS us-east-1
- Funcționează cel mai bine pe proiecte cu structură standard
- Poate necesita ajustări manuale post-transformare
- Generează cod .NET 10 cu structura de proiect modernă
Nu te baza exclusiv pe instrumente automate, dar folosește-le ca punct de plecare — pot economisi ore bune de muncă repetitivă.
Întrebări Frecvente (FAQ)
Pot reutiliza codul Xamarin.Forms existent în .NET MAUI?
Da, mai ales logica de business, modelele de date și ViewModels. Acestea necesită doar actualizarea namespace-urilor. Codul XAML al paginilor se migrează relativ ușor, dar renderer-ele personalizate și efectele necesită rescriere semnificativă. Aproximativ 60-80% din codul unei aplicații Xamarin.Forms tipice poate fi reutilizat cu modificări minime.
Cât durează migrarea de la Xamarin.Forms la .NET MAUI?
Depinde de complexitate. Un proiect simplu (5-10 ecrane, fără renderer-e personalizate) se migrează în 2-3 săptămâni. Un proiect enterprise complex, cu renderer-e personalizate și dependențe multiple, poate dura 3-4 luni. Factorul principal e numărul de renderer-e personalizate care trebuie convertite la handler-e.
Ce se întâmplă dacă un pachet NuGet nu are versiune .NET MAUI?
Ai trei opțiuni: caută o alternativă compatibilă cu .NET MAUI (cel mai frecvent caz), scrie funcționalitatea manual, sau izolează temporar feature-ul care depinde de pachetul incompatibil. Recomandarea mea: nu lăsa un singur pachet NuGet să blocheze întreaga migrare — izolează-l și revino la el ulterior.
Este .NET MAUI gata de producție pentru aplicații enterprise?
Da, absolut. Odată cu .NET 10 (LTS cu suport până în noiembrie 2028), .NET MAUI a atins maturitate. Microsoft și comunitatea au rezolvat majoritatea problemelor de stabilitate din primii ani — și da, au fost probleme, să fim sinceri. Companii mari precum UPS, NBC Sports și Aloha POS folosesc .NET MAUI în producție. Framework-ul suportă NativeAOT pe iOS, Profiled AOT pe Android și are performanțe comparabile sau superioare Xamarin.Forms.
Pot rula aplicația veche Xamarin și cea nouă MAUI simultan în timpul migrării?
Da, și e chiar recomandabil. Păstrează aplicația Xamarin funcțională în producție și dezvoltă versiunea MAUI în paralel. Poți publica versiunea MAUI ca update al aceleiași aplicații (păstrând același bundle identifier/package name) sau ca aplicație nouă, în funcție de strategia ta de lansare. Asigură-te doar că testezi temeinic înainte de a face switch-ul.