Search code examples
mvvmdata-bindingmauiobservablecollection

ListView Not Displaying ObservableProperty Binding from ViewModel


I am new to .NET MAUI/MVVM/SQLite, and C# for that matter. I am trying to utilize my ViewModel to bind data from my model class. I can use the model namespace as my binding property just fine, and it shows the data in my listview. As soon as I try to use my ViewModel, however, it is no longer displaying.

CRUDView XAML Working

        <Frame Grid.Row="6" Grid.Column="1" Grid.ColumnSpan="3" BackgroundColor="#B1C0C9" BorderColor="#7989A3">
            <ScrollView Orientation="Vertical">
                <ListView x:Name="listView" 
                          BackgroundColor="Gray" 
                          HasUnevenRows="True"
                          SeparatorVisibility="Default"
                          ItemTapped="listView_ItemTapped"
                          FlexLayout.Grow="1"
                          >
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <ViewCell>
                                <VerticalStackLayout Padding="5">
                                    <Label Text="{Binding Question}"
                                           FontSize="17" 
                                           FontFamily="OCRA" 
                                           TextColor="Black"/>
                                    <Grid>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="50"/>
                                            <ColumnDefinition Width="*"/>
                                            <ColumnDefinition Width="*"/>
                                            <ColumnDefinition Width="Auto"/>
                                        </Grid.ColumnDefinitions>
                                        <Label Text="{Binding Id}"
                                               FontSize="10" 
                                               FontFamily="OCRA" 
                                               TextColor="Black"
                                               Grid.Column="0"
                                               />
                                        <Label Text="{Binding Answer}"
                                               FontSize="15" 
                                               FontFamily="OCRA" 
                                               TextColor="White"
                                               Grid.Column="1"
                                               Padding="10"/>
                                        <Label Text="{Binding Difficulty}"
                                               FontSize="15" 
                                               FontFamily="OCRA" 
                                               TextColor="Black"
                                               Grid.Column="3"
                                               Padding="10"/>
                                    </Grid>
                                    
                                </VerticalStackLayout>
                            </ViewCell>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </ScrollView>

        </Frame>

Showing data in ListView

When I revert back to using the ViewModel for data binding:

CRUDView XAML

<Frame Grid.Row="6" Grid.Column="1" Grid.ColumnSpan="3" BackgroundColor="#B1C0C9" BorderColor="#7989A3">
    <ScrollView Orientation="Vertical">
        <ListView x:Name="listView" 
                  BackgroundColor="Gray" 
                  HasUnevenRows="True"
                  SeparatorVisibility="Default"
                  ItemTapped="listView_ItemTapped"
                  FlexLayout.Grow="1"
                  ItemsSource="{Binding Flashcards}">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="vm:CRUDViewModel">
                    <ViewCell>
                        <VerticalStackLayout Padding="5">
                            <Label Text="{Binding Flashcard.Question}"
                                   FontSize="17" 
                                   FontFamily="OCRA" 
                                   TextColor="Black"/>
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="50"/>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <Label Text="{Binding Flashcard.Id}"
                                       FontSize="10" 
                                       FontFamily="OCRA" 
                                       TextColor="Black"
                                       Grid.Column="0"
                                       />
                                <Label Text="{Binding Flashcard.Answer}"
                                       FontSize="15" 
                                       FontFamily="OCRA" 
                                       TextColor="White"
                                       Grid.Column="1"
                                       Padding="10"/>
                                <Label Text="{Binding Flashcard.Difficulty}"
                                       FontSize="15" 
                                       FontFamily="OCRA" 
                                       TextColor="Black"
                                       Grid.Column="3"
                                       Padding="10"/>
                            </Grid>
                            
                        </VerticalStackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </ScrollView>

</Frame>

ViewModel

namespace cadflash.ViewModels
{
    public partial class CRUDViewModel : ObservableObject
    {
        [ObservableProperty]
        public ObservableCollection<Flashcard> flashcards = new();

        [ObservableProperty]
        public Flashcard flashcard = new();
        
        public CRUDViewModel()
        {
            Flashcard = new Flashcard();
            Flashcards = [];
        }

MauiProgram

MauiProgram

    public static class MauiProgram
    {
        public static MauiApp CreateMauiApp()
        {
            var builder = MauiApp.CreateBuilder();
            builder
                .UseMauiApp<App>()
                .UseMauiCommunityToolkit()
                .ConfigureFonts(fonts =>
                {
                    fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                    fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
                    fonts.AddFont("OCRAEXT.ttf", "OCRA");
                });

            builder.Services.AddSingleton<LocalDbService>();
            builder.Services.AddTransient<HomeView>();
            builder.Services.AddTransient<DifficultyView>();
            builder.Services.AddTransient<CRUDView>();
            builder.Services.AddTransient<CRUDViewModel>();
            builder.Services.AddTransient<DifficultyViewModel>();
            builder.Services.AddTransient<HomeViewModel>();


#if DEBUG
            builder.Logging.AddDebug();
#endif

            return builder.Build();
        }
    }
}

CRUDView.cs

CRUDView.xaml.cs

public partial class CRUDView : ContentPage
{
    CRUDViewModel viewModel;
    private readonly LocalDbService localDbService; // This is the service that is used to interact with the local database
    private int _editFlashcardId; // This is the ID of the flashcard that is being edited
    public CRUDView(LocalDbService localDbService)
    {
        InitializeComponent();
        BindingContext = new CRUDViewModel();
        this.localDbService = localDbService;
        Task.Run(async () => await localDbService.GetFlashcardsAsync());
    }

    protected override async void OnAppearing()
    {
        base.OnAppearing();
        listView.ItemsSource = await localDbService.GetFlashcardsAsync();
    }

No data in ListView

It has been a week of agonizing pain trying to get the data showing in listview. Today I figured out how to just bind the data directly from the model class. Ideally, I would like to use the ViewModel binding...


Solution

  • There are some problems with the usages in your code.

    First, since you tried to use MVVM, you need to add the relative code about localDbService to your MVVM model. For example, you can add both the localDbService and loading data method to MVVM model.

        //private readonly LocalDbService localDbService; // This is the service that is used to interact with the local database
    
        public CRUDViewModel()
        {
            //initialize localDbService and load data
            //this.localDbService = localDbService;
        }
    

    Second,since you set ItemsSource="{Binding Flashcards}" for your ListView, the data type of the DataTemplate of ListView should be Flashcard not <DataTemplate x:DataType="vm:CRUDViewModel">.

    Third, please remove the prefix Flashcard while binding(e.g. <Label Text="{Binding Flashcard.Id}").

    I have achieved this function with some fake data.

    You can refer to the following code:

    CRUDViewModel.cs

    public partial class CRUDViewModel:ObservableObject
    {
        [ObservableProperty]
        public ObservableCollection<Flashcard> flashcards = new();
    
        [ObservableProperty]
        public Flashcard flashcard = new();
    
    
        //private readonly LocalDbService localDbService; // This is the service that is used to interact with the local database
    
    
        public CRUDViewModel()
        {
            Flashcard = new Flashcard();
            Flashcards = [];
    
            //initialize localDbService and load data
            //this.localDbService = localDbService;
           
            GetData();
        }
    
        public async void GetData() {
    
            //List<Flashcard>  datas = await localDbService.GetFlashcardsAsync());
    
            // add the data to Flashcards
            //foreach (Flashcard flashcard in datas) {
            //    Flashcards.Add(flashcard);
            //}
    
            //add some fake data to variable Flashcards
            Flashcards.Add(new Flashcard { Id = 01, Question = "1+ 1 = ", Answer = "2", Difficulty = "easy" });
            Flashcards.Add(new Flashcard { Id = 02, Question = "1+ 3 = ", Answer = "4", Difficulty = "easy" });
            Flashcards.Add(new Flashcard { Id = 03, Question = "123+163 = ", Answer = "286", Difficulty = "difficult" });
    
        }
    }
    

    MainPage.xaml

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 
                 xmlns:models="clr-namespace:MauiMvvmListApp0407.Models"
                 x:Class="MauiMvvmListApp0407.MainPage">
    
        <Frame Grid.Row="6"  Grid.Column="1" Grid.ColumnSpan="3" BackgroundColor="#B1C0C9" BorderColor="#7989A3">
                <ListView x:Name="listView"
                      BackgroundColor="Gray"
                      HasUnevenRows="True"
                      SeparatorVisibility="Default"
                      ItemsSource="{Binding Flashcards}">
                    <ListView.ItemTemplate>
                    <DataTemplate x:DataType="models:Flashcard">
                            <ViewCell>
                                <VerticalStackLayout Padding="5">
                                    <Label Text="{Binding Question}"
                                       FontSize="17"
                                       FontFamily="OCRA"
                                       TextColor="Black"/>
                                    <Grid>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="50"/>
                                            <ColumnDefinition Width="*"/>
                                            <ColumnDefinition Width="*"/>
                                            <ColumnDefinition Width="Auto"/>
                                        </Grid.ColumnDefinitions>
                                        <Label Text="{Binding Id}"
                                           FontSize="10"
                                           FontFamily="OCRA"
                                           TextColor="Black"
                                           Grid.Column="0"
                                           />
                                        <Label Text="{Binding Answer}"
                                           FontSize="15"
                                           FontFamily="OCRA"
                                           TextColor="White"
                                           Grid.Column="1"
                                           Padding="10"/>
                                        <Label Text="{Binding Difficulty}"
                                           FontSize="15"
                                           FontFamily="OCRA"
                                           TextColor="Black"
                                           Grid.Column="3"
                                           Padding="10"/>
                                    </Grid>
    
                                </VerticalStackLayout>
                            </ViewCell>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
        </Frame>
    </ContentPage>
    

    MainPage.xaml.cs

    public partial class MainPage : ContentPage
    {
        CRUDViewModel viewModel;
        public MainPage()
        {
            InitializeComponent();
    
            viewModel = new CRUDViewModel();
            this.BindingContext = viewModel;
        }
    }