xamarin / Xamarin.Forms

Xamarin.Forms is no longer supported. Migrate your apps to .NET MAUI.
https://aka.ms/xamarin-upgrade
Other
5.64k stars 1.88k forks source link

VisualStudio2022 ListView RetainElement infinitely creates cells #15712

Open vsfeedback opened 1 year ago

vsfeedback commented 1 year ago

This issue has been moved from a ticket on Developer Community.


[severity:Other] [regression] [worked-in:VS2019] I have this tiny demo that appears to show that ListView continually creates ViewCells, and binds them to one view model instance, until it runs out of memory. It does NOT fail with VisualStudio2019.

NOTE: In the example, I display "serial" numbers that are incremented by the OnBindingContextChanged() override, which is discouraged. This has nothing to do with the problem. If you remove all of the serial number stuff, and put a breakpoint in OnBindingContextChanged(), it will break there as many times as you're willing to press continue. I added the serial numbers just to see what was going on.

Here is the code:

MainPage.xaml:


        <label>
        <label>

MainPain.xaml.cs:

public partial class MainPage : ContentPage
{
    MainVM _vm = null;
    public MainPage()
    {
        InitializeComponent();
        _vm = new MainVM();
        BindingContext = _vm;
        DemoList.ItemTemplate = new DataTemplate(typeof(myCell));
    }
}

MainVM.cs:

using System.Collections.ObjectModel;

namespace LVD
{
    class MainVM
    {
        public string MainNum { get; set; }
        private static int _mainserno = 0;
        public ObservableCollection Itms { get; set; }

        public MainVM()
        {
            MainNum = "Parent serial#: " + _mainserno.ToString();
            _mainserno++;
            if (Itms == null)
                Itms = new ObservableCollection();            
            else
                Itms.Clear();

            CellVM item = new CellVM();
            Itms.Add(item);
            // If you add a second cell, both sets of numbers count up
            //CellVM item2 = new CellVM();
            //Itms.Add(item2);
        }
    }
}

MyCell.cs:

using Xamarin.Forms;

namespace LVD
{
    class myCell : ViewCell
    {
        static int _localserialno = 0;
        Label VMnumLbl, CellNumLbl, PLbl;

        public static readonly BindableProperty ItemProperty =
            BindableProperty.Create("CellItem", typeof(CellVM), typeof(myCell), null);

        public CellVM CellItem
        {
            get { return (CellVM)GetValue(ItemProperty); }
            set { SetValue(ItemProperty, value); }
        }

        public myCell()
        {
            VMnumLbl = new Label();
            VMnumLbl.SetBinding(Label.TextProperty, "VMSerNum", BindingMode.Default, null, "VM Serial #: {0}");

            CellNumLbl = new Label();
            CellNumLbl.SetBinding(Label.TextProperty, "CellSerNum", BindingMode.Default, null, "Cell Serial #: {0}");

            // Optional -->
            PLbl = new Label();
            PLbl.SetBinding(Label.TextProperty, "PingCount", BindingMode.Default, null, "Pings: {0}");
            // <--- (and remove the Children.Add below)
            var stackit = new StackLayout();
            stackit.Children.Add(VMnumLbl);
            stackit.Children.Add(CellNumLbl);
            stackit.Children.Add(PLbl);
            View = stackit;
        }

        protected override void OnBindingContextChanged()
        {
            base.OnBindingContextChanged();

            if (BindingContext == null)
                CellItem = null;
            else if (BindingContext != CellItem)
            {
                CellItem = (CellVM)BindingContext;
                CellItem.CellSerNum = _localserialno++;
                // Optional: 
                CellItem.Ping();
            }
        }
    }
}

CellVM.cs:

class CellVM : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    private static int serialno = 0;
    private int _vmserialno;
    public int VMSerNum { get { return _vmserialno; } }
    public int CellSerNum { get; set; }
    public int PingCount { get; set; }  // Optional
    public CellVM()
    {
        _vmserialno = serialno++;
        PingCount = 0;
    }

    // Optional:
    public void Ping()
    {
        PingCount++;
        OnPropertyChanged(string.Empty);
    }
}

When I run this code, the display looks like this:

This is a listview with one item.
Parent serial #: 0
VM Serial #: 0
Cell Serial #: ++
Pings: ++

The bottom two numbers continually increase. That means the listview recreates the CellView, and binds them all to the same viewmodel. If I put two items in the list, both cells have running Cell Serial #'s and Pings.

Again, the numbers and their display have NOTHING to do with the problem. I added them so I could see what was going on. If you remove all of the numbers, the code will still eventually crash. I have verified that this exact same code does not exhibit this behavior in VS2019. It creates a few extra objects, but not infinitely.

The impact of this issue is that my app will run quite a bit more slowly than it should AND, some customers will probably have memory issues, as their lists are much longer than the ones I use to develop and test with. [Application performance should be one of the options for the impact question...]


Original Comments

Feedback Bot on 3/6/2023, 07:00 PM:

(private comment, text removed)


Original Solutions

(no solutions)