0
votes

This my ViewModel:

namespace DietAndFitness.ViewModels
{
public class FoodItemsViewModel 
{
    private SQLiteAsyncConnection database;

    public static ObservableCollection<GlobalFoodItem> FoodItems { get; set; }

    public FoodItemsViewModel(SQLiteAsyncConnection _database)
    {
        database = _database;        
    }

    public async void LoadList()
    {
         FoodItems = new ObservableCollection<GlobalFoodItem>(await database.Table<GlobalFoodItem>().ToListAsync());
    }
    public void Add()
    {
        FoodItems.Add(new GlobalFoodItem("Item"));
    }
}
}

I want to bind an instance of this VM to the list in my View but I just can't get it to work properly. The only thing I've managed to get to work is this:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:local="clr-namespace:DietAndFitness.ViewModels"
         x:Class="DietAndFitness.Views.FoodDatabasePage">
<ContentPage.Content>
    <StackLayout>
        <ListView ItemsSource="{Binding FoodItems}" RowHeight="120">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <ContentView Padding="5">
                            <Frame OutlineColor="Accent" Padding="10">
                                <StackLayout>
                                    <Grid HorizontalOptions="Center">
                                        <Grid.RowDefinitions>
                                            <RowDefinition Height="Auto"/>
                                        </Grid.RowDefinitions>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="Auto"/>
                                            <ColumnDefinition Width="Auto" />
                                        </Grid.ColumnDefinitions>
                                        <Label Text="{Binding Name}" FontSize="22" VerticalOptions="CenterAndExpand" HorizontalOptions="Center" Grid.Row="0" Grid.Column="0"/>
                                        <Label Text="{Binding Calories}" FontSize="22" VerticalOptions="CenterAndExpand" HorizontalOptions="Center" Grid.Row="0" Grid.Column="1"/>
                                    </Grid>

                                    <Grid HorizontalOptions="Center">
                                        <Grid.RowDefinitions>
                                            <RowDefinition Height="Auto"/>
                                            <RowDefinition Height="Auto"/>
                                        </Grid.RowDefinitions>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="Auto"/>
                                            <ColumnDefinition Width="Auto" />
                                            <ColumnDefinition Width="Auto" />
                                        </Grid.ColumnDefinitions>

                                        <Label Text="Carbohydrates" Grid.Row="1" Grid.Column="0" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" FontSize="16"/>
                                        <Label Text="Proteins" Grid.Row="1" Grid.Column="1" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" FontSize="16"/>
                                        <Label Text="Fats" Grid.Row="1" Grid.Column="2" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" FontSize="16"/>

                                        <Label Text="{Binding Carbohydrates}" Grid.Row="2" Grid.Column="0" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" FontSize="16"/>
                                        <Label Text="{Binding Proteins}" Grid.Row="2" Grid.Column="1" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" FontSize="16"/>
                                        <Label Text="{Binding Fats}" Grid.Row="2" Grid.Column="2" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" FontSize="16"/>
                                    </Grid>
                                </StackLayout>
                            </Frame>
                        </ContentView>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <Grid HorizontalOptions="Center">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <Button x:Name="AddFoodItemButton" BackgroundColor="LightGray" HorizontalOptions="CenterAndExpand" Text="Add" Grid.Row="0" Grid.Column="0" Clicked="AddFoodItemButton_Clicked" />
            <Button x:Name="EditFoodItemButton" BackgroundColor="LightGray" HorizontalOptions="CenterAndExpand" Text="Edit" Grid.Row="0" Grid.Column="1" />
            <Button x:Name="DeleteFoodItemButton" BackgroundColor="LightGray" HorizontalOptions="CenterAndExpand" Text="Delete" Grid.Row="0" Grid.Column="2"  />
        </Grid>
    </StackLayout>
</ContentPage.Content>

But this has problems too:

  1. If I create the VM in the page constructor the list won't load unless I refresh the page
  2. If I create the VM inside App.cs the list will load on the first try but then I can't seem to access the instance of the VM

How do I bind my ListView to this VM? And is it possible to do so without making my ObservableCollection static? I'm using a Master-Detail page in my app if it makes any difference (this is one of the detail pages).

Edit: Here is what I did in the constructor. SQLiteConnection.Database is just a static class that contains the connection to the database.

    public FoodDatabasePage ()
    {
        FoodDatabase = new FoodItemsViewModel(SQLiteConnection.Database);
        FoodDatabase.LoadList();
        InitializeComponent ();
    }
1
Please show the C# constructor for DietAndFitness.Views.FoodDatabasePage - DavidS
@DavidS I added the constructor. - FullStackOverflowDev

1 Answers

0
votes

There's a few things that you have there.

When binding a ViewModel usually it's against the whole Page making the ViewModel its BindingContext specially in cases like yours when you have methods that want to access later.

So to do this you will need a few changes.

Your Page XAML:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:local="clr-namespace:DietAndFitness.ViewModels"
        x:Class="DietAndFitness.Views.FoodDatabasePage">
<ContentPage.Content>
    <StackLayout>
        <ListView ItemsSource="{Binding FoodItems}" 
                RowHeight="120">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Frame OutlineColor="Accent" Padding="15">
                            <StackLayout>
                                <Grid HorizontalOptions="Center">
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="Auto"/>
                                    </Grid.RowDefinitions>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="Auto"/>
                                        <ColumnDefinition Width="Auto" />
                                    </Grid.ColumnDefinitions>
                                    <Label Text="{Binding Name}" 
                                        FontSize="22" 
                                        VerticalOptions="CenterAndExpand" 
                                        HorizontalOptions="Center" 
                                        Grid.Row="0" 
                                        Grid.Column="0"/>
                                </Grid>
                            </StackLayout>
                        </Frame>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

Check that I changed the ListView ItemSource for the name of the collection in your FoodItemsViewModel.

Also, part of your XAML disappeared so I could not complete it.

Your Code Behind class: (FoodDatabasePage.xaml.cs)

public FoodItemsViewModel FoodDatabase {get; set;}

public FoodDatabasePage ()
{
    InitializeComponent ();
    FoodDatabase = new FoodItemsViewModel(SQLiteConnection.Database);
    BindingContext = FoodDatabase;
}

public override void OnAppearing()
{
    FoodDatabase.LoadList();    
}

Here is where the "magic" happens. We are creating and storing the object of our FoodItemsViewModel so you can access it later as you need and then we are setting this object recently created as the BindingContext of the page (as explained above). Setting the ViewModel as the BindingContext of Page the latter will be able to access any public property of the FoodItemsViewModel.

Also, you will notice the LoadList() method was removed from the constructor, this is because there shouldn't be any heavy transaction (actually we should avoid any transactions) in the Page constructor. We moved it instead to the OnAppearing callback method that is executed by the Page when this is about to be displayed.

Note: This method is called every time the Page is presented on the screen so if you navigate away and come back it will be called again.

Your ViewModel:

namespace DietAndFitness.ViewModels
{
    public class FoodItemsViewModel 
    {
        private SQLiteAsyncConnection database;

        //You don't need it static
        public ObservableCollection<GlobalFoodItem> FoodItems { get; set; }

        public FoodItemsViewModel(SQLiteAsyncConnection _database)
        {
            database = _database;        
        }

        public async void LoadList()
        {
            // separated it only for readability.
            var items = await database.Table<GlobalFoodItem>().ToListAsync();
            FoodItems = new ObservableCollection<GlobalFoodItem>(items);
        }

        public void Add()
        {
            FoodItems.Add(new GlobalFoodItem("Item"));
        }
    }
}

This remains almost the same. The only change was removing the static qualifier to the FoodItems property since it does not need it anymore.

The above is just the basic of Mvvm and DataBinding. There're a few other things that you will need to take into consideration like INotifiedPropertyChanged, Commands and other important things.

You can find more information regarding this here

Hope this helps.-

PS: I couldn't check all of your code for any error.