--- description: 'Instructions for upgrading .NET MAUI applications from version 9 to version 10, including breaking changes, deprecated APIs, and migration strategies for ListView to CollectionView.' applyTo: '**/*.csproj, **/*.cs, **/*.xaml' --- # Upgrading from .NET MAUI 9 to .NET MAUI 10 This guide helps you upgrade your .NET MAUI application from .NET 9 to .NET 10 by focusing on the critical breaking changes and obsolete APIs that require code updates. --- ## Table of Contents 1. [Quick Start](#quick-start) 2. [Update Target Framework](#update-target-framework) 3. [Breaking Changes (P0 - Must Fix)](#breaking-changes-p0---must-fix) - [MessagingCenter Made Internal](#messagingcenter-made-internal) - [ListView and TableView Deprecated](#listview-and-tableview-deprecated) 4. [Deprecated APIs (P1 - Fix Soon)](#deprecated-apis-p1---fix-soon) 5. [Recommended Changes (P2)](#recommended-changes-p2) 6. [Bulk Migration Tools](#bulk-migration-tools) 7. [Testing Your Upgrade](#testing-your-upgrade) 8. [Troubleshooting](#troubleshooting) --- ## Quick Start **Five-Step Upgrade Process:** 1. **Update TargetFramework** to `net10.0` 2. **Update CommunityToolkit.Maui** to 12.3.0+ (if you use it) - REQUIRED 3. **Fix breaking changes** - MessagingCenter (P0) 4. **Migrate ListView/TableView to CollectionView** (P0 - CRITICAL) 5. **Fix deprecated APIs** - Animation methods, DisplayAlert, IsBusy (P1) > âš ī¸ **Major Breaking Changes**: > - CommunityToolkit.Maui **must** be version 12.3.0 or later > - ListView and TableView are now obsolete (most significant migration effort) --- ## Update Target Framework ### Single Platform ```xml net10.0 ``` ### Multi-Platform ```xml net10.0-android;net10.0-ios;net10.0-maccatalyst;net10.0-windows10.0.19041.0 ``` ### Optional: Linux Compatibility (GitHub Copilot, WSL, etc.) > 💡 **For Linux Development**: If you're building on Linux (e.g., GitHub Codespaces, WSL, or using GitHub Copilot), you can make your project compile on Linux by conditionally excluding iOS/Mac Catalyst targets: ```xml net10.0-android $(TargetFrameworks);net10.0-ios;net10.0-maccatalyst $(TargetFrameworks);net10.0-windows10.0.19041.0 ``` **Benefits:** - ✅ Compiles successfully on Linux (no iOS/Mac tools required) - ✅ Works with GitHub Codespaces and Copilot - ✅ Automatically includes correct targets based on build OS - ✅ No changes needed when switching between OS environments **Reference:** [dotnet/maui#32186](https://github.com/dotnet/maui/pull/32186) ### Update Required NuGet Packages > âš ī¸ **CRITICAL**: If you use CommunityToolkit.Maui, you **must** update to version 12.3.0 or later. Earlier versions are not compatible with .NET 10 and will cause compilation errors. ```bash # Update CommunityToolkit.Maui (if you use it) dotnet add package CommunityToolkit.Maui --version 12.3.0 # Update other common packages to .NET 10 compatible versions dotnet add package Microsoft.Maui.Controls --version 10.0.0 ``` **Check all your NuGet packages:** ```bash # List all packages and check for updates dotnet list package --outdated # Update all packages to latest compatible versions dotnet list package --outdated | grep ">" | cut -d '>' -f 1 | xargs -I {} dotnet add package {} ``` --- ## Breaking Changes (P0 - Must Fix) ### MessagingCenter Made Internal **Status:** 🚨 **BREAKING** - `MessagingCenter` is now `internal` and cannot be accessed. **Error You'll See:** ``` error CS0122: 'MessagingCenter' is inaccessible due to its protection level ``` **Migration Required:** #### Step 1: Install CommunityToolkit.Mvvm ```bash dotnet add package CommunityToolkit.Mvvm --version 8.3.0 ``` #### Step 2: Define Message Classes ```csharp // OLD: No message class needed MessagingCenter.Send(this, "UserLoggedIn", userData); // NEW: Create a message class public class UserLoggedInMessage { public UserData Data { get; set; } public UserLoggedInMessage(UserData data) { Data = data; } } ``` #### Step 3: Update Send Calls ```csharp // ❌ OLD (Broken in .NET 10) using Microsoft.Maui.Controls; MessagingCenter.Send(this, "UserLoggedIn", userData); MessagingCenter.Send(this, "StatusChanged", "Active"); // ✅ NEW (Required) using CommunityToolkit.Mvvm.Messaging; WeakReferenceMessenger.Default.Send(new UserLoggedInMessage(userData)); WeakReferenceMessenger.Default.Send(new StatusChangedMessage("Active")); ``` #### Step 4: Update Subscribe Calls ```csharp // ❌ OLD (Broken in .NET 10) MessagingCenter.Subscribe(this, "UserLoggedIn", (sender, data) => { // Handle message CurrentUser = data; }); // ✅ NEW (Required) WeakReferenceMessenger.Default.Register(this, (recipient, message) => { // Handle message CurrentUser = message.Data; }); ``` #### âš ī¸ Important Behavioral Difference: Duplicate Subscriptions **WeakReferenceMessenger** throws an `InvalidOperationException` if you try to register the same message type multiple times on the same recipient (MessagingCenter allowed this): ```csharp // ❌ This THROWS InvalidOperationException in WeakReferenceMessenger WeakReferenceMessenger.Default.Register(this, (r, m) => Handler1(m)); WeakReferenceMessenger.Default.Register(this, (r, m) => Handler2(m)); // ❌ THROWS! // ✅ Solution 1: Unregister before re-registering WeakReferenceMessenger.Default.Unregister(this); WeakReferenceMessenger.Default.Register(this, (r, m) => Handler1(m)); // ✅ Solution 2: Handle multiple actions in one registration WeakReferenceMessenger.Default.Register(this, (r, m) => { Handler1(m); Handler2(m); }); ``` **Why this matters:** If your code subscribes to the same message in multiple places (e.g., in a page constructor and in `OnAppearing`), you'll get a runtime crash. #### Step 5: Unregister When Done ```csharp // ❌ OLD MessagingCenter.Unsubscribe(this, "UserLoggedIn"); // ✅ NEW (CRITICAL - prevents memory leaks) WeakReferenceMessenger.Default.Unregister(this); // Or unregister all messages for this recipient WeakReferenceMessenger.Default.UnregisterAll(this); ``` #### Complete Before/After Example **Before (.NET 9):** ```csharp // Sender public class LoginViewModel { public async Task LoginAsync() { var user = await AuthService.LoginAsync(username, password); MessagingCenter.Send(this, "UserLoggedIn", user); } } // Receiver public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); MessagingCenter.Subscribe(this, "UserLoggedIn", (sender, user) => { WelcomeLabel.Text = $"Welcome, {user.Name}!"; }); } protected override void OnDisappearing() { base.OnDisappearing(); MessagingCenter.Unsubscribe(this, "UserLoggedIn"); } } ``` **After (.NET 10):** ```csharp // 1. Define message public class UserLoggedInMessage { public User User { get; } public UserLoggedInMessage(User user) { User = user; } } // 2. Sender public class LoginViewModel { public async Task LoginAsync() { var user = await AuthService.LoginAsync(username, password); WeakReferenceMessenger.Default.Send(new UserLoggedInMessage(user)); } } // 3. Receiver public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); WeakReferenceMessenger.Default.Register(this, (recipient, message) => { WelcomeLabel.Text = $"Welcome, {message.User.Name}!"; }); } protected override void OnDisappearing() { base.OnDisappearing(); WeakReferenceMessenger.Default.UnregisterAll(this); } } ``` **Key Differences:** - ✅ Type-safe message classes - ✅ No magic strings - ✅ Better IntelliSense support - ✅ Easier to refactor - âš ī¸ **Must remember to unregister!** --- ### ListView and TableView Deprecated **Status:** 🚨 **DEPRECATED (P0)** - `ListView`, `TableView`, and all Cell types are now obsolete. Migrate to `CollectionView`. **Warning You'll See:** ``` warning CS0618: 'ListView' is obsolete: 'ListView is deprecated. Please use CollectionView instead.' warning CS0618: 'TableView' is obsolete: 'Please use CollectionView instead.' warning CS0618: 'TextCell' is obsolete: 'The controls which use TextCell (ListView and TableView) are obsolete. Please use CollectionView instead.' ``` **Obsolete Types:** - `ListView` → `CollectionView` - `TableView` → `CollectionView` (for settings pages, consider vertical StackLayout with BindableLayout) - `TextCell` → Custom DataTemplate with Label(s) - `ImageCell` → Custom DataTemplate with Image + Label(s) - `EntryCell` → Custom DataTemplate with Entry - `SwitchCell` → Custom DataTemplate with Switch - `ViewCell` → DataTemplate **Impact:** This is a **MAJOR** breaking change. ListView and TableView are among the most commonly used controls in MAUI apps. #### Why This Takes Time Converting ListView/TableView to CollectionView is not a simple find-replace: 1. **Different event model** - `ItemSelected` → `SelectionChanged` with different arguments 2. **Different grouping** - GroupDisplayBinding no longer exists 3. **Context actions** - Must convert to SwipeView 4. **Item sizing** - `HasUnevenRows` handled differently 5. **Platform-specific code** - iOS/Android ListView platform configurations need removal 6. **Testing required** - CollectionView virtualizes differently, may affect performance #### Migration Strategy **Step 1: Inventory Your ListViews** ```bash # Find all ListView/TableView usages grep -r "ListView\|TableView" --include="*.xaml" --include="*.cs" . ``` **Step 2: Basic ListView → CollectionView** **Before (ListView):** ```xaml ``` **After (CollectionView):** ```xaml ``` > âš ī¸ **Note:** CollectionView has `SelectionMode="None"` by default (selection disabled). You must explicitly set `SelectionMode="Single"` or `SelectionMode="Multiple"` to enable selection. **Code-behind changes:** ```csharp // ❌ OLD (ListView) void OnItemSelected(object sender, SelectedItemChangedEventArgs e) { if (e.SelectedItem == null) return; var item = (MyItem)e.SelectedItem; // Handle selection // Deselect ((ListView)sender).SelectedItem = null; } // ✅ NEW (CollectionView) void OnSelectionChanged(object sender, SelectionChangedEventArgs e) { if (e.CurrentSelection.Count == 0) return; var item = (MyItem)e.CurrentSelection.FirstOrDefault(); // Handle selection // Deselect (optional) ((CollectionView)sender).SelectedItem = null; } ``` **Step 3: Grouped ListView → Grouped CollectionView** **Before (Grouped ListView):** ```xaml ``` **After (Grouped CollectionView):** ```xaml ``` **Step 4: Context Actions → SwipeView** > âš ī¸ **Platform Note:** SwipeView requires touch input. On Windows desktop, it only works with touch screens, not with mouse/trackpad. Consider providing alternative UI for desktop scenarios (e.g., buttons, right-click menu). **Before (ListView with ContextActions):** ```xaml ``` **After (CollectionView with SwipeView):** ```xaml ``` **Step 5: TableView for Settings → Alternative Approaches** TableView is commonly used for settings pages. Here are modern alternatives: **Option 1: CollectionView with Grouped Data** ```xaml ``` **Option 2: Vertical StackLayout (for small settings lists)** ```xaml ``` **Step 6: Remove Platform-Specific ListView Code** If you used platform-specific ListView features, remove them: ```csharp // ❌ OLD - Remove these using statements (NOW OBSOLETE IN .NET 10) using Microsoft.Maui.Controls.PlatformConfiguration; using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific; using Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific; // ❌ OLD - Remove ListView platform configurations (NOW OBSOLETE IN .NET 10) myListView.On().SetSeparatorStyle(SeparatorStyle.FullWidth); myListView.On().IsFastScrollEnabled(); // ❌ OLD - Remove Cell platform configurations (NOW OBSOLETE IN .NET 10) viewCell.On().SetDefaultBackgroundColor(Colors.White); viewCell.On().SetIsContextActionsLegacyModeEnabled(false); ``` **Migration:** CollectionView does not have platform-specific configurations in the same way. If you need platform-specific styling: ```csharp // ✅ NEW - Use conditional compilation #if IOS var backgroundColor = Colors.White; #elif ANDROID var backgroundColor = Colors.Transparent; #endif var grid = new Grid { BackgroundColor = backgroundColor, // ... rest of cell content }; ``` Or in XAML: ```xaml ``` #### Common Patterns & Pitfalls **1. Empty View** ```xaml ``` **2. Pull to Refresh** ```xaml ``` **3. Item Spacing** ```xaml ``` **4. Header and Footer** ```xaml ``` **5. Load More / Infinite Scroll** ```xaml ``` **6. Item Sizing Optimization** CollectionView uses `ItemSizingStrategy` to control item measurement: ```xaml ``` > 💡 **Performance Tip:** If your list items have consistent heights, use `ItemSizingStrategy="MeasureFirstItem"` for better performance with large lists. #### .NET 10 Handler Changes (iOS/Mac Catalyst) > â„šī¸ **.NET 10 uses new optimized CollectionView and CarouselView handlers** on iOS and Mac Catalyst by default, providing improved performance and stability. **If you previously opted-in to the new handlers in .NET 9**, you should now **REMOVE** this code: ```csharp // ❌ REMOVE THIS in .NET 10 (these handlers are now default) #if IOS || MACCATALYST builder.ConfigureMauiHandlers(handlers => { handlers.AddHandler(); handlers.AddHandler(); }); #endif ``` The optimized handlers are used automatically in .NET 10 - no configuration needed! **Only if you experience issues**, you can revert to the legacy handler: ```csharp // In MauiProgram.cs - only if needed #if IOS || MACCATALYST builder.ConfigureMauiHandlers(handlers => { handlers.AddHandler(); }); #endif ``` However, Microsoft recommends using the new default handlers for best results. #### Testing Checklist After migration, test these scenarios: - [ ] **Item selection** works correctly - [ ] **Grouped lists** display with proper headers - [ ] **Swipe actions** (if used) work on both iOS and Android - [ ] **Empty view** appears when list is empty - [ ] **Pull to refresh** works (if used) - [ ] **Scroll performance** is acceptable (especially for large lists) - [ ] **Item sizing** is correct (CollectionView auto-sizes by default) - [ ] **Selection visual state** shows/hides correctly - [ ] **Data binding** updates the list correctly - [ ] **Navigation** from list items works #### Migration Complexity Factors ListView to CollectionView migration is complex because: - Each ListView may have unique behaviors - Platform-specific code needs updating - Extensive testing required - Context actions need SwipeView conversion - Grouped lists need template updates - ViewModel changes may be needed #### Quick Reference: ListView vs CollectionView | Feature | ListView | CollectionView | |---------|----------|----------------| | **Selection Event** | `ItemSelected` | `SelectionChanged` | | **Selection Args** | `SelectedItemChangedEventArgs` | `SelectionChangedEventArgs` | | **Getting Selected** | `e.SelectedItem` | `e.CurrentSelection.FirstOrDefault()` | | **Context Menus** | `ContextActions` | `SwipeView` | | **Grouping** | `IsGroupingEnabled="True"` | `IsGrouped="true"` | | **Group Header** | `GroupDisplayBinding` | `GroupHeaderTemplate` | | **Even Rows** | `HasUnevenRows="False"` | Auto-sizes (default) | | **Empty State** | Manual | `EmptyView` property | | **Cells** | TextCell, ImageCell, etc. | Custom DataTemplate | --- ## Deprecated APIs (P1 - Fix Soon) These APIs still work in .NET 10 but show compiler warnings. They will be removed in future versions. ### 1. Animation Methods **Status:** âš ī¸ **DEPRECATED** - All sync animation methods replaced with async versions. **Warning You'll See:** ``` warning CS0618: 'ViewExtensions.FadeTo(VisualElement, double, uint, Easing)' is obsolete: 'Please use FadeToAsync instead.' ``` **Migration Table:** | Old Method | New Method | Example | |-----------|-----------|---------| | `FadeTo()` | `FadeToAsync()` | `await view.FadeToAsync(0, 500);` | | `ScaleTo()` | `ScaleToAsync()` | `await view.ScaleToAsync(1.5, 300);` | | `TranslateTo()` | `TranslateToAsync()` | `await view.TranslateToAsync(100, 100, 250);` | | `RotateTo()` | `RotateToAsync()` | `await view.RotateToAsync(360, 500);` | | `RotateXTo()` | `RotateXToAsync()` | `await view.RotateXToAsync(45, 300);` | | `RotateYTo()` | `RotateYToAsync()` | `await view.RotateYToAsync(45, 300);` | | `ScaleXTo()` | `ScaleXToAsync()` | `await view.ScaleXToAsync(2.0, 300);` | | `ScaleYTo()` | `ScaleYToAsync()` | `await view.ScaleYToAsync(2.0, 300);` | | `RelRotateTo()` | `RelRotateToAsync()` | `await view.RelRotateToAsync(90, 300);` | | `RelScaleTo()` | `RelScaleToAsync()` | `await view.RelScaleToAsync(0.5, 300);` | | `LayoutTo()` | `LayoutToAsync()` | See special note below | #### Migration Examples **Simple Animation:** ```csharp // ❌ OLD (Deprecated) await myButton.FadeTo(0, 500); await myButton.ScaleTo(1.5, 300); await myButton.TranslateTo(100, 100, 250); // ✅ NEW (Required) await myButton.FadeToAsync(0, 500); await myButton.ScaleToAsync(1.5, 300); await myButton.TranslateToAsync(100, 100, 250); ``` **Sequential Animations:** ```csharp // ❌ OLD await image.FadeTo(0, 300); await image.ScaleTo(0.5, 300); await image.FadeTo(1, 300); // ✅ NEW await image.FadeToAsync(0, 300); await image.ScaleToAsync(0.5, 300); await image.FadeToAsync(1, 300); ``` **Parallel Animations:** ```csharp // ❌ OLD await Task.WhenAll( image.FadeTo(0, 300), image.ScaleTo(0.5, 300), image.RotateTo(360, 300) ); // ✅ NEW await Task.WhenAll( image.FadeToAsync(0, 300), image.ScaleToAsync(0.5, 300), image.RotateToAsync(360, 300) ); ``` **With Cancellation:** ```csharp // NEW: Async methods support cancellation CancellationTokenSource cts = new(); try { await view.FadeToAsync(0, 2000); } catch (TaskCanceledException) { // Animation was cancelled } // Cancel from elsewhere cts.Cancel(); ``` #### Special Case: LayoutTo `LayoutToAsync()` is deprecated with a special message: "Use Translation to animate layout changes." ```csharp // ❌ OLD (Deprecated) await view.LayoutToAsync(new Rect(100, 100, 200, 200), 250); // ✅ NEW (Use TranslateToAsync instead) await view.TranslateToAsync(100, 100, 250); // Or animate Translation properties directly var animation = new Animation(v => view.TranslationX = v, 0, 100); animation.Commit(view, "MoveX", length: 250); ``` --- ### 2. DisplayAlert and DisplayActionSheet **Status:** âš ī¸ **DEPRECATED** - Sync methods replaced with async versions. **Warning You'll See:** ``` warning CS0618: 'Page.DisplayAlert(string, string, string)' is obsolete: 'Use DisplayAlertAsync instead' ``` #### Migration Examples **DisplayAlert:** ```csharp // ❌ OLD (Deprecated) await DisplayAlert("Success", "Data saved successfully", "OK"); await DisplayAlert("Error", "Failed to save", "Cancel"); bool result = await DisplayAlert("Confirm", "Delete this item?", "Yes", "No"); // ✅ NEW (Required) await DisplayAlertAsync("Success", "Data saved successfully", "OK"); await DisplayAlertAsync("Error", "Failed to save", "Cancel"); bool result = await DisplayAlertAsync("Confirm", "Delete this item?", "Yes", "No"); ``` **DisplayActionSheet:** ```csharp // ❌ OLD (Deprecated) string action = await DisplayActionSheet( "Choose an action", "Cancel", "Delete", "Edit", "Share", "Duplicate" ); // ✅ NEW (Required) string action = await DisplayActionSheetAsync( "Choose an action", "Cancel", "Delete", "Edit", "Share", "Duplicate" ); ``` **In ViewModels (with IDispatcher):** ```csharp // If you're calling from a ViewModel, you'll need access to a Page public class MyViewModel { private readonly IDispatcher _dispatcher; private readonly Page _page; public MyViewModel(IDispatcher dispatcher, Page page) { _dispatcher = dispatcher; _page = page; } public async Task ShowAlertAsync() { await _dispatcher.DispatchAsync(async () => { await _page.DisplayAlertAsync("Info", "Message from ViewModel", "OK"); }); } } ``` --- ### 3. Page.IsBusy **Status:** âš ī¸ **DEPRECATED** - Property will be removed in .NET 11. **Warning You'll See:** ``` warning CS0618: 'Page.IsBusy' is obsolete: 'Page.IsBusy has been deprecated and will be removed in .NET 11' ``` **Why It's Deprecated:** - Inconsistent behavior across platforms - Limited customization options - Doesn't work well with modern MVVM patterns #### Migration Examples **Simple Page:** ```xaml ``` **With Loading Overlay:** ```xaml ``` **In Code-Behind:** ```csharp // ❌ OLD (Deprecated) public partial class MyPage : ContentPage { async Task LoadDataAsync() { IsBusy = true; try { await LoadDataFromServerAsync(); } finally { IsBusy = false; } } } // ✅ NEW (Recommended) public partial class MyPage : ContentPage { async Task LoadDataAsync() { LoadingIndicator.IsVisible = true; LoadingIndicator.IsRunning = true; try { await LoadDataFromServerAsync(); } finally { LoadingIndicator.IsVisible = false; LoadingIndicator.IsRunning = false; } } } ``` **In ViewModel:** ```csharp public class MyViewModel : INotifyPropertyChanged { private bool _isLoading; public bool IsLoading { get => _isLoading; set { _isLoading = value; OnPropertyChanged(); } } public async Task LoadDataAsync() { IsLoading = true; try { await LoadDataFromServerAsync(); } finally { IsLoading = false; } } } ``` --- ## Recommended Changes (P2) These changes are recommended but not required immediately. Consider migrating during your next refactoring cycle. ### Application.MainPage **Status:** âš ī¸ **DEPRECATED** - Property will be removed in future version. **Warning You'll See:** ``` warning CS0618: 'Application.MainPage' is obsolete: 'This property is deprecated. Initialize your application by overriding Application.CreateWindow...' ``` #### Migration Example ```csharp // ❌ OLD (Deprecated) public partial class App : Application { public App() { InitializeComponent(); MainPage = new AppShell(); } // Changing page later public void SwitchToLoginPage() { MainPage = new LoginPage(); } } // ✅ NEW (Recommended) public partial class App : Application { public App() { InitializeComponent(); } protected override Window CreateWindow(IActivationState? activationState) { return new Window(new AppShell()); } // Changing page later public void SwitchToLoginPage() { if (Windows.Count > 0) { Windows[0].Page = new LoginPage(); } } } ``` **Benefits of CreateWindow:** - Better multi-window support - More explicit initialization - Cleaner separation of concerns - Works better with Shell --- ## Bulk Migration Tools Use these find/replace patterns to quickly update your codebase. ### Visual Studio / VS Code **Regex Mode - Find/Replace** #### Animation Methods ```regex Find: \.FadeTo\( Replace: .FadeToAsync( Find: \.ScaleTo\( Replace: .ScaleToAsync( Find: \.TranslateTo\( Replace: .TranslateToAsync( Find: \.RotateTo\( Replace: .RotateToAsync( Find: \.RotateXTo\( Replace: .RotateXToAsync( Find: \.RotateYTo\( Replace: .RotateYToAsync( Find: \.ScaleXTo\( Replace: .ScaleXToAsync( Find: \.ScaleYTo\( Replace: .ScaleYToAsync( Find: \.RelRotateTo\( Replace: .RelRotateToAsync( Find: \.RelScaleTo\( Replace: .RelScaleToAsync( ``` #### Display Methods ```regex Find: DisplayAlert\( Replace: DisplayAlertAsync( Find: DisplayActionSheet\( Replace: DisplayActionSheetAsync( ``` #### ListView/TableView Detection (Manual Migration Required) **âš ī¸ Note:** ListView/TableView migration CANNOT be automated. Use these searches to find instances: ```bash # Find all ListView usages in XAML grep -r " migration-report.txt echo "" >> migration-report.txt echo "XAML ListView instances:" >> migration-report.txt grep -rn "> migration-report.txt echo "" >> migration-report.txt echo "XAML TableView instances:" >> migration-report.txt grep -rn "> migration-report.txt echo "" >> migration-report.txt echo "ItemSelected handlers:" >> migration-report.txt grep -rn "ItemSelected" --include="*.xaml" --include="*.cs" . >> migration-report.txt echo "" >> migration-report.txt cat migration-report.txt ``` ### PowerShell Script ```powershell # Replace animation methods in all .cs files Get-ChildItem -Path . -Recurse -Filter *.cs | ForEach-Object { $content = Get-Content $_.FullName -Raw # Animation methods $content = $content -replace '\.FadeTo\(', '.FadeToAsync(' $content = $content -replace '\.ScaleTo\(', '.ScaleToAsync(' $content = $content -replace '\.TranslateTo\(', '.TranslateToAsync(' $content = $content -replace '\.RotateTo\(', '.RotateToAsync(' $content = $content -replace '\.RotateXTo\(', '.RotateXToAsync(' $content = $content -replace '\.RotateYTo\(', '.RotateYToAsync(' $content = $content -replace '\.ScaleXTo\(', '.ScaleXToAsync(' $content = $content -replace '\.ScaleYTo\(', '.ScaleYToAsync(' $content = $content -replace '\.RelRotateTo\(', '.RelRotateToAsync(' $content = $content -replace '\.RelScaleTo\(', '.RelScaleToAsync(' # Display methods $content = $content -replace 'DisplayAlert\(', 'DisplayAlertAsync(' $content = $content -replace 'DisplayActionSheet\(', 'DisplayActionSheetAsync(' Set-Content $_.FullName $content } Write-Host "✅ Migration complete!" ``` --- ## Testing Your Upgrade ### Build Validation ```bash # Clean solution dotnet clean # Restore packages dotnet restore # Build for each platform dotnet build -f net10.0-android -c Release dotnet build -f net10.0-ios -c Release dotnet build -f net10.0-maccatalyst -c Release dotnet build -f net10.0-windows -c Release # Check for warnings dotnet build --no-incremental 2>&1 | grep -i "warning CS0618" ``` ### Enable Warnings as Errors (Temporary) ```xml CS0618 ``` ### Test Checklist - [ ] App launches successfully on all platforms - [ ] All animations work correctly - [ ] Dialogs (alerts/action sheets) display properly - [ ] Loading indicators work (if you used IsBusy) - [ ] Inter-component communication works (MessagingCenter replacement) - [ ] No CS0618 warnings in build output - [ ] No runtime exceptions related to obsolete APIs --- ## Troubleshooting ### Error: 'MessagingCenter' is inaccessible due to its protection level **Cause:** MessagingCenter is now internal in .NET 10. **Solution:** 1. Install `CommunityToolkit.Mvvm` package 2. Replace with `WeakReferenceMessenger` (see [MessagingCenter section](#messagingcenter-made-internal)) 3. Create message classes for each message type 4. Don't forget to unregister! --- ### Warning: Animation method is obsolete **Cause:** Using sync animation methods (`FadeTo`, `ScaleTo`, etc.) **Quick Fix:** ```bash # Use PowerShell script from Bulk Migration Tools section # Or use Find/Replace patterns ``` **Manual Fix:** Add `Async` to the end of each animation method call: - `FadeTo` → `FadeToAsync` - `ScaleTo` → `ScaleToAsync` - etc. --- ### Page.IsBusy doesn't work anymore **Cause:** IsBusy still works but is deprecated. **Solution:** Replace with explicit ActivityIndicator (see [IsBusy section](#3-pageisbusy)) --- ### Build fails with "Target framework 'net10.0' not found" **Cause:** .NET 10 SDK not installed or not latest version. **Solution:** ```bash # Check SDK version dotnet --version # Should be 10.0.100 or later # Install .NET 10 SDK from: # https://dotnet.microsoft.com/download/dotnet/10.0 # Update workloads dotnet workload update ``` --- ### MessagingCenter migration breaks existing code **Common Issues:** 1. **Forgot to unregister:** ```csharp // âš ī¸ Memory leak if you don't unregister protected override void OnDisappearing() { base.OnDisappearing(); WeakReferenceMessenger.Default.UnregisterAll(this); } ``` 2. **Wrong message type:** ```csharp // ❌ Wrong WeakReferenceMessenger.Default.Register(this, handler); WeakReferenceMessenger.Default.Send(new UserData()); // Wrong type! // ✅ Correct WeakReferenceMessenger.Default.Register(this, handler); WeakReferenceMessenger.Default.Send(new UserLoggedInMessage(userData)); ``` 3. **Recipient parameter confusion:** ```csharp // The recipient parameter is the object that registered (this) WeakReferenceMessenger.Default.Register(this, (recipient, message) => { // recipient == this // message == the message that was sent }); ``` --- ### Animation doesn't complete after migration **Cause:** Forgetting `await` keyword. ```csharp // ❌ Wrong - animation runs but code continues immediately view.FadeToAsync(0, 500); DoSomethingElse(); // ✅ Correct - wait for animation to complete await view.FadeToAsync(0, 500); DoSomethingElse(); ``` --- ### Warning: ListView/TableView/TextCell is obsolete **Cause:** Using deprecated ListView, TableView, or Cell types. **Solution:** Migrate to CollectionView (see [ListView and TableView section](#listview-and-tableview-deprecated)) **Quick Decision Guide:** - **Simple list** → CollectionView with custom DataTemplate - **Settings page with <20 items** → VerticalStackLayout with BindableLayout - **Settings page with 20+ items** → Grouped CollectionView - **Grouped data list** → CollectionView with `IsGrouped="True"` --- ### CollectionView doesn't have SelectedItem event **Cause:** CollectionView uses `SelectionChanged` instead of `ItemSelected`. **Solution:** ```csharp // ❌ OLD (ListView) void OnItemSelected(object sender, SelectedItemChangedEventArgs e) { var item = e.SelectedItem as MyItem; } // ✅ NEW (CollectionView) void OnSelectionChanged(object sender, SelectionChangedEventArgs e) { var item = e.CurrentSelection.FirstOrDefault() as MyItem; } ``` --- ### Platform-specific ListView configuration is obsolete **Cause:** Using `Microsoft.Maui.Controls.PlatformConfiguration.*Specific.ListView` extensions. **Error:** ``` warning CS0618: 'ListView' is obsolete: 'With the deprecation of ListView, this class is obsolete. Please use CollectionView instead.' ``` **Solution:** 1. Remove platform-specific ListView using statements: ```csharp // ❌ Remove these using Microsoft.Maui.Controls.PlatformConfiguration; using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific; using Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific; ``` 2. Remove platform-specific ListView calls: ```csharp // ❌ Remove these myListView.On().SetSeparatorStyle(SeparatorStyle.FullWidth); myListView.On().IsFastScrollEnabled(); viewCell.On().SetDefaultBackgroundColor(Colors.White); ``` 3. CollectionView has different platform customization options - consult CollectionView docs for alternatives. --- ### CollectionView performance issues after ListView migration **Common Causes:** 1. **Not using DataTemplate caching:** ```xaml ``` 2. **Complex nested layouts:** - Avoid deeply nested layouts in ItemTemplate - Use Grid instead of StackLayout when possible - Consider FlexLayout for complex layouts 3. **Images not being cached:** ```xaml ``` --- ## Quick Reference Card ### Priority Checklist **Must Fix (P0 - Breaking/Critical):** - [ ] Replace `MessagingCenter` with `WeakReferenceMessenger` - [ ] Migrate `ListView` to `CollectionView` - [ ] Migrate `TableView` to `CollectionView` or `BindableLayout` - [ ] Replace `TextCell`, `ImageCell`, etc. with custom DataTemplates - [ ] Convert `ContextActions` to `SwipeView` - [ ] Remove platform-specific ListView configurations **Should Fix (P1 - Deprecated):** - [ ] Update animation methods: add `Async` suffix - [ ] Update `DisplayAlert` → `DisplayAlertAsync` - [ ] Update `DisplayActionSheet` → `DisplayActionSheetAsync` - [ ] Replace `Page.IsBusy` with `ActivityIndicator` **Nice to Have (P2):** - [ ] Migrate `Application.MainPage` to `CreateWindow` ### Common Patterns ```csharp // Animation await view.FadeToAsync(0, 500); // Alert await DisplayAlertAsync("Title", "Message", "OK"); // Messaging WeakReferenceMessenger.Default.Send(new MyMessage()); WeakReferenceMessenger.Default.Register(this, (r, m) => { }); WeakReferenceMessenger.Default.UnregisterAll(this); // Loading IsLoading = true; try { await LoadAsync(); } finally { IsLoading = false; } ``` --- ## Additional Resources - **Official Docs:** https://learn.microsoft.com/dotnet/maui/ - **Migration Guide:** https://learn.microsoft.com/dotnet/maui/migration/ - **GitHub Issues:** https://github.com/dotnet/maui/issues - **CommunityToolkit.Mvvm:** https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/ --- **Document Version:** 2.0 **Last Updated:** November 2025 **Applies To:** .NET MAUI 10.0.100 and later