Latest Stable Xamarin Forms (2.3.4.224) ListView Crashes on iOS

Hello,
I'm having trouble with the latest stable Xamarin Forms ListView. When the list view is bound to an ObservableCollection, the collection is cleared, and items are added, iOS crashes. The message seems to suggest an invalid UITableView datasource.

Attached is a sample project that demonstrates the issue. I've been trying to figure out a workaround without success... Any help or suggestions would be greatly appreciated.

John P.

Posts

  • DavidDancyDavidDancy AUMember ✭✭✭✭

    @JohnPrzyborski I made it work by making a few changes.

    There are several problems that you're running into:

    1. The ListViewPage doesn't have its BindingContext set properly.
    2. The ListView object doesn't have a bound ItemsSource property. Setting the ItemsSource the way you have done it isn't the same as creating a binding.

    The net result is that when your Source has items added to it, the screen control (which is supposed to be bound, but actually isn't in this case) can't keep up to date. This causes the UITableView that's under the hood to have a conniption (the number of elements is alleged to be different than what's actually in the control) and crash.

    If you change your ListViewPage's code-behind to the following, your example doesn't crash:

    using System.Collections.ObjectModel;
    using Xamarin.Forms;
    
    namespace ListView
    {
        public partial class ListViewPage : ContentPage
        {
            public ListViewPage ()
            {
                InitializeComponent ();
                BindingContext = this;
                Source = new ObservableCollection<MyModel> ();
            }
    
            private ObservableCollection<MyModel> _source;
            public ObservableCollection<MyModel> Source {
                get {
                    return _source;
                }
                set {
                    _source = value;
                    OnPropertyChanged (nameof (Source));
                }
            }
    
            protected override void OnAppearing ()
            {
                base.OnAppearing ();
    
                Source.Clear ();
    
                for (int i = 0; i < 20; i++) {
                    Source.Add (new MyModel ());
                }
            }
        }
    }
    
    

    I also changed your ListViewPage's 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:ListView" 
        x:Class="ListView.ListViewPage">
    
        <ContentPage.Content>
            <AbsoluteLayout BackgroundColor="Silver" Padding="0,20,0,0">
                <ListView x:Name="MyListView" ItemsSource="{Binding Source}" AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1" HasUnevenRows="true">
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <local:MyViewCell />
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </AbsoluteLayout>
        </ContentPage.Content>
    </ContentPage>
    

    This works because ContentPage already implements INotifyPropertyChanged (which is what's needed to make the binding work) so I just call its OnPropertyChanged method to get the bound controls on screen up to date with reality. I had to set the Source collection after setting the BindingContext so that Xamarin Forms can hook into the INotifyCollectionChanged events that it emits.

    I only arrived at this solution after challenging myself to change as little as possible in your code. :smile:

    A more comprehensive fix is to move things around and create extra classes to implement a "proper" MVVM-type solution. This is what I did first, and I've uploaded the result so you can compare the two ways of doing things. Apologies if you already know all about MVVM etc but I hope it's useful anyway.

    My version of your example is probably a little more involved than is warranted for a simple proof-of-concept like this, but it works as a template for how to set up the structure of Pages and their corresponding ViewModels. There are some good MVVM frameworks out there that also encourage this kind of structure but do more of the plumbing automatically for you.

    In this case I moved your code that adds elements to the bound ObservableCollection out of the ListViewPage class and into a separate MyListModel class. I made that class inherit from a BaseViewModel class, which in turn inherits from a NotifyingObject class whose only job is to implement INotifyPropertyChanged and save you from typing out some boilerplate code. The INotifyPropertyChanged bit is essential for the binding to work in Xamarin Forms Page objects.

    I also added two virtual methods to the BaseViewModel class: Appearing and Disappearing. These mirror the Appearing and Disappearing callbacks in the Page object. I modified the ListViewPage to call its ViewModel's Appearing method when it gets the Appearing callback from Xamarin Forms, and now everything works.

    For a tiny example such as this, having this many classes could be regarded as overkill. However all tiny examples grow, and if they're not structured well to begin with you have to refactor them and do things properly at some point or they send you crazy.

    The advantage of doing things this way is that you have a good pattern to work with. In general, my pattern is:

    1. Back each Page with its own ViewModel
    2. Create the Page
    3. Create the backing ViewModel
    4. Either use a MVVM framework that does the plumbing for me, or manually set the ViewModel as the Pages BindingContext before the page goes on screen
    5. Set up the initial state of bound properties (especially important for things like ObservableCollections that have events of their own that Xamarin Forms takes advantage of)
    6. Display the page
  • JohnPrzyborskiJohnPrzyborski USMember ✭✭

    Hi Dave,

    Thank you for the thoughtful/thorough response. The purpose of my code was to provide a simplistic example. However, I still believe there is a bug somewhere in Xamarin.Forms.

    I've looked into this more today, and I found if I simply specify a RowHeight in the initializer of the ListViewPage...

    public ListViewPage()
    {
        InitializeComponent();
        this.MyListView.RowHeight = 20;
        this.MyListView.ItemsSource = this.Source;
    }
    

    ...the sample code works normally.

    There seems to be a strange interaction between HasUnevenRows and the RowHeight property on the ListView.

    John

  • Yes, Jon you are right. I faced same problem as yours. It has work around by setting RowHeight. It seems like xamarin forms bug. Someone should report it so they can fix this thing in next release.

Sign In or Register to comment.