Search code examples
.netxamllistviewmauiobservablecollection

.NET MAUI ListView - ObservableCollection - Not updating during async method


I've tried a few different ways of doing this, but haven't quite figured out the combination to get this to work as intended. This is in .NET MAUI using .NET 6. Basically, the UI is not updating / showing the ListView rows & values. I've found that if I change the .xaml during debugging (using the hot reload process), then the values of the ListView magically appear. Therefore, I am convinced that the UI is not being notified that the ObservableCollection has been changed. When the GetList button is clicked in my UI - MainPage.xaml, an async method is called to grab data from my API and fill the observable collection with the returned data from the API. Here is the necessary code below:

Update: Oddly enough adding <Label Text="{Binding MyObvList.Count}" /> into the XAML causes the UI to refresh, successfully showing the list and it's rows/variables. However, when that label is removed, it goes back to acting the same old way. So, I am further convinced it's an update issue.

Here is my XAML (MainPage.xaml):

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:mct="clr-namespace:CommunityToolkit.Maui.Views;assembly=CommunityToolkit.Maui"
             x:Class="MyApp.MainPage"
             x:Name="PageRoot"
             Title="My App">

    <VerticalStackLayout Spacing="25" Padding="30,0" VerticalOptions="Center">
            <Label Text="My App"
               SemanticProperties.HeadingLevel="Level2"
               FontSize="18"
               HorizontalOptions="Center" />
            <Button Text="Get List" Clicked="GetList"/>

            <StackLayout Orientation="Horizontal">
                <Label Text="Number" WidthRequest="100" />
                <Label Text="Name" WidthRequest="100" />
                <Label Text="Info" WidthRequest="100" />
            </StackLayout>

            <ListView ItemsSource="{Binding MyObvList}">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <StackLayout Orientation="Horizontal">
                                <Label Text="{Binding Number}" WidthRequest="100" />
                                <Label Text="{Binding Name}" WidthRequest="100" />
                                <Label Text="{Binding Info}" WidthRequest="300" />
                            </StackLayout>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
     </VerticalStackLayout>
</ContentPage>

Code Behind (MainPage.xaml.cs):

namespace MyApp;

public partial class MainPage : ContentPage, INotifyPropertyChanged 
{
    private ObservableCollection<MyData> _myObvList = new();
    public ObservableCollection<MyData> MyObvList {
        get { return _myObvList; }
        set {
            if(_myObvList != value) {
                _myObvList = value;
                OnPropertyChanged(nameof(MyObvList));
            }
        }
    }
    public MainPage()
    {
        InitializeComponent();        
        BindingContext = this;
        NavigationPage.SetHasNavigationBar(this, false);
        NavigationPage.SetHasBackButton(this, false);
    }
    async void GetList(object sender, EventArgs e)
    {
        try {
            List<MyAPIData> apiData = await MyApi.GetDataAsync();
            MainThread.BeginInvokeOnMainThread(() => {
                MyObvList.Clear();
                if(apiData != null && apiData.Count > 0) {
                   foreach(MyAPIData data in apiData) {
                      MyData myNewData = new();
                      myNewData.Name = = data.Person.FirstName + " " + (string.IsNullOrEmpty(data.Person.MiddleName) ? "" : data.Person.MiddleName + " ") + data.Person.LastName + (string.IsNullOrEmpty(data.Person.Suffix) ? "" : " " + data.Person.Suffix);
                      myNewData.Number = data.Number;
                      myNewData.Info = data.Info;
                      
                      MyObvList.Add(myNewData);
                   }
                }
            }
        } catch(Exception ex) {
            Debug.WriteLine($"An error occurred: {ex.Message}");
        }
    }
}

Then here is the MyData.cs class:

public class MyData : INotifyPropertyChanged {
    private string number { get; set; }
    public string Number {
        get { return number; }
        set {
            if(number != value) {
                number = value;
                OnPropertyChanged(nameof(Number));
            }
        }
    }
    private string name { get; set; }
    public string Name {
        get { return name; }
        set {
            if(name != value) {
                name = value;
                OnPropertyChanged(nameof(Name));
            }
        }
    }
    private string info { get; set; }
    public string Info {
        get { return info; } 
        set { 
            if(info != value)  { 
                info = value; 
                OnPropertyChanged(nameof(Info)); 
            } 
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName) {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Additionally, when I go the route of inserting the<Label Text="{Binding MyObvList.Count}" /> the next time I click the button I get the "Object reference not set to an instance of an object." error as it attempts to do the MyObvList.Clear();This is apparently known with iOS. I've seen various suggested solutions for that such as setting the object to a new ObservableCollection<MyData> or while(MyObvList.Any()) { MyObvList.RemoveAt(0); } however, I've not had any luck with these, failing to get the list to populate a second time. I tried swapping over to the MVVM model, but had the same issues as above. So, there's something bigger going on here. Need solutions.


Solution

  • There's an issue with ListView in .NET MAUI for iOS. Swapping over to CollectionView fixed the issue entirely.