Sam
Sam

Reputation: 30324

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?

Upvotes: 3

Views: 3056

Answers (1)

Brandon Minnick
Brandon Minnick

Reputation: 15400

  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>

Upvotes: 4

Related Questions