Skip to main content
rule official · GitHub

dotnet-maui-9-to-dotnet-maui-10-upgrade — GitHub Copilot rule

Guides .NET MAUI migrations from version 9 to 10 by updating TargetFrameworks, replacing internal MessagingCenter calls, and migrating obsolete UI controls.

Install into GitHub Copilot · Manually: put the files in place All methods and platforms ↓
.github/instructions/dotnet-maui-9-to-dotnet-maui-10-upgrade.instructions.md
Open on GitHub

What dotnet-maui-9-to-dotnet-maui-10-upgrade does

This rule triggers on *.csproj, *.cs, and *.xaml files. It organizes the migration process. First, it switches target frameworks to net10.0. It also adds conditions to skip iOS and Mac Catalyst targets during Linux or GitHub Codespaces builds.

The primary fix addresses compiler blockers. Because MessagingCenter became internal in .NET 10, Copilot migrates code to WeakReferenceMessenger from CommunityToolkit.Mvvm 8.3.0 and introduces dedicated message classes. Runtime behavior also shifts. Registering duplicate subscriptions on one recipient throws an InvalidOperationException.

In XAML views, obsolete ListView and TableView declarations transition toward CollectionView. Legacy animation calls, DisplayAlert, and Page.IsBusy are marked for replacement. Without updating CommunityToolkit.Maui to version 12.3.0 or higher, the project fails to compile.

What it helps with

  • switch target frameworks to net10.0
  • replace internal MessagingCenter with WeakReferenceMessenger
  • configure Linux builds in Codespaces by excluding iOS targets
  • migrate deprecated ListView components to CollectionView

Example prompts for the agent

  • Upgrade this MAUI project to .NET 10 and swap MessagingCenter for WeakReferenceMessenger
  • Update target frameworks in csproj to support building on Linux without iOS tools
  • Scan this XAML view and migrate obsolete ListView elements to CollectionView

How to install dotnet-maui-9-to-dotnet-maui-10-upgrade

Manually: put the files in place

.github/instructions/dotnet-maui-9-to-dotnet-maui-10-upgrade.instructions.md

The applyTo field sets which files it applies to

Step by step, with commands for each platform: AGENTS.md vs CLAUDE.md: what they are and which one to use

Questions about dotnet-maui-9-to-dotnet-maui-10-upgrade

How to install dotnet-maui-9-to-dotnet-maui-10-upgrade in GitHub Copilot?

Save the rule to .github/instructions/dotnet-maui-9-to-dotnet-maui-10-upgrade.instructions.md in your repository. GitHub Copilot will apply its instructions to matching .csproj, .cs, and .xaml files.

What does dotnet-maui-9-to-dotnet-maui-10-upgrade do?

It guides the assistant through migrating a .NET MAUI 9 app to .NET 10, resolving breaking API changes and obsolete UI controls.

Why does WeakReferenceMessenger fail on duplicate registrations?

Unlike MessagingCenter, WeakReferenceMessenger throws an InvalidOperationException when registering the same message twice on one recipient. The migration requires unregistering first or merging actions into a single handler.

Source text

dotnet-maui-9-to-dotnet-maui-10-upgrade.instructions.md · MIT
---
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)
   - [Animation Methods](#1-animation-methods)
   - [DisplayAlert and DisplayActionSheet](#2-displayalert-and-displayactionsheet)
   - [Page.IsBusy](#3-pageisbusy)
   - [MediaPicker APIs](#4-mediapicker-apis)
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, MediaPicker (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
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
  </PropertyGroup>
</Project>
```

### Multi-Platform

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst;net10.0-windows10.0.19041.0</TargetFrameworks>
  </PropertyGroup>
</Project>
```

### 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
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <!-- Start with Android (always supported) -->
    <TargetFrameworks>net10.0-android</TargetFrameworks>
    
    <!-- Add iOS/Mac Catalyst only when NOT on Linux -->
    <TargetFrameworks Condition="!$([MSBuild]::IsOSPlatform('linux'))">$(TargetFrameworks);net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
    
    <!-- Add Windows only when on Windows -->
    <TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net10.0-windows10.0.19041.0</TargetFrameworks>
  </PropertyGroup>
</Project>
```

**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<App, string>(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<App, UserData>(this, "UserLoggedIn", (sender, data) =>
{
    // Handle message
    CurrentUser = data;
});

// ✅ NEW (Required)
WeakReferenceMessenger.Default.Register<UserLoggedInMessage>(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<UserLoggedInMessage>(this, (r, m) => Handler1(m));
WeakReferenceMessenger.Default.Register<UserLoggedInMessage>(this, (r, m) => Handler2(m)); // ❌ THROWS!

// ✅ Solution 1: Unregister before re-registering
WeakReferenceMessenger.Default.Unregister<UserLoggedInMessage>(this);
WeakReferenceMessenger.Default.Register<UserLoggedInMessage>(this, (r, m) => Handler1(m));

// ✅ Solution 2: Handle multiple actions in one registration
WeakReferenceMessenger.Default.Register<UserLoggedInMessage>(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<App, UserData>(this, "UserLoggedIn");

// ✅ NEW (CRITICAL - prevents memory leaks)
WeakReferenceMessenger.Default.Unregister<UserLoggedInMessage>(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<LoginViewModel, User>(this, "UserLoggedIn", (sender, user) =>
        {
            WelcomeLabel.Text = $"Welcome, {user.Name}!";
        });
    }
    
    protected override void OnDisappearing()
    {
        base.OnDisappearing();
        MessagingCenter.Unsubscribe<LoginViewModel, User>(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<UserLoggedInMessage>(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
<ListView ItemsSource="{Binding Items}"
          ItemSelected="OnItemSelected"
          HasUnevenRows="True">
    <ListView.ItemTemplate>
        <DataTemplate>
            <TextCell Text="{Binding Title}"
                     Detail="{Binding Description}" />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>
```

**After (CollectionView):**
```xaml
<CollectionView ItemsSource="{Binding Items}"
                SelectionMode="Single"
                SelectionChanged="OnSelectionChanged">
    <CollectionView.ItemTemplate>
        <DataTemplate>
            <VerticalStackLayout Padding="10">
                <Label Text="{Binding Title}" 
                       FontAttributes="Bold" />
                <Label Text="{Binding Description}"
                       FontSize="12"
                       TextColor="{StaticResource Gray600}" />
            </VerticalStackLayout>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>
```

> ⚠️ **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
<ListView ItemsSource="{Binding GroupedItems}"
          IsGroupingEnabled="True"
          GroupDisplayBinding="{Binding Key}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <TextCell Text="{Binding Name}" />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>
```

**After (Grouped CollectionView):**
```xaml
<CollectionView ItemsSource="{Binding GroupedItems}"
                IsGrouped="true">
    <CollectionView.GroupHeaderTemplate>
        <DataTemplate>
            <Label Text="{Binding Key}"
                   FontAttributes="Bold"
                   BackgroundColor="{StaticResource Gray100}"
                   Padding="10,5" />
        </DataTemplate>
    </CollectionView.GroupHeaderTemplate>
    
    <CollectionView.ItemTemplate>
        <DataTemplate>
            <VerticalStackLayout Padding="20,10">
                <Label Text="{Binding Name}" />
            </VerticalStackLayout>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>
```

**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
<ListView.ItemTemplate>
    <DataTemplate>
        <ViewCell>
            <ViewCell.ContextActions>
                <MenuItem Text="Delete" 
                         IsDestructive="True"
                         Command="{Binding Source={RelativeSource AncestorType={x:Type local:MyPage}}, Path=DeleteCommand}"
                         CommandParameter="{Binding .}" />
            </ViewCell.ContextActions>
            
            <Label Text="{Binding Title}" Padding="10" />
        </ViewCell>
    </DataTemplate>
</ListView.ItemTemplate>
```

**After (CollectionView with SwipeView):**
```xaml
<CollectionView.ItemTemplate>
    <DataTemplate>
        <SwipeView>
            <SwipeView.RightItems>
                <SwipeItems>
                    <SwipeItem Text="Delete"
                              BackgroundColor="Red"
                              Command="{Binding Source={RelativeSource AncestorType={x:Type local:MyPage}}, Path=DeleteCommand}"
                              CommandParameter="{Binding .}" />
                </SwipeItems>
            </SwipeView.RightItems>
            
            <VerticalStackLayout Padding="10">
                <Label Text="{Binding Title}" />
            </VerticalStackLayout>
        </SwipeView>
    </DataTemplate>
</CollectionView.ItemTemplate>
```

**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
<CollectionView ItemsSource="{Binding SettingGroups}"
                IsGrouped="true"
                SelectionMode="None">
    <CollectionView.GroupHeaderTemplate>
        <DataTemplate>
            <Label Text="{Binding Title}" 
                   FontAttributes="Bold"
                   Margin="10,15,10,5" />
        </DataTemplate>
    </CollectionView.GroupHeaderTemplate>
    
    <CollectionView.ItemTemplate>
        <DataTemplate>
            <Grid Padding="15,10" ColumnDefinitions="*,Auto">
                <Label Text="{Binding Title}" 
                       VerticalOptions="Center" />
                <Switch Grid.Column="1" 
                        IsToggled="{Binding IsEnabled}"
                        IsVisible="{Binding ShowSwitch}" />
            </Grid>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>
```

**Option 2: Vertical StackLayout (for small settings lists)**
```xaml
<ScrollView>
    <VerticalStackLayout BindableLayout.ItemsSource="{Binding Settings}"
                        Spacing="10"
                        Padding="15">
        <BindableLayout.ItemTemplate>
            <DataTemplate>
                <Border StrokeThickness="0"
                       BackgroundColor="{StaticResource Gray100}"
                       Padding="15,10">
                    <Grid ColumnDefinitions="*,Auto">
                        <Label Text="{Binding Title}" 
                              VerticalOptions="Center" />
                        <Switch Grid.Column="1" 
                               IsToggled="{Binding IsEnabled}" />
                    </Grid>
                </Border>
            </DataTemplate>
        </BindableLayout.ItemTemplate>
    </VerticalStackLayout>
</ScrollView>
```

**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<iOS>().SetSeparatorStyle(SeparatorStyle.FullWidth);
myListView.On<Android>().IsFastScrollEnabled();

// ❌ OLD - Remove Cell platform configurations (NOW OBSOLETE IN .NET 10)
viewCell.On<iOS>().SetDefaultBackgroundColor(Colors.White);
viewCell.On<Android>().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
<CollectionView.ItemTemplate>
    <DataTemplate>
        <Grid>
            <Grid.BackgroundColor>
                <OnPlatform x:TypeArguments="Color">
                    <On Platform="iOS" Value="White" />
                    <On Platform="Android" Value="Transparent" />
                </OnPlatform>
            </Grid.BackgroundColor>
            <!-- Cell content -->
        </Grid>
    </DataTemplate>
</CollectionView.ItemTemplate>
```

#### Common Patterns & Pitfalls

**1. Empty View**
```xaml
<!-- CollectionView has built-in EmptyView support -->
<CollectionView ItemsSource="{Binding Items}">
    <CollectionView.EmptyView>
        <ContentView>
            <VerticalStackLayout Padding="50" VerticalOptions="Center">
                <Label Text="No items found" 
                       HorizontalTextAlignment="Center" />
            </VerticalStackLayout>
        </ContentView>
    </CollectionView.EmptyView>
    <!-- ... -->
</CollectionView>
```

**2. Pull to Refresh**
```xaml
<RefreshView IsRefreshing="{Binding IsRefreshing}"
             Command="{Binding RefreshCommand}">
    <CollectionView ItemsSource="{Binding Items}">
        <!-- ... -->
    </CollectionView>
</RefreshView>
```

**3. Item Spacing**
```xaml
<!-- Use ItemsLayout for spacing -->
<CollectionView ItemsSource="{Binding Items}">
    <CollectionView.ItemsLayout>
        <LinearItemsLayout Orientation="Vertical" 
                          ItemSpacing="10" />
    </CollectionView.ItemsLayout>
    <!-- ... -->
</CollectionView>
```

**4. Header and Footer**
```xaml
<CollectionView ItemsSource="{Binding Items}">
    <CollectionView.Header>
        <Label Text="My List" 
               FontSize="24" 
               Padding="10" />
    </CollectionView.Header>
    
    <CollectionView.Footer>
        <Label Text="End of list" 
               Padding="10" 
               HorizontalTextAlignment="Center" />
    </CollectionView.Footer>
    
    <!-- ItemTemplate -->
</CollectionView>
```

**5. Load More / Infinite Scroll**
```xaml
<CollectionView ItemsSource="{Binding Items}"
                RemainingItemsThreshold="5"
                RemainingItemsThresholdReachedCommand="{Binding LoadMoreCommand}">
    <!-- ItemTemplate -->
</CollectionView>
```

**6. Item Sizing Optimization**

CollectionView uses `ItemSizingStrategy` to control item measurement:

```xaml
<!-- Default: Each item measured individually (like HasUnevenRows="True") -->
<CollectionView ItemSizingStrategy="MeasureAllItems">
    <!-- ... -->
</CollectionView>

<!-- Performance: Only first item measured, rest use same height -->
<CollectionView ItemSizingStrategy="MeasureFirstItem">
    <!-- Use this when all items have similar heights -->
</CollectionView>
```

> 💡 **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<CollectionView, CollectionViewHandler2>();
    handlers.AddHandler<CarouselView, CarouselViewHandler2>();
});
#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<Microsoft.Maui.Controls.CollectionView, 
                        Microsoft.Maui.Controls.Handlers.Items.CollectionViewHandler>();
});
#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
<!-- ❌ OLD (Deprecated) -->
<ContentPage IsBusy="{Binding IsLoading}">
    <StackLayout>
        <Label Text="Content here" />
    </StackLayout>
</ContentPage>

<!-- ✅ NEW (Recommended) -->
<ContentPage>
    <Grid>
        <!-- Main content -->
        <StackLayout>
            <Label Text="Content here" />
        </StackLayout>
        
        <!-- Loading indicator overlay -->
        <ActivityIndicator IsRunning="{Binding IsLoading}"
                          IsVisible="{Binding IsLoading}"
                          Color="{StaticResource Primary}"
                          VerticalOpti...

© github · MIT · github/awesome-copilot

Similar IDE rules

Everything for Mobile →

From the same set770

The whole set →
draw-io Tells Copilot how to build and edit draw.io diagrams: mxGraph XML structure, a semantic colour palette, grid alignment and a validation script. ★ 40,418repo stars dotnet-wpf Directs GitHub Copilot to write WPF desktop code using the MVVM pattern on .NET 8.0+. It prevents business logic leaks into code-behind files. ★ 40,418repo stars dotnet-upgrade A GitHub Copilot rule for staged .NET upgrades, covering project dependencies, builds, tests, and CI/CD checks. ★ 40,418repo stars dotnet-maui This rule steers Copilot toward idiomatic .NET MAUI: MVVM, CollectionView over ListView, Shell navigation. Useful once the agent starts editing XAML and C#. ★ 40,418repo stars dotnet-framework This rule guides Copilot through .NET Framework projects, from .csproj conventions to C# 7.3 limits. Use it when you want generated code and build advice to follow those constraints. ★ 40,418repo stars devops-core-principles Guides GitHub Copilot on CALMS principles, DORA metrics, and delivery automation across CI/CD pipelines and infrastructure as code. ★ 40,418repo stars devcontainers Twelve rules that make Copilot review devcontainer.json, Dev Container Features and Codespaces lifecycle settings, each with the exact fix and a source. ★ 40,418repo stars devbox-image-definition A GitHub Copilot rule that helps you write YAML image definition files for Microsoft Dev Box Team Customizations. Useful when your customizations rely on ~/winget, ~/gitclone and ~/powershell tasks. ★ 40,418repo stars

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor