0
votes

I am trying to implement a MVVM ContentPage with a ListView that needs to bind to a populated generic list of XML model objects in a ViewModel but the binding fails. The code that is shown calls an API that does return a valid list of XML data. The same code works fine when the binding is done directly in the code behind of the XAML Xamarin contentpage by setting the ItemSource in the codebehind. As said, the problem only happens when trying to pass the ListView through the ViewModel assigned to the contentpage. I have stepped through the code in the ViewModel and the ListView is populated successfully but the binding just doesn't work. I have other controls that are on the page that the model binding does work for but the only control that doesn't work is the ListView. The code is shown below:

ViewModel:

using RestDemo.Model;
using RestDemo.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using System.ComponentModel;
using System.Windows.Input;
using System.Collections.ObjectModel;

namespace RestDemo.ViewModel
{
    
    public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public ViewModel ()
        {
           GetRequest();
        }

        List<XmlPizzaDetails> _objPizzaList;
        string _selectedDescription = "Descriptions: ";
        bool _progress;
        string _cusButtonText = "Hello";

        public bool Progress
        {
            get { return _progress; }
            set { _progress = value; }
        }

        public string CusButtonText
        {
            get { return _cusButtonText; }
            set { _cusButtonText = value; }
        }

        public string SelectedDescription
        {
            get { return _selectedDescription; }
            set { _selectedDescription = value; }
        }

        public List<XmlPizzaDetails> ObjPizzaList
        {
            get { return _objPizzaList; }

            set
            {
                if (_objPizzaList != value)
                {
                    _objPizzaList = value;
                    OnPropertyChanged("ObjPizzaList");
                }
            }
        }

        public string Description
        {
            get { return _selectedDescription; }
            set { _selectedDescription = value; }
        }

        public ICommand SelectedCommand => new Command(() =>
        {
            CusButtonText = "Goodby";
        });

       event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
        {
            add
            {
            }

            remove
            {
            }
        } 

        public async void GetRequest()
        {
            if (NetworkCheck.IsInternet())
            {

                Uri geturi = new Uri("http://api.androidhive.info/pizza/?format=xml"); //replace your                       xml url
                HttpClient client = new HttpClient();
                HttpResponseMessage responseGet = await client.GetAsync(geturi);
                string response = await responseGet.Content.ReadAsStringAsync();

                //Xml Parsing
                ObjPizzaList = new List<XmlPizzaDetails>();
                XDocument doc = XDocument.Parse(response);
                foreach (var item in doc.Descendants("item"))
                {
                    XmlPizzaDetails ObjPizzaItem = new XmlPizzaDetails();
                    ObjPizzaItem.ID = item.Element("id").Value.ToString();
                    ObjPizzaItem.Name = item.Element("name").Value.ToString();
                    ObjPizzaItem.Cost = item.Element("cost").Value.ToString();
                    ObjPizzaItem.Description = item.Element("description").Value.ToString();
                    ObjPizzaList.Add(ObjPizzaItem);
                }
                Progress = false;
            }
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this,
                    new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

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:xlocal="clr-namespace:RestDemo.ViewModel"
             xmlns:local="clr-namespace:RestDemo"
             xmlns:Views="clr-namespace:RestDemo.Views"
             x:Class="RestDemo.XmlParsingPageBehavior">
    <ContentPage.BindingContext>
        <xlocal:ViewModel />
    </ContentPage.BindingContext>
    <Grid>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Views:CustomButton Grid.Row="0" Grid.Column="0" Text="HOME"  />
                <Views:CustomButton Grid.Row="0" Grid.Column="1" Text="Administrative Maintence"  />
                <Views:CustomButton Grid.Row="0" Grid.Column="2" Text="User Maintence"  />
                <Views:CustomButton Grid.Row="0" Grid.Column="3" Text="About" />
            </Grid>
            <Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>
                <Views:CustomButton Grid.Row="0" Grid.Column="0" Text="{Binding CusButtonText,                             Mode=TwoWay}">
                    <Views:CustomButton.Behaviors>
                        <local:ItemSelectedToCommandBehavior />
                    </Views:CustomButton.Behaviors>
                </Views:CustomButton>
            </Grid>
            <Frame Margin="5, 5, 5, 5" Grid.Row="2" Grid.Column="1" BackgroundColor = "Cyan">
                <ListView x:Name="PizzaListView" ItemsSource="{Binding ObjPizzaList}"  Margin="5, 0, 5,                     0" Grid.Row="2" Grid.Column="1" HorizontalOptions="FillAndExpand"                                     HasUnevenRows="True">
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <ViewCell>
                                <Grid HorizontalOptions="FillAndExpand" Margin="0,0,0,0" Padding="20">
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="Auto"/>
                                        <RowDefinition Height="Auto"/>
                                        <RowDefinition Height="Auto"/>
                                        <RowDefinition Height="Auto"/>
                                    </Grid.RowDefinitions>
                                    <Label Text="{Binding Name}" HorizontalOptions="StartAndExpand"                                             Grid.Row="0" TextColor="Blue"  FontAttributes="Bold"/>
                                    <Label Text="{Binding Cost}" HorizontalOptions="StartAndExpand"                                             Grid.Row="1" TextColor="Orange"  FontAttributes="Bold"/>
                                    <Label Text="{Binding Description}"                                                                         HorizontalOptions="StartAndExpand" Grid.Row="2"                                                       TextColor="Gray"  FontAttributes="Bold"/>
                                    <BoxView HeightRequest="2" Margin="0,10,10,0"                                                               BackgroundColor="Gray" Grid.Row="3" HorizontalOptions="Fill" />
                                </Grid>
                            </ViewCell>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </Frame>
        </Grid>
        <ActivityIndicator x:Name="ProgressLoader" IsVisible="{Binding Progress}" IsRunning="True"/>
    </Grid>
</ContentPage>
1
Have you tried changing the List to an ObservableCollection? This is how data is sent to ListViews following MVVM pattern. ā€“ SergioAMG
Iā€˜m trying reproduce your issue with your code, but it miss XmlPizzaDetails class, could you share it? ā€“ Nico Zhu - MSFT

1 Answers

1
votes

I have it working. Yes ObservableCollections are the way to go but generic Lists are fine as well. The problem is that when the Model is bound the WebService call has not completed so when the List property is bound it is still null. Even when updated at this point the ObservableCollection won't work because it has not been seeded. The solution is to seed the ObservableCollection, or List, on the Page's OnAppearing event and bind the ViewModel as the BindingContext in this event. My solution is below:

protected override async void OnAppearing()
{
    var vm = new ViewModel.ViewModel();
    if (vm == null)
        return;

    HttpClient client = new HttpClient();
    HttpResponseMessage responseGet = await client.GetAsync(vm.Geturi);
    string response = await responseGet.Content.ReadAsStringAsync();

    //Xml Parsing
    var _objPizzaList = new ObservableCollection<XmlPizzaDetails>();
    XDocument doc = XDocument.Parse(response);

    vm.GetRequest(doc);

    this.BindingContext = vm;
}