Sandi Horvat
Sandi Horvat

Reputation: 465

Bind items to a ListView

I am trying to create an infinite scroll in xamarin.forms. I think that the problem is, that my items are not binded to my ListView. Do I have to bind my datatemplate to listview. My datatemplate contains imagecell with text,detail and ImageSource.

While I was debugging, my listview.ItemAppearing += (sender, e) => was never called. So I am assuming that here is my problem.

I am using Http client with json response. Bellow is my code:

public partial class MainPage : ContentPage
    {
        readonly IList<Article> books = new ObservableCollection<Article>();
        readonly BookManager manager = new BookManager();           
        bool isLoading;

        public MainPage()
        {
            books = new ObservableCollection<Article>();
            var listview = new ListView();
            listview.ItemsSource = books;

            listview.ItemAppearing += (sender, e) =>
            {
                if (isLoading || books.Count == 0)
                    return;

                //hit bottom!
                if (e.Item == books[books.Count - 1])
                {
                    LoadItems();
                }
            };

            LoadItems();
            BindingContext = books;
            InitializeComponent();          
        }

my LoadItems method:

public async void LoadItems()
        {       
            //simulator delayed load       
                var bookCollection = await manager.GetAll();

                foreach (Article book in bookCollection.articles)
                {                 
                        books.Add(book);
                }

        }

and my xamlpage

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="BookClient.MainPage"
             Title="Library Books">
  <ContentPage.ToolbarItems>
    <ToolbarItem Text="Add New" Icon="ic_action_new.png" Clicked="OnAddNewBook" />
    <ToolbarItem Text="Refresh" Icon="ic_autorenew.png" Clicked="OnRefresh" />
  </ContentPage.ToolbarItems>

  <ListView
    ItemsSource="{Binding}"
          ItemTapped="OnEditBook">
    <ListView.ItemTemplate>
      <DataTemplate>
        <ImageCell 
          Text="{Binding id, StringFormat='ID= {0}'}" Detail="{Binding content}" ImageSource="{Binding images.image_intro}">
          <ImageCell.ContextActions>          
            <MenuItem Clicked="OnDeleteBook"
                CommandParameter="{Binding}"
                Text="Delete" IsDestructive="True" />
          </ImageCell.ContextActions>
        </ImageCell>       
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>
</ContentPage>

Upvotes: 1

Views: 3849

Answers (1)

hvaughan3
hvaughan3

Reputation: 11105

Looks like you have a couple problems, though I have not messed with infinite scrolling before. To answer your question, you do not need to bind your DataTemplate, it looks good exactly how you have it.

  1. In your XAML you specify ItemsSource="{Binding}" but then in your code-behind you set listview.ItemsSource = books; which cancels out your original binding. I would suggest commenting out that line in your code-behind and leave in the XAML line.

  2. You are not awaiting LoadItems();. You should move LoadItems(); into OnAppearing() so that it can be awaited.

  3. Move the books property into your ViewModel and then have you ViewModel inherit from INotifyPropertyChanged and set your ViewModel as the BindingContext. This would then make your ListView.ItemSource change to ItemsSource="{Binding Books}". So your ViewModel will become

    public class BooksViewModel : INotifyPropertyChanged {
    
    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
    
    private ObservableCollection<Article> _books;
    public  ObservableCollection<Article> Books {
        get { return _books ?? (_books = new ObservableCollection<Article>()); }
        set {
            if(_books != value) {
                _books = value;
                OnPropertyChanged();
            }
        }
    }
    
    public async void LoadItems()
    {       
        //simulator delayed load       
            var bookCollection = await manager.GetAll();
    
            foreach (Article book in bookCollection.articles)
            {                 
                    books.Add(book);
            }
    
    }
    
    protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) {
    
        System.ComponentModel.PropertyChangedEventHandler handler = PropertyChanged;
    
        if(handler != null) { handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); }
    }
    }
    

Doing the above allows Books to notify the UI that it has changed so the UI will update. You also should be specifying Books as ObservableCollection<> instead of IList<>

  1. A suggestion, I would attach a method to listview.ItemAppearing in ContentPage.OnAppearing() and then remove the event handler in ContentPage.OnDisappearing(). This will prevent memory leaks and should be done for any event handling that it makes sense to do it to. This will require you to put your ItemAppearing lambda code into its own method

Let me know if it is still not working for you after this.

Edit: Forgot about the ViewModel's PropertyChanged event. See ViewModel code again, at the top.

Upvotes: 2

Related Questions