Just a few hours ago, the .NET MAUI team announced a significant change coming in .NET 10: the ListView
control and all its related cell types (TextCell
, ImageCell
, ViewCell
, etc.) will be marked as obsolete. This decision is part of Microsoft's strategy to streamline the developer experience by focusing on a single, optimized control for displaying collections of data. Which I'm personally happy with, the less duplicated controls to maintain the better.
If you've been using MAUI (or Xamarin.Forms before it), you're likely familiar with ListView
- it's been a staple for displaying lists of data since the early days. However, CollectionView
in the past had several performance and rendering issues, but these days thanks to Microsoft and the community, the Collection View offers numerous advantages, including better performance, more flexible layouts, and improved customization options. With .NET 10 on the horizon, now is the perfect time to migrate your existing ListView
implementations to CollectionView
.
In this article, I'll walk you through a straightforward migration process, highlighting the key differences between these controls and providing practical examples to ensure a smooth transition for your MAUI applications.
Understanding the Key Differences
Before diving into the migration process, it's helpful to understand some fundamental differences between ListView
and CollectionView
:
Feature | ListView | CollectionView |
---|---|---|
Cell Types | Uses predefined cells (TextCell , ImageCell , etc.) |
Uses DataTemplates directly |
Selection | Single or multiple selection with built-in visual feedback | Single or multiple but even more flexible selection with customizable visual feedback |
Item Appearance | Uses Cell hierarchy |
Uses direct DataTemplate
|
Layouts | Vertical list only | Vertical, horizontal, and grid layouts |
Performance | Less optimized | Better virtualization and performance |
Grouping | Through GroupDisplayBinding
|
More flexible grouping options |
Headers/Footers | Basic header/footer templates | Enhanced header/footer templates |
Step 1: Replace the Control Declaration
The first step is to replace the ListView
declaration with CollectionView
in your XAML:
Before (ListView):
x:Name="MyListView"
ItemsSource="{Binding Items}">
After (CollectionView):
x:Name="MyCollectionView"
ItemsSource="{Binding Items}">
Step 2: Convert Cell Templates to DataTemplates
One of the biggest differences is how items are templated. ListView
uses various cell types, while CollectionView
uses DataTemplate
directly.
Example: Converting TextCell
Before (ListView with TextCell):
ItemsSource="{Binding Items}">
Text="{Binding Title}"
Detail="{Binding Description}" />
After (CollectionView):
ItemsSource="{Binding Items}">
Padding="10">
Height="Auto" />
Height="Auto" />
Grid.Row="0" Text="{Binding Title}" FontAttributes="Bold" />
Grid.Row="1" Text="{Binding Description}" FontSize="Small" />
Example: Converting ImageCell
Before (ListView with ImageCell):
ItemsSource="{Binding Items}">
Text="{Binding Title}"
Detail="{Binding Description}"
ImageSource="{Binding ImageUrl}" />
After (CollectionView):
ItemsSource="{Binding Items}">
Padding="10" ColumnSpacing="10">
Width="50" />
Width="*" />
Height="Auto" />
Height="Auto" />
Grid.Row="0" Grid.RowSpan="2" Grid.Column="0"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="50" WidthRequest="50" />
Grid.Row="0" Grid.Column="1"
Text="{Binding Title}"
FontAttributes="Bold" />
Grid.Row="1" Grid.Column="1"
Text="{Binding Description}"
FontSize="Small" />
Step 3: Update Selection Handling
The selection mechanism differs between the two controls:
Before (ListView):
void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem == null)
return;
// Handle the selected item
var selectedItem = e.SelectedItem as MyItemType;
// Important: Deselect the item
((ListView)sender).SelectedItem = null;
}
After (CollectionView):
x:Name="collectionView"
SelectionMode="Single"
SelectionChanged="OnSelectionChanged">
async void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var previous = e.PreviousSelection.FirstOrDefault();
var current = e.CurrentSelection.FirstOrDefault();
}
Step 4: Convert Grouping
If you're using grouping in your ListView
, you'll need to adapt it for CollectionView
:
Before (ListView with grouping):
ItemsSource="{Binding GroupedItems}"
IsGroupingEnabled="True"
GroupDisplayBinding="{Binding Key}">
After (CollectionView with grouping):
ItemsSource="{Binding GroupedItems}"
IsGrouped="True">
Text="{Binding Key}"
FontAttributes="Bold"
BackgroundColor="LightGray"
Padding="10" />
Step 5: Headers and Footers
Converting headers and footers is straightforward:
Before (ListView):
ItemsSource="{Binding Items}">
Text="Items List" FontSize="Large" />
Text="End of list" />
After (CollectionView):
ItemsSource="{Binding Items}">
Text="Items List" FontSize="Large" />
Text="End of list" />
Step 6: Take Advantage of New Layout Options
One significant advantage of CollectionView
is its flexible layout options:
Vertical List (default):
ItemsSource="{Binding Items}">
Orientation="Vertical" ItemSpacing="5" />
Horizontal List:
ItemsSource="{Binding Items}">
Orientation="Horizontal" ItemSpacing="5" />
Grid Layout:
ItemsSource="{Binding Items}">
Orientation="Vertical"
Span="2"
HorizontalItemSpacing="5"
VerticalItemSpacing="5" />
Step 7: Handle Empty State
CollectionView
has better support for empty state handling:
ItemsSource="{Binding Items}">
Padding="20">
Text="No items available"
HorizontalOptions="Center"
VerticalOptions="Center" />
Text="Refresh" Command="{Binding RefreshCommand}" />
Common Challenges and Solutions
Challenge 1: Context Actions
ListView's ContextActions
don't have a direct equivalent in CollectionView
. Instead, you can use SwipeView
:
Before (ListView with ContextActions):
ItemsSource="{Binding Items}">
Text="Edit" Command="{Binding Source={RelativeSource AncestorType={x:Type vm:MyViewModel}}, Path=EditCommand}" CommandParameter="{Binding .}" />
Text="Delete" Command="{Binding Source={RelativeSource AncestorType={x:Type vm:MyViewModel}}, Path=DeleteCommand}" CommandParameter="{Binding .}" />
Text="{Binding Title}" />
After (CollectionView with SwipeView):
ItemsSource="{Binding Items}">
Text="Edit"
Command="{Binding Source={RelativeSource AncestorType={x:Type vm:MyViewModel}}, Path=EditCommand}"
CommandParameter="{Binding .}"
BackgroundColor="Blue" />
Text="Delete"
Command="{Binding Source={RelativeSource AncestorType={x:Type vm:MyViewModel}}, Path=DeleteCommand}"
CommandParameter="{Binding .}"
BackgroundColor="Red" />
Padding="10">
Text="{Binding Title}" />
Challenge 2: HasUnevenRows
ListView
had HasUnevenRows
for variable height rows. With CollectionView
, rows are automatically sized based on content:
Before (ListView):
ItemsSource="{Binding Items}" HasUnevenRows="True">
After (CollectionView):
ItemsSource="{Binding Items}">
Conclusion
With .NET 10 marking ListView
as obsolete, now is the ideal time to migrate your MAUI applications to use CollectionView
. The transition may require some initial effort, especially if you have complex templates or custom behaviors, but the benefits are substantial. CollectionView
offers better performance, more flexible layouts, and an overall improved developer experience.
The migration process outlined in this article provides a straightforward path to update your applications, covering the most common scenarios you'll encounter. By embracing CollectionView
now, you'll future-proof your applications and take advantage of the enhancements that Microsoft continues to make to this control.
Remember that while the obsolescence marking begins with .NET 10 Preview 3, ListView
will continue to function in .NET 10, giving you some time to complete your migration. However, bug fixes for ListView
will generally not be prioritized, so it's best to start the transition sooner rather than later.
Have you encountered any specific challenges in migrating from ListView
to CollectionView
? Let me know in the comments below, and let's work through them together!