Search code examples
c#mvvmmauiobservablecollectioncommunity-toolkit-mvvm

Showing ObservableCollection item changes using CommunityToolkit.Mvvm in .NET MAUI


I went through SO questions on this topic and thought I followed the examples carefully but I still don't see a UI update when a property of an item in a CollectionView gets updated.

First, I made sure the property in my model is decorated with ObservableProperty:


public partial class Student : ObservableObject
{
   public int Id { get; set; }

   public string Name { get; set; }

   [ObservableProperty]
   bool isRegistered; 
}

I then use an ObservableCollection in my view model which is also decorated with ObservableProperty:


public partial class MyViewModel : ObservableObject
{
   public MyViewModel()
   {
      var student = new Student
      {
          Id = 123,
          Name = "John Doe",
          IsRegistered = false
      };
      Students.Add(student);
      student = new Student
      {
          Id = 234,
          Name = "Jane Doe",
          IsRegistered = false
      };
      Students.Add(student);
   }

   [ObservableProperty]
   ObservableCollection<Student> students = new();

   [RelayCommand]
   void UpdateRegistrations()
   {
      foreach(var item in Students)
         item.IsRegistered = true;
   }
}

Here's the XAML:


<ContentPage...>
   <Grid
      RowDefinitions="*,50">

      <CollectionView
         Grid.Row="0"
         ItemsSource="{Binding Students}">
         <CollectionView.ItemTemplate>
            <DataTemplate
               x:DataType="model:Student">
               <Grid
                  ColumnDefinitions="*,30">
                  <Label
                     Grid.Column="0"
                     Text="{Binding Name}" />
                  <Image
                     Grid.Column="1"
                     Source="checkmark.png"
                     IsVisible="{Binding IsRegistered}" />
               </Grid>
            </DataTemplate>
         </CollectionView.ItemTemplate>
      </CollectionView>

      <Button
         Grid.Row="1"
         Text="Update Data"
         Command="{Binding UpdateRegistrationsCommand}" />

   </Grid>
</ContentPage>

I don't see the change in IsRegistered reflected in the UI after executing UpdateRegistrations(). What am I missing here? I'm under the impression that CommunityToolkit.Mvvm handles INotifyPropertyChanged logic. Do I need to handle the change manually?


Solution

    1. ObservableCollection doesn't need the [ObservableProperty] attribute; it automatically invokes NotifyPropertyChanged and NotifyCollectionChanged when you call its methods like .Add(). That's kinda the whole reason we use ObservableCollection<T> instead of List<T> for MVVM.

    2. In .NET MAUI (and Xamarin), View Model properties used for bindings must be a public non-static property, so we'll promote Students from a field to a read-only property (aka add { get; } ).

    3. In the XAML, the DataTemplate bindings are using incorrect syntax: the Name binding is missing the Binding keyword, and both should be wrapped in double-quotes ("). These bugs should be invoking a compiler (syntax) error when XAMLC is enabled, so make sure you haven't accidentally disabled XAMLC.

    public partial class MyViewModel : ObservableObject
    {
       public MyViewModel()
       {
          Students.Add(new Student
          {
              Id = 123,
              Name = "John Doe",
              IsRegistered = false
          });
    
          Students.Add(new Student
          {
              Id = 234,
              Name = "Jane Doe",
              IsRegistered = false
          });
       }
    
       public ObservableCollection<Student> Students { get; } = new();
    
       [RelayCommand]
       void UpdateRegistrations()
       {
          foreach(var item in Students)
             item.IsRegistered = true;
       }
    }
    
    <ContentPage...>
       <Grid
          RowDefinitions="*,50">
    
          <CollectionView
             Grid.Row="0"
             ItemsSource="{Binding Students}">
             <CollectionView.ItemTemplate>
                <DataTemplate
                   x:DataType="model:Student">
                   <Grid
                      ColumnDefinitions="*,30">
                      <Label
                         Grid.Column="0"
                         Text="{Binding Name}" />
                      <Image
                         Grid.Column="1"
                         Source="checkmark.png"
                         IsVisible="{Binding IsRegistered}" />
                   </Grid>
                </DataTemplate>
             </CollectionView.ItemTemplate>
          </CollectionView>
    
          <Button
             Grid.Row="1"
             Text="Update Data"
             Command="{Binding UpdateRegistrationsCommand}" />
    
       </Grid>
    </ContentPage>