What’s New in .NET 10 for MAUI Developers: CollectionView Default, ListView Deprecated

·

·

If your .NET MAUI app has a list — and most do — .NET 10 has something to say about it. ListView, the original list control that every Xamarin.Forms developer knows, is officially deprecated. CollectionView is now the unambiguous default, and the new project templates reflect this.

This isn’t just a recommendation change. Deprecation in .NET means you’ll see build warnings today and face removal in a future version. Here’s everything you need to know.

Table of Contents

Why Did This Change Happen in .NET 10?

ListView was designed for Xamarin.Forms and carries significant architectural baggage. It was built on platform-specific list implementations with minimal abstraction — on Android it used a ListView widget, on iOS a UITableView. Every customization required platform-specific renderer overrides.

ListView’s Known Limitations

  • Requires ViewCell wrappers around all item templates — adds overhead and XML noise
  • No built-in grid layout — only vertical lists
  • No native multiple selection API
  • No EmptyView support without hacks
  • Group headers are clunky compared to CollectionView’s implementation
  • Performance degrades measurably on large lists due to non-virtualized rendering in some scenarios

CollectionView was purpose-built to fix every one of these issues. With two major MAUI versions of CollectionView proving its stability, the MAUI team was ready to make the call.

What “Deprecated” Actually Means

In .NET, deprecation follows a defined lifecycle:

  1. Deprecated (.NET 10) — [Obsolete] attribute applied, IDE warnings shown, build warnings appear with warning codes
  2. Removed (future version) — compile errors if still used; typically 2 major versions after deprecation

Current status: ListView is decorated with [Obsolete(DiagnosticId = "MA0001")] in .NET 10. You can suppress warnings per-project if needed:

<!-- .csproj — suppress ListView deprecation warnings temporarily -->
<PropertyGroup>
    <NoWarn>$(NoWarn);MA0001</NoWarn>
</PropertyGroup>

This buys time, but the deprecation warning exists for a reason. Plan your migration within the next 1–2 release cycles.

CollectionView as the New Default

Starting with .NET 10, all MAUI project templates and scaffolding use CollectionView exclusively. The official documentation landing pages for “displaying lists” now point to CollectionView with no mention of ListView.

New Template Output

<!-- .NET 10 dotnet new maui template output -->
<CollectionView ItemsSource="{Binding Items}"
                SelectionMode="Single"
                SelectedItem="{Binding SelectedItem}">
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="model:ItemModel">
            <Grid Padding="10">
                <Label Text="{Binding Title}" />
            </Grid>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

.NET 10 CollectionView New Features

Beyond being the default, CollectionView itself gains new capabilities in .NET 10.

Scroll Anchoring Modes

Three new scroll anchoring modes control behavior when items are added:

<CollectionView ItemsUpdatingScrollMode="KeepLastItemInView">
<!-- Also available: KeepScrollOffset, KeepItemsInView -->
  • KeepLastItemInView — scrolls to show the last item when new items are added (ideal for chat)
  • KeepScrollOffset — maintains current scroll offset when items prepend (ideal for “load earlier” pattern)
  • KeepItemsInView — keeps currently visible items in view (default)

Header and Footer Templates

Sticky headers and footers now work correctly on all platforms in .NET 10 — previously they had inconsistent behavior on Android:

<CollectionView ItemsSource="{Binding Products}">
    <CollectionView.Header>
        <Label Text="Product Catalog"
               FontSize="20"
               FontAttributes="Bold"
               Padding="12,16" />
    </CollectionView.Header>

    <CollectionView.Footer>
        <Button Text="Load More"
                Command="{Binding LoadMoreCommand}"
                Margin="12" />
    </CollectionView.Footer>
</CollectionView>

Improved Grouping Performance

Group header rendering performance improved significantly on Android — group headers now render using the same virtualization path as item cells, rather than being rendered outside the recycling pool.

<CollectionView ItemsSource="{Binding GroupedProducts}"
                IsGrouped="True">
    <CollectionView.GroupHeaderTemplate>
        <DataTemplate x:DataType="model:ProductGroup">
            <Label Text="{Binding CategoryName}"
                   BackgroundColor="#F5F5F5"
                   Padding="12,8"
                   FontAttributes="Bold" />
        </DataTemplate>
    </CollectionView.GroupHeaderTemplate>
</CollectionView>

Migration Guide: ListView to CollectionView

The migration is mostly mechanical. Here’s the full mapping:

<!-- BEFORE: ListView -->
<ListView x:Name="MyList"
          ItemsSource="{Binding Items}"
          HasUnevenRows="True"
          SeparatorVisibility="None"
          SeparatorColor="Transparent"
          IsPullToRefreshEnabled="True"
          RefreshCommand="{Binding RefreshCommand}"
          IsRefreshing="{Binding IsRefreshing}"
          ItemSelected="OnItemSelected">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <Grid Padding="12">
                    <Label Text="{Binding Title}" />
                </Grid>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

<!-- AFTER: CollectionView -->
<RefreshView Command="{Binding RefreshCommand}"
             IsRefreshing="{Binding IsRefreshing}">
    <CollectionView x:Name="MyList"
                    ItemsSource="{Binding Items}"
                    SelectionMode="Single"
                    SelectionChanged="OnSelectionChanged">
        <CollectionView.ItemTemplate>
            <DataTemplate>
                <Grid Padding="12">
                    <Label Text="{Binding Title}" />
                </Grid>
                <!-- No ViewCell needed -->
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
</RefreshView>

Key differences to remember:

  • Remove all ViewCell wrappers from DataTemplates
  • Pull-to-refresh moves to a RefreshView wrapper (CollectionView doesn’t have it built in)
  • ItemSelected event → SelectionChanged event (different event args)
  • HasUnevenRows is not needed — CollectionView measures each item automatically
  • SeparatorVisibility is not available — add separators manually in the DataTemplate if needed

Migrating Common ListView Patterns

Cell Tap Navigation

// ListView (old)
private void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
    if (e.SelectedItem is Product product)
        Navigation.PushAsync(new ProductDetailPage(product));
    ((ListView)sender).SelectedItem = null; // Deselect
}

// CollectionView (new) — use command binding in ViewModel
[RelayCommand]
private async Task SelectProductAsync(Product product)
{
    await Shell.Current.GoToAsync("productdetail",
        new Dictionary<string, object> { { "Product", product } });
}

// XAML
<CollectionView SelectionMode="None">
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="model:Product">
            <Grid>
                <Grid.GestureRecognizers>
                    <TapGestureRecognizer
                        Command="{Binding Source={RelativeSource AncestorType={x:Type vm:ProductListViewModel}}, Path=SelectProductCommand}"
                        CommandParameter="{Binding .}" />
                </Grid.GestureRecognizers>
                <Label Text="{Binding Name}" />
            </Grid>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

Context Actions (Swipe to Delete)

<!-- ListView context actions → CollectionView SwipeView -->
<CollectionView ItemsSource="{Binding Items}">
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="model:TaskItem">
            <SwipeView>
                <SwipeView.RightItems>
                    <SwipeItems>
                        <SwipeItem Text="Delete"
                                   BackgroundColor="Red"
                                   Command="{Binding Source={RelativeSource AncestorType={x:Type vm:TaskViewModel}}, Path=DeleteCommand}"
                                   CommandParameter="{Binding .}" />
                    </SwipeItems>
                </SwipeView.RightItems>
                <Grid Padding="12">
                    <Label Text="{Binding Title}" />
                </Grid>
            </SwipeView>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

Other .NET 10 MAUI Changes Worth Knowing

  • Frame → Border: Frame is deprecated alongside ListView. Replace with Border and StrokeShape="RoundRectangle 12"
  • Minimum platform targets raised: iOS 15+ and Android API 24+ required
  • Device.RuntimePlatform removed: use DeviceInfo.Platform (was deprecated since .NET 8)
  • .NET Aspire integration: MAUI apps can now join an Aspire AppHost for local dev service discovery
  • New Shell route guards: intercept navigation for auth checks without hacking Shell’s navigation pipeline

Upgrade Checklist

  • ☐ Update target frameworks to net10.0-android, net10.0-ios, etc. in .csproj
  • ☐ Run build — collect all MA0001 (ListView) and MA0002 (Frame) deprecation warnings
  • ☐ Replace each ListView with CollectionView (remove ViewCell wrappers)
  • ☐ Move pull-to-refresh from ListView to RefreshView wrappers
  • ☐ Migrate ItemSelected event handlers to SelectionChanged or command bindings
  • ☐ Replace Frame with Border
  • ☐ Remove any remaining Device.RuntimePlatform calls → DeviceInfo.Platform
  • ☐ Verify iOS minimum deployment target is 15.0+
  • ☐ Test all list screens on Android emulator and iOS simulator

FAQ

When will ListView actually be removed from MAUI?

No specific removal version is announced, but the pattern for .NET deprecations is removal 2 major versions after deprecation. That puts likely removal at .NET 12. Don’t wait — migrate in your next sprint cycle.

Does CollectionView support the exact same cell types as ListView?

CollectionView DataTemplates accept any View — remove the ViewCell wrapper and put your layout directly in the DataTemplate. TextCell, ImageCell, and other built-in cell types don’t have CollectionView equivalents — recreate their UI with Grid/Label/Image directly.

My ListView uses grouping with alphabet scrubber on iOS — does CollectionView support this?

The native iOS alphabet scrubber (section index) is not built into CollectionView. It requires a custom handler implementation. If this is a hard requirement, keep the ListView temporarily while building the handler, then migrate.

Is there a performance difference between ListView and CollectionView for small lists?

For under 20 items, the difference is negligible and not user-perceptible. Migrate for code consistency and to avoid future forced migration, not for performance gains at small scale.

Can I use both ListView and CollectionView in the same app during a phased migration?

Yes — suppress the MA0001 warning project-wide and migrate screen by screen. There’s no technical conflict with using both controls simultaneously.

Conclusion

The ListView deprecation in .NET 10 is the right call — CollectionView is strictly better in every measurable way. The migration is straightforward for most screens: remove ViewCell, move pull-to-refresh to RefreshView, update event handlers. The more complex cases (grouping with native scrubbers, heavy custom cells) need more planning but are still manageable.

Start with your highest-traffic screens and work through the build warning list systematically. Getting ahead of this now prevents a forced all-at-once migration when removal happens in a future release.

[INTERNAL_LINK: Migrating from ListView to CollectionView complete guide] [INTERNAL_LINK: .NET MAUI in .NET 10 all new features explained]


Leave a Reply

Your email address will not be published. Required fields are marked *