Migrer de Xamarin.Forms vers .NET MAUI : Guide Complet avec Exemples de Code

Guide étape par étape pour migrer vos apps Xamarin.Forms vers .NET MAUI : restructuration de projet, remplacement du DependencyService, conversion des renderers en handlers, et stratégie de test post-migration.

Introduction : Pourquoi Migrer de Xamarin.Forms vers .NET MAUI en 2026

Depuis mai 2024, Microsoft a officiellement mis fin au support de Xamarin. Plus de correctifs de sécurité, plus de mises à jour, plus de patches critiques. Si votre application mobile tourne encore sur Xamarin.Forms, vous êtes sur un terrain non maintenu — et honnêtement, chaque mois qui passe creuse un peu plus votre dette technique.

.NET MAUI (Multi-platform App UI) est le successeur naturel de Xamarin.Forms, et ce n'est pas un simple rebranding. L'architecture a été repensée en profondeur : projet unique, système de handlers performant, injection de dépendances native, intégration complète avec l'écosystème .NET moderne. Les gains concrets ? Démarrage jusqu'à 40 % plus rapide, applications jusqu'à 25 % plus légères, et une expérience de développement nettement plus agréable.

Dans ce guide, on va parcourir chaque étape de la migration ensemble. Des changements de structure de projet jusqu'à la conversion des custom renderers en handlers, en passant par le remplacement du DependencyService par l'injection de dépendances intégrée. Que vous ayez un petit projet perso ou une app d'entreprise avec des dizaines de pages, vous trouverez ici une feuille de route concrète et actionnable.

Allez, on y va.

Évaluer Votre Application Avant la Migration

Audit des Dépendances NuGet

La première étape — et sans doute la plus critique — c'est de dresser l'inventaire de toutes vos dépendances. Ouvrez chaque fichier .csproj de votre solution et listez chaque package NuGet utilisé. Pour chacun, vérifiez s'il existe une version compatible .NET MAUI.

// Commande pour lister toutes les dépendances d'un projet
dotnet list package --outdated

// Vérifier la compatibilité .NET MAUI d'un package spécifique
dotnet package search Xamarin.CommunityToolkit --source nuget.org

Les packages les plus courants ont généralement un équivalent .NET MAUI :

  • Xamarin.CommunityToolkitCommunityToolkit.Maui
  • Xamarin.Essentials → Intégré directement dans .NET MAUI (namespace Microsoft.Maui.Devices, Microsoft.Maui.Storage, etc.)
  • Xamarin.Forms.MapsMicrosoft.Maui.Controls.Maps
  • Rg.Plugins.PopupCommunityToolkit.Maui (Popup)
  • FFImageLoading → Considérer les alternatives natives ou CommunityToolkit.Maui.MediaElement

Si un package n'a pas d'équivalent, vous avez trois options : trouver une bibliothèque alternative, implémenter la fonctionnalité vous-même, ou isoler temporairement cette dépendance pour la migrer plus tard. Personnellement, j'ai tendance à privilégier l'isolation — ça évite de bloquer toute la migration pour un seul composant.

Inventaire du Code Spécifique à la Plateforme

Identifiez tous les custom renderers, effects et services spécifiques à chaque plateforme. Créez un tableau récapitulatif avec :

  • Nombre de custom renderers (iOS et Android séparément)
  • Nombre d'effects Xamarin.Forms
  • Services enregistrés via DependencyService
  • Code spécifique dans les fichiers AppDelegate.cs et MainActivity.cs

Cet inventaire va déterminer l'ampleur réelle de votre migration. C'est aussi lui qui vous permettra de prioriser les composants les plus critiques — et de ne pas vous retrouver avec de mauvaises surprises à mi-parcours.

Restructuration du Projet : Du Multi-Projet au Projet Unique

Comprendre la Nouvelle Structure

L'un des changements les plus visibles entre Xamarin.Forms et .NET MAUI, c'est la structure du projet. Xamarin.Forms utilisait typiquement trois projets distincts — un projet partagé (.NET Standard), un projet iOS et un projet Android. .NET MAUI consolide tout ça en un seul projet avec des dossiers de plateforme.

Et franchement, une fois qu'on y a goûté, on ne revient plus en arrière.

// Structure Xamarin.Forms (ancienne)
MonApp/
├── MonApp/                    // Projet partagé (.NET Standard)
│   ├── Views/
│   ├── ViewModels/
│   └── Services/
├── MonApp.Android/            // Projet Android
│   ├── MainActivity.cs
│   └── Renderers/
└── MonApp.iOS/                // Projet iOS
    ├── AppDelegate.cs
    └── Renderers/

// Structure .NET MAUI (nouvelle)
MonApp/
├── Platforms/
│   ├── Android/
│   │   └── MainActivity.cs
│   ├── iOS/
│   │   └── AppDelegate.cs
│   ├── MacCatalyst/
│   └── Windows/
├── Views/
├── ViewModels/
├── Services/
├── Resources/
└── MauiProgram.cs

Utiliser le .NET Upgrade Assistant

Microsoft fournit un outil de migration automatisé — le .NET Upgrade Assistant — qui gère une bonne partie du travail mécanique. Installez-le et exécutez-le sur votre solution :

// Installer le .NET Upgrade Assistant
dotnet tool install -g upgrade-assistant

// Exécuter la migration sur votre solution
upgrade-assistant upgrade MonApp.sln

L'outil effectue automatiquement plusieurs transformations : conversion du format de projet vers le SDK-style, mise à jour des Target Frameworks, remplacement des principaux namespaces, et migration de certaines configurations.

Mais ne vous attendez pas à un miracle. Il ne peut pas tout gérer — les custom renderers, les effects complexes et la logique spécifique devront être migrés manuellement. Considérez-le comme un bon point de départ, pas comme une solution complète.

Le Nouveau Fichier MauiProgram.cs

En Xamarin.Forms, le point d'entrée de l'application se trouvait dans App.xaml.cs avec la méthode OnStart(). .NET MAUI introduit un nouveau pattern inspiré d'ASP.NET Core : le builder pattern, centralisé dans MauiProgram.cs.

using Microsoft.Extensions.Logging;
using CommunityToolkit.Maui;
using MonApp.Services;
using MonApp.ViewModels;
using MonApp.Views;

namespace MonApp;

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");
            });

        // Enregistrement des services (remplace DependencyService)
        builder.Services.AddSingleton<IApiService, ApiService>();
        builder.Services.AddSingleton<INavigationService, NavigationService>();
        builder.Services.AddSingleton<IDatabaseService, DatabaseService>();

        // Enregistrement des ViewModels
        builder.Services.AddTransient<AccueilViewModel>();
        builder.Services.AddTransient<ProfilViewModel>();
        builder.Services.AddTransient<ParametresViewModel>();

        // Enregistrement des Pages
        builder.Services.AddTransient<AccueilPage>();
        builder.Services.AddTransient<ProfilPage>();
        builder.Services.AddTransient<ParametresPage>();

#if DEBUG
        builder.Logging.AddDebug();
#endif

        return builder.Build();
    }
}

Ce fichier devient le centre névralgique de votre application — c'est ici que vous configurez les services, les polices, les handlers personnalisés et les packages tiers. Si vous avez déjà travaillé avec ASP.NET Core, vous vous sentirez immédiatement en terrain familier.

Migration des Namespaces et des API

Remplacement Systématique des Namespaces

Le changement le plus immédiat concerne les namespaces. Chaque fichier C# et XAML devra être mis à jour. C'est un travail un peu rébarbatif, mais relativement mécanique. Voici les correspondances principales :

// C# - Remplacements de namespaces
Xamarin.Forms                    → Microsoft.Maui.Controls
Xamarin.Forms.Xaml               → Microsoft.Maui.Controls.Xaml
Xamarin.Forms.PlatformConfiguration → Microsoft.Maui.Controls.PlatformConfiguration
Xamarin.Essentials               → Microsoft.Maui.Devices (ou sous-namespaces)

// XAML - Remplacement du namespace racine
xmlns="http://xamarin.com/schemas/2014/forms"
→ xmlns="http://schemas.microsoft.com/dotnet/2021/maui"

// XAML - Namespace Essentials
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
→ xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"

Pour les fichiers C#, un simple rechercher-remplacer global dans votre IDE fera l'essentiel du travail. Pour le XAML, n'oubliez pas de mettre à jour chaque fichier .xaml de votre solution.

API Renommées et Modifiées

Certaines API ont changé de nom ou de comportement entre Xamarin.Forms et .NET MAUI. Voici les changements les plus fréquents (gardez cette liste sous le coude, elle vous sera utile) :

// Changements de propriétés
Frame                → Border (recommandé) ou Frame (toujours disponible)
StackLayout          → VerticalStackLayout / HorizontalStackLayout (plus performant)
RelativeLayout       → Supprimé (utiliser Grid)

// Changements de méthodes
Device.RuntimePlatform       → DeviceInfo.Platform
Device.BeginInvokeOnMainThread → MainThread.BeginInvokeOnMainThread
Device.OpenUri               → Launcher.OpenAsync

// Changements Xamarin.Essentials
Xamarin.Essentials.Preferences   → Microsoft.Maui.Storage.Preferences
Xamarin.Essentials.SecureStorage  → Microsoft.Maui.Storage.SecureStorage
Xamarin.Essentials.Connectivity   → Microsoft.Maui.Networking.Connectivity
Xamarin.Essentials.Geolocation    → Microsoft.Maui.Devices.Sensors.Geolocation

Une astuce pratique : compilez votre projet fréquemment pendant la migration. Le compilateur vous signalera les API manquantes ou renommées, et c'est beaucoup plus fiable que de chercher manuellement dans la documentation.

Remplacer le DependencyService par l'Injection de Dépendances

Le Problème avec DependencyService

Dans Xamarin.Forms, l'accès aux fonctionnalités spécifiques à la plateforme passait par le DependencyService — un mécanisme basé sur les attributs et la réflexion. Ça fonctionnait, mais avouons-le, c'était loin d'être élégant. Résolution par réflexion (lente), pas de support pour le cycle de vie des instances, et un couplage implicite qui rendait les tests unitaires pénibles.

// Xamarin.Forms - Ancien pattern avec DependencyService

// 1. Interface dans le projet partagé
public interface IAudioService
{
    void PlaySound(string filename);
    void StopSound();
}

// 2. Implémentation Android avec attribut
[assembly: Dependency(typeof(AudioService))]
namespace MonApp.Droid.Services
{
    public class AudioService : IAudioService
    {
        public void PlaySound(string filename)
        {
            // Code Android spécifique
            var mediaPlayer = MediaPlayer.Create(
                Android.App.Application.Context,
                Android.Net.Uri.Parse(filename));
            mediaPlayer.Start();
        }

        public void StopSound() { /* ... */ }
    }
}

// 3. Utilisation dans le code partagé
var audioService = DependencyService.Get<IAudioService>();
audioService.PlaySound("notification.mp3");

Le Nouveau Pattern avec l'Injection de Dépendances

.NET MAUI intègre nativement Microsoft.Extensions.DependencyInjection, le même conteneur IoC utilisé par ASP.NET Core. C'est typé, performant, et ça supporte pleinement les durées de vie des services (Singleton, Transient, Scoped).

// .NET MAUI - Nouveau pattern avec DI

// 1. Interface (identique)
public interface IAudioService
{
    void PlaySound(string filename);
    void StopSound();
}

// 2. Implémentation dans Platforms/Android/Services/
namespace MonApp.Platforms.Android.Services
{
    public class AudioService : IAudioService
    {
        public void PlaySound(string filename)
        {
            var mediaPlayer = MediaPlayer.Create(
                Platform.CurrentActivity,
                global::Android.Net.Uri.Parse(filename));
            mediaPlayer.Start();
        }

        public void StopSound() { /* ... */ }
    }
}

// 3. Enregistrement dans MauiProgram.cs
builder.Services.AddSingleton<IAudioService, AudioService>();

// 4. Injection via le constructeur
public class NotificationViewModel : BaseViewModel
{
    private readonly IAudioService _audioService;

    public NotificationViewModel(IAudioService audioService)
    {
        _audioService = audioService;
    }

    [RelayCommand]
    private void JouerSon()
    {
        _audioService.PlaySound("notification.mp3");
    }
}

Gestion Multi-Plateforme avec la Compilation Conditionnelle

.NET MAUI propose une approche élégante pour enregistrer différentes implémentations selon la plateforme, grâce aux dossiers Platforms/ et au système de multi-ciblage :

// Dans MauiProgram.cs - Enregistrement conditionnel
#if ANDROID
builder.Services.AddSingleton<IAudioService, Platforms.Android.Services.AudioService>();
#elif IOS
builder.Services.AddSingleton<IAudioService, Platforms.iOS.Services.AudioService>();
#endif

// Alternative plus propre avec les fichiers partiels :
// Créer un fichier AudioService.cs dans chaque dossier Platforms/
// Le système de build résoudra automatiquement la bonne implémentation

L'avantage majeur ici, c'est la testabilité. Avec l'injection par constructeur, substituer des mocks dans vos tests unitaires devient trivial — ce qui était nettement plus acrobatique avec le DependencyService.

Convertir les Custom Renderers en Handlers

Pourquoi les Handlers Sont Supérieurs

Les custom renderers de Xamarin.Forms créaient un élément parent natif supplémentaire dans la hiérarchie visuelle, ce qui alourdissait le rendu. Les handlers de .NET MAUI éliminent cette couche intermédiaire — résultat : meilleures performances et architecture plus claire.

La différence architecturale fondamentale réside dans les mappers. Au lieu de surcharger des méthodes monolithiques comme OnElementPropertyChanged, les handlers utilisent un PropertyMapper et un CommandMapper qui associent chaque propriété ou commande à une méthode dédiée. C'est plus granulaire, plus lisible, et surtout plus facile à maintenir.

Exemple Concret : Migration d'un Renderer d'Entry

Prenons un cas classique — un custom renderer qui supprime le soulignement d'un Entry sur Android. C'est probablement l'un des renderers les plus répandus dans les projets Xamarin :

// AVANT - Xamarin.Forms Custom Renderer
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Android.Content;

[assembly: ExportRenderer(typeof(BorderlessEntry), typeof(BorderlessEntryRenderer))]
namespace MonApp.Droid.Renderers
{
    public class BorderlessEntryRenderer : EntryRenderer
    {
        public BorderlessEntryRenderer(Context context) : base(context) { }

        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);

            if (Control != null)
            {
                Control.SetBackgroundColor(Android.Graphics.Color.Transparent);
                Control.SetPadding(0, 0, 0, 0);
            }
        }

        protected override void OnElementPropertyChanged(object sender,
            PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);

            if (e.PropertyName == BorderlessEntry.CornerRadiusProperty.PropertyName)
            {
                UpdateCornerRadius();
            }
        }

        private void UpdateCornerRadius() { /* ... */ }
    }
}
// APRÈS - .NET MAUI Handler
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;

namespace MonApp.Handlers
{
    public partial class BorderlessEntryHandler : EntryHandler
    {
        public static readonly IPropertyMapper<IEntry, BorderlessEntryHandler>
            BorderlessMapper = new PropertyMapper<IEntry, BorderlessEntryHandler>(Mapper)
        {
            [nameof(IBorderlessEntry.CornerRadius)] = MapCornerRadius
        };

        public BorderlessEntryHandler() : base(BorderlessMapper) { }
    }

    // Fichier: Platforms/Android/Handlers/BorderlessEntryHandler.cs
    public partial class BorderlessEntryHandler
    {
        protected override void ConnectHandler(AppCompatEditText platformView)
        {
            base.ConnectHandler(platformView);
            platformView.SetBackgroundColor(Android.Graphics.Color.Transparent);
            platformView.SetPadding(0, 0, 0, 0);
        }

        private static void MapCornerRadius(BorderlessEntryHandler handler,
            IEntry entry)
        {
            if (entry is IBorderlessEntry borderlessEntry)
            {
                // Appliquer le rayon de bordure
                handler.PlatformView?.UpdateCornerRadius(borderlessEntry.CornerRadius);
            }
        }
    }
}

Le code est un peu plus verbeux, oui. Mais la séparation des responsabilités est bien plus nette, et chaque propriété est mappée de manière explicite.

Enregistrer le Handler

L'enregistrement se fait dans MauiProgram.cs via la méthode ConfigureMauiHandlers :

builder
    .UseMauiApp<App>()
    .ConfigureMauiHandlers(handlers =>
    {
        handlers.AddHandler<BorderlessEntry, BorderlessEntryHandler>();
    });

Approche Progressive : Réutiliser les Renderers Existants

Si vous avez de nombreux custom renderers et que le temps presse (et quand est-ce que le temps ne presse pas ?), .NET MAUI offre un mécanisme de compatibilité. Certains renderers de base peuvent être réutilisés avec des modifications minimales.

// Compatibilité temporaire via UseMauiCompatibility
builder
    .UseMauiApp<App>()
    .ConfigureMauiHandlers(handlers =>
    {
#if ANDROID
        handlers.AddCompatibilityRenderer(
            typeof(BorderlessEntry),
            typeof(Platforms.Android.Renderers.BorderlessEntryRenderer));
#endif
    });

C'est un bon compromis pour une migration incrémentale : ça permet de faire tourner l'application rapidement, puis de convertir progressivement chaque renderer en handler pour bénéficier des gains de performance. Ne restez pas sur les shims de compatibilité indéfiniment, mais ils sont précieux pour débloquer la situation.

Migration des Effects vers les Platform Behaviors

Comprendre le Changement

Les Effects de Xamarin.Forms étaient un mécanisme pour modifier le comportement des contrôles natifs sans créer un renderer complet. Dans .NET MAUI, vous pouvez soit réutiliser vos effects existants (avec des modifications mineures de namespace), soit les convertir en PlatformBehavior<T>, qui est l'approche recommandée.

// AVANT - Xamarin.Forms Effect
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ResolutionGroupName("MonApp")]
[assembly: ExportEffect(typeof(ShadowEffect), nameof(ShadowEffect))]
namespace MonApp.Droid.Effects
{
    public class ShadowEffect : PlatformEffect
    {
        protected override void OnAttached()
        {
            Control?.SetElevation(10f);
        }

        protected override void OnDetached()
        {
            Control?.SetElevation(0f);
        }
    }
}
// APRÈS - .NET MAUI PlatformBehavior
using Microsoft.Maui.Controls;

namespace MonApp.Behaviors
{
    public partial class ShadowBehavior : PlatformBehavior<View>
    {
        public static readonly BindableProperty ElevationProperty =
            BindableProperty.Create(nameof(Elevation), typeof(float),
            typeof(ShadowBehavior), 10f);

        public float Elevation
        {
            get => (float)GetValue(ElevationProperty);
            set => SetValue(ElevationProperty, value);
        }
    }

    // Fichier: Platforms/Android/Behaviors/ShadowBehavior.cs
    public partial class ShadowBehavior
    {
        protected override void OnAttachedTo(View bindable,
            Android.Views.View platformView)
        {
            base.OnAttachedTo(bindable, platformView);
            platformView.Elevation = Elevation;
        }

        protected override void OnDetachedFrom(View bindable,
            Android.Views.View platformView)
        {
            base.OnDetachedFrom(bindable, platformView);
            platformView.Elevation = 0f;
        }
    }
}

// Utilisation en XAML
<Label Text="Texte avec ombre">
    <Label.Behaviors>
        <local:ShadowBehavior Elevation="15" />
    </Label.Behaviors>
</Label>

L'avantage des PlatformBehavior, c'est qu'ils reçoivent directement la vue native en paramètre. Plus besoin d'accéder à Control ou Container de manière implicite — tout est explicite et typé.

Navigation et Shell : Ce qui Change

Shell dans .NET MAUI

Bonne nouvelle si vous utilisez déjà Shell dans Xamarin.Forms : la migration est relativement simple. Shell est maintenu et amélioré dans .NET MAUI, et les principaux changements concernent surtout les namespaces et quelques API renommées.

// Navigation par URI - fonctionne de manière similaire
await Shell.Current.GoToAsync("//accueil/details",
    new Dictionary<string, object>
    {
        { "ArticleId", article.Id }
    });

// Paramètres de requête avec le nouveau QueryProperty
[QueryProperty(nameof(ArticleId), "ArticleId")]
public partial class DetailsPage : ContentPage
{
    private int _articleId;
    public int ArticleId
    {
        get => _articleId;
        set
        {
            _articleId = value;
            ChargerArticle(value);
        }
    }
}

Navigation avec Injection de Dépendances

Un avantage considérable de .NET MAUI, c'est la possibilité de combiner navigation Shell et injection de dépendances. Les pages enregistrées dans le conteneur DI reçoivent automatiquement leurs dépendances — plus besoin d'instancier quoi que ce soit manuellement :

// Enregistrement dans MauiProgram.cs
builder.Services.AddTransient<DetailsPage>();
builder.Services.AddTransient<DetailsViewModel>();

// Le constructeur de DetailsPage reçoit le ViewModel par DI
public partial class DetailsPage : ContentPage
{
    public DetailsPage(DetailsViewModel viewModel)
    {
        InitializeComponent();
        BindingContext = viewModel;
    }
}

// Le ViewModel reçoit ses services par DI
public partial class DetailsViewModel : ObservableObject
{
    private readonly IApiService _apiService;
    private readonly INavigationService _navigationService;

    public DetailsViewModel(IApiService apiService,
        INavigationService navigationService)
    {
        _apiService = apiService;
        _navigationService = navigationService;
    }

    [RelayCommand]
    private async Task ChargerDonnees()
    {
        var article = await _apiService.GetArticleAsync(ArticleId);
        // ...
    }
}

Ce pattern élimine les instanciations manuelles et les localisateurs de services. Chaque classe déclare explicitement ses dépendances, ce qui rend le code plus testable et surtout plus maintenable sur le long terme.

Migration des Ressources et des Assets

Nouveau Système de Ressources Unifiées

.NET MAUI unifie la gestion des ressources dans un dossier Resources/ à la racine du projet. Les images, polices, icônes et fichiers bruts sont automatiquement traités par le pipeline de build :

<!-- Dans le fichier .csproj -->
<ItemGroup>
    <!-- Images - redimensionnées automatiquement pour chaque plateforme -->
    <MauiImage Include="Resources\Images\*" />

    <!-- Polices personnalisées -->
    <MauiFont Include="Resources\Fonts\*" />

    <!-- Icône de l'application -->
    <MauiIcon Include="Resources\AppIcon\appicon.svg"
              ForegroundFile="Resources\AppIcon\appiconfg.svg"
              Color="#512BD4" />

    <!-- Écran de démarrage -->
    <MauiSplashScreen Include="Resources\Splash\splash.svg"
                       Color="#512BD4"
                       BaseSize="128,128" />

    <!-- Fichiers bruts (JSON, SQL, etc.) -->
    <MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>

L'un des vrais gains de productivité ici, c'est la gestion automatique des images. Là où Xamarin.Forms vous obligeait à préparer manuellement les images en plusieurs résolutions (1x, 2x, 3x pour iOS, mdpi/hdpi/xhdpi pour Android), .NET MAUI génère automatiquement toutes les tailles à partir d'un seul fichier SVG ou d'une image haute résolution. Ça paraît anodin, mais sur un projet avec beaucoup d'assets, c'est un gain de temps non négligeable.

Migration des Styles et Thèmes

Les dictionnaires de ressources XAML restent fonctionnellement identiques, mais les namespaces doivent être mis à jour. Profitez-en pour adopter les styles dynamiques et le support thème clair/sombre :

<!-- Resources/Styles/Colors.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui">
    <Color x:Key="Primaire">#512BD4</Color>
    <Color x:Key="Secondaire">#DFD8F7</Color>
    <Color x:Key="Tertiaire">#2B0B98</Color>

    <!-- Support thème sombre -->
    <Color x:Key="ArrierePlanPage">#FFFFFF</Color>
    <Color x:Key="ArrierePlanPageSombre">#1C1C1E</Color>
</ResourceDictionary>

Stratégie de Test Post-Migration

Tests Unitaires et Mocking

Grâce à l'injection de dépendances, vos ViewModels sont désormais directement testables sans dépendance à la plateforme. Et ça, c'est un vrai changement par rapport à l'époque Xamarin :

// Test unitaire avec xUnit et Moq
[Fact]
public async Task ChargerDonnees_Devrait_Remplir_ListeArticles()
{
    // Arrange
    var mockApiService = new Mock<IApiService>();
    mockApiService.Setup(s => s.GetArticlesAsync())
        .ReturnsAsync(new List<Article>
        {
            new Article { Id = 1, Titre = "Test 1" },
            new Article { Id = 2, Titre = "Test 2" }
        });

    var viewModel = new AccueilViewModel(mockApiService.Object);

    // Act
    await viewModel.ChargerDonneesCommand.ExecuteAsync(null);

    // Assert
    Assert.Equal(2, viewModel.Articles.Count);
    Assert.Equal("Test 1", viewModel.Articles[0].Titre);
}

[Fact]
public async Task ChargerDonnees_ErreurReseau_Devrait_Afficher_Message()
{
    // Arrange
    var mockApiService = new Mock<IApiService>();
    mockApiService.Setup(s => s.GetArticlesAsync())
        .ThrowsAsync(new HttpRequestException("Pas de connexion"));

    var viewModel = new AccueilViewModel(mockApiService.Object);

    // Act
    await viewModel.ChargerDonneesCommand.ExecuteAsync(null);

    // Assert
    Assert.True(viewModel.AErreur);
    Assert.Contains("connexion", viewModel.MessageErreur);
}

Tests sur Appareils Physiques

Après la migration, testez systématiquement sur des appareils physiques. Les émulateurs c'est bien, mais rien ne remplace un vrai device. Voici une checklist des points critiques :

  • Démarrage à froid : mesurez le temps de démarrage et comparez-le à l'ancienne version Xamarin
  • Navigation : vérifiez chaque flux de navigation, y compris les deep links
  • Rendu visuel : inspectez chaque page pour des différences de rendu entre les plateformes
  • Comportements tactiles : testez les gestes, le défilement et les animations
  • Cycle de vie : vérifiez le comportement en arrière-plan, la restauration d'état et les notifications push
  • Mémoire : utilisez les outils de profilage pour détecter les fuites mémoire
  • Accessibilité : testez avec VoiceOver (iOS) et TalkBack (Android)

Checklist Complète de Migration

Pour récapituler, voici la liste ordonnée de toutes les étapes à suivre. Imprimez-la, collez-la sur votre bureau, faites-en ce que vous voulez — mais ne sautez aucune étape :

  1. Préparation
    • Auditer toutes les dépendances NuGet et vérifier leur compatibilité
    • Inventorier les custom renderers, effects et services de plateforme
    • Créer une branche dédiée dans votre dépôt Git
    • Mettre à jour Xamarin.Forms vers la version 5.0+ si ce n'est pas déjà fait
  2. Structure du Projet
    • Exécuter le .NET Upgrade Assistant
    • Vérifier et corriger le fichier .csproj généré
    • Créer le fichier MauiProgram.cs
    • Réorganiser le code spécifique dans les dossiers Platforms/
  3. Code
    • Remplacer tous les namespaces Xamarin par les équivalents MAUI
    • Migrer le DependencyService vers l'injection de dépendances
    • Convertir les custom renderers en handlers (ou utiliser les shims temporaires)
    • Migrer les effects vers les PlatformBehaviors
    • Mettre à jour les API renommées ou modifiées
  4. Ressources
    • Déplacer les images vers Resources/Images/
    • Déplacer les polices vers Resources/Fonts/
    • Mettre à jour les fichiers de styles XAML
  5. Validation
    • Exécuter les tests unitaires existants
    • Tester sur appareils physiques iOS et Android
    • Profiler les performances et comparer avec les benchmarks Xamarin
    • Valider l'accessibilité

Conclusion : Une Migration qui Vaut l'Investissement

Migrer de Xamarin.Forms vers .NET MAUI, c'est un investissement significatif en temps et en effort. Mais les bénéfices sont indéniables. Au-delà de la simple survie (Xamarin n'est plus maintenu, rappelons-le), vous gagnez un framework moderne avec une architecture plus performante, des outils de développement améliorés et un accès à l'ensemble de l'écosystème .NET 10.

L'injection de dépendances intégrée transforme la testabilité de votre code. Le système de handlers améliore les performances de rendu. Le projet unique simplifie considérablement la maintenance. Et avec des fonctionnalités comme la génération de source XAML et le NativeAOT, votre application peut atteindre des niveaux de performance qui étaient tout simplement impossibles avec Xamarin.Forms.

La clé d'une migration réussie ? La progressivité. N'essayez pas de tout migrer d'un coup — c'est le meilleur moyen de se retrouver enlisé. Commencez par la structure du projet, puis les namespaces, ensuite les services, et enfin les renderers. Utilisez les mécanismes de compatibilité quand c'est nécessaire, et convertissez progressivement vers les patterns natifs de .NET MAUI.

À chaque étape, compilez, testez et validez avant de passer à la suivante. Votre application mobile le mérite — et vos utilisateurs aussi.

À propos de l'auteur Editorial Team

Our team of expert writers and editors.