julian zapata
julian zapata

Reputation: 89

Listview doesn't refresh the item view correctly

I'm developing an app with xamarin forms and the MVVM pattern. I have a page with a listview that has three buttons but all the time with only 2 visibles and change the visibility of two of them when I press a button. The problem is that for the first ten items it works like supposed to be, press the button and dissapear and appear the other, but after the 10th item when I press the button it dissapear but the other doesn't appear until I scrool the list view to a position where the item is out of the screen. When the item is out of the screen and come back to be on the screen, the button appear. The visibility of the buttons is controlled changing a boolean property that is binded to the IsVisible property of the button and one of them with a converter to negate the value of the property. This is a repository that you can clone and see the code and test, maybe is something with my Visual Studio.

Initially, I thought it could be for a race condition and made the method that change the variable synchronous but it doesn't work.

This is my list view

   <ListView ItemsSource="{Binding Items}"
                          HasUnevenRows="True"
                          SeparatorVisibility="None"
                          IsRefreshing="False">
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <ViewCell>
                                <StackLayout>
                                    <Label Text="{Binding Name}"/>
                                    <StackLayout Orientation="Horizontal">
                                        <Button Text="One" 
                                                HorizontalOptions="CenterAndExpand"
                                                TextColor="Green"                                                     
                                                BackgroundColor="White"
                                                BorderColor="Green"
                                                BorderWidth="1"
                                                WidthRequest="150" />
                                        <Button Text="Two"
                                                HorizontalOptions="CenterAndExpand"
                                                BackgroundColor="Green"
                                                TextColor="White"
                                                Command="{Binding TestCommand}"
                                                WidthRequest="150"
                                                IsVisible="{Binding TestVariable, Converter={StaticResource negate}}" />
                                        <Button Text="Three"
                                                HorizontalOptions="CenterAndExpand"
                                                BackgroundColor="Red"
                                                Command="{Binding TestCommand}"
                                                TextColor="White"
                                                WidthRequest="150"
                                                IsVisible="{Binding TestVariable}" />
                                    </StackLayout>
                                </StackLayout>
                            </ViewCell>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>

The viewmodel

 public class ListViewTestModel : BaseViewModel
    {
        private List<ListItemTestModel> items;
        public List<ListItemTestModel> Items
        {
            get => items;
            set
            {
                SetValue(ref items, value);
            }
        }


        public ListViewTestModel()
        {
            List<ListItemTestModel> itemList = new List<ListItemTestModel>();

            for (int i = 0; i < 40; i++)
            {
                itemList.Add(new ListItemTestModel { Name = "Test" });
            }

            Items = itemList;
        }
    }

And another view model that is binded to each item in the listView


public class ListItemTestModel : BaseViewModel
    {
        private bool testVariable;
        public string Name { get; set; }

        public bool TestVariable
        {
            get
            {
                return testVariable;
            }
            set
            {
                SetValue(ref testVariable, value);
            }
        }

        public Command TestCommand { get; set; }

        public ListItemTestModel()
        {
            TestCommand = new Command(() =>
            {
                TestMethod();
            });
        }

        public void TestMethod()
        {
            TestVariable = !TestVariable;
        }
    }

the BaseViewModel

public class BaseViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        protected void SetValue<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(backingField, value))
            {
                return;
            }

            backingField = value;
            OnPropertyChanged(propertyName);
        }
    }

And the codebehind of the page

  public partial class MainPage : ContentPage
    {
        public ListViewTestModel ViewModel { get; }
        public MainPage()
        {
            ViewModel = new ListViewTestModel();
            BindingContext = ViewModel;
             InitializeComponent();
        }
    }

Upvotes: 0

Views: 2209

Answers (2)

Umar3x
Umar3x

Reputation: 1084

You should definitely go to ObservableCollection type for your items thus you'll be able to observe and display any changes

   private ObservableCollection<ListItemTestModel> items;
        public ObservableCollection<ListItemTestModel> Items
        {
            get => items;
            set => SetValue(ref items, value);

        } 

And you should set your BindingContext AFTER the InitializeComponent() method or property changed will be propagate before your view is initialized.

public MainPage()
    {      
        InitializeComponent();
        BindingContext = new ListViewTestModel();;
    }



public ListViewTestModel()
        {
            List<ListItemTestModel> itemList = new List<ListItemTestModel>();

            for (int i = 0; i < 40; i++)
            {
                itemList.Add(new ListItemTestModel { Name = "Test" });
            }

            Items = new ObservableCollection<ListItemTestModel>(itemList);
        }

Upvotes: 1

Cherry Bu - MSFT
Cherry Bu - MSFT

Reputation: 10346

I suggest listview Caching Strategy may case this issue, the default value is RetainElement for ListView, so using CachingStrategy="RecycleElement" in ListView.

About listview Caching Strategy, you can take a look:

https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/listview/performance#caching-strategy

Upvotes: 2

Related Questions