Search code examples
c#xamlxamarinxamarin.formsobservablecollection

CollectionView items does not update while setting x:DataType to ViewModel in Xamarin Forms


I have a CollectionView that has its ItemsSource bound to an ObservableCollection and it works fine. But when I add x:DataType="viewmodels:MyPageViewModel" in the XAML, it does not show any items, even those items that are initialized in the ViewModel's constructor are not shown in the CollectionView. It also does not show updated items.

Here's my XAML Code (MyPage.xaml):

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...
             xmlns:viewmodels="clr-namespace:MyProject.ViewModels"
             x:DataType="viewmodels:MyPageViewModel"
             x:Class="MyProject.Views.MyPage">

    <ContentPage.Content>
        <StackLayout>
            <CollectionView ItemsSource="{Binding Strings}">
                <CollectionView.ItemTemplate>
                    <DataTemplate>
                        <StackLayout>
                            <Label Text="{Binding .}" />
                        </StackLayout>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>

            <Button Text="Add More" Command="{Binding AddMoreItemsCommand}" />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

Here I have specified Binding in the C# Code Behind (MyPage.xaml.cs):

BindingContext = new ViewModels.MyPageViewModel();

Here's my ViewModel (MyPageViewModel.cs):

public class MyPageViewModel : INotifyPropertyChanged
{
    public ObservableCollection<string> Strings { get { return strings; } set { strings = value; OnPropertyChanged(nameof(Strings)); } }
    ObservableCollection<string> strings;

    public MyPageViewModel()
    {
        Strings = new ObservableCollection<string> { "1", "2", "3" };
    }

    public ICommand AddMoreItemsCommand => new Command(() =>
    {
        Strings.Add("4");
        Strings.Add("5");
        Strings.Add("6");
    });

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Note that if I remove x:DataType="viewmodels:MyPageViewModel", everything works fine. Also if I edit something in ColllectionView while the app is running, it renders the items properly as expected through XAML Hot Reload. But it doesn't fulfil the need as it does not work if I rerun the Project again.

Is there a bug in Xamarin Forms, I am running the latest Xamarin.Forms 5.0.0.2515, I have also tried it in Xamarin.Forms 5.0.0.2478? Or am I missing something? any help would be much appreciated.


Solution

  • Specify x:DataType="modelOfYourCollection" in <DataTemplate> of the CollectionView.

    If you are using any control that has a DataTemplate you must also setup the compiled binding on it. Here, we have a CollectionView that is data bound to an ObservableCollection<string>. We can bring in the System namespace just like we did for the viewmodels:MyPageViewModel because we are using collection of string type and String class resides in System namespace.

    • Firstly, add namespace in root of the page:
    xmlns:sys="clr-namespace:System;assembly=System.Runtime"
    
    • And then in your DataTemplate add:
    <DataTemplate x:DataType="sys:String">
    

    If we are using our own Custom Model, add model's namespace at the root of the page and then specify model class in DataTemplate of the CollectionView. Note that we have to use Model and not ViewModel in the DataTemplate.

    Rest of the code is correct

    Final XAML Code will look like:

    <ContentPage ...
                 xmlns:viewmodels="clr-namespace:MyProject.ViewModels"
                 xmlns:models="clr-namespace:MyProject.Models"
                 xmlns:sys="clr-namespace:System;assembly=System.Runtime"
                 x:DataType="viewmodels:MyPageViewModel"
                 x:Class="MyProject.Views.MyPage">
        ...
    
        <CollectionView ItemsSource="{Binding Strings}">
            <CollectionView.ItemTemplate>
                <DataTemplate x:DataType="sys:String">
                    <Label Text="{Binding .}" />
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    
        <!-- For collection of your custom model -->
    
        <CollectionView ItemsSource="{Binding Monkeys}" >
            <CollectionView.ItemTemplate>
                <DataTemplate x:DataType="models:MonkeyModel">
                    <Label Text="{Binding MonkeyName}" />
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </ContentPage>
    

    Why use Compiled Bindings? Compiled bindings are resolved more quickly than classic bindings. Hence, improves data binding performance in Xamarin.Forms applications.