Hello Anton Moroder!
I noticed that you're trying to access the first element of an empty PayGuests
list, which can lead to a System.Reflection.TargetInvocationException
, especially as it crashes in Release mode.
When the UI is being rendered, it attempts to bind to PayGuests[0].GuestName
, but the PayGuests
data list hasn't populated yet. You can see the exception count of System.ArgumentOutOfRangeException
error corresponds to the number of items in the data list, which strongly suggests that the data within the IEnumerable
list hasn't loaded by the time of binding.
The reason it might work in Debug but not Release is due to the optimizations in Release builds. These optimizations can result in the following:
- Stricter error handling: Release builds are less forgiving of unexpected conditions like accessing an empty list, as mentioned above.
- Faster execution: The faster pace increases the likelihood of the UI trying to access the data before it's ready.
- Different execution order: In older .NET runtime versions, there might have been slight variations in the timing of data binding and UI rendering.
I suggest adding a null and count check within the FirstGuestName
property to handle this scenario. This ensures that you only try to access the first element when the list actually contains elements, preventing the exception. It will display "Loading..." initially and then show the data once it's loaded.
I have created an example implementation for you:
//Your example class
public class Xenus_GIS_InHouseReservation : INotifyPropertyChanged
{
private ObservableCollection<PayGuestItem> _payGuests = new();
public Xenus_GIS_InHouseReservation()
{
PayGuests = new ObservableCollection<PayGuestItem>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ObservableCollection<PayGuestItem> PayGuests
{
get => _payGuests;
set
{
if (_payGuests != value)
{
_payGuests = value;
OnPropertyChanged(nameof(PayGuests));
OnPropertyChanged(nameof(FirstGuestName));
}
}
}
public string FirstGuestName
{
get
{
return PayGuests?.FirstOrDefault()?.GuestName ?? "Loading...";
}
}
public void NotifyUpdate()
{
OnPropertyChanged(nameof(FirstGuestName));
}
}
Load data example implementation:
public partial class with_error_page : ContentPage
{
ObservableCollection<Xenus_GIS_InHouseReservation> Reservations = new ();
public with_error_page()
{
InitializeComponent();
Reservations.Add(new Xenus_GIS_InHouseReservation
{
PayGuests = new ObservableCollection<PayGuestItem> { }
});
Reservations.Add(new Xenus_GIS_InHouseReservation
{
PayGuests = new ObservableCollection<PayGuestItem> {
new PayGuestItem { GuestName = "Bob Johnson" },
new PayGuestItem { GuestName = "Charlie Brown" }
}
});
BindableLayout.SetItemsSource(listGuest, Reservations);
}
//Example stimulate value loaded after UI rendered and UI updated for first element in list
public void OnAddItemClicked(object sender, EventArgs e)
{
Reservations[0].PayGuests.Add(new PayGuestItem { GuestName = "New Guest" });
// Manually notify that FirstGuestName has changed
Reservations[0].NotifyUpdate();
}
}
In your XAML page, you can bind the name like this. I've also added a button to simulate this issue; you can see that the System.ArgumentOutOfRangeException
is handled.
<StackLayout>
<FlexLayout x:Name="listGuest" Wrap="Wrap" Direction="Row">
<BindableLayout.ItemTemplate>
<DataTemplate x:DataType="local:Xenus_GIS_InHouseReservation">
<Border FlexLayout.Basis="50%">
<Label Text="{Binding FirstGuestName}"/>
</Border>
</DataTemplate>
</BindableLayout.ItemTemplate>
</FlexLayout>
<Button Text="Add Item" Clicked="OnAddItemClicked"/>
</StackLayout>
I hope this helps! If you encounter any issues, don't hesitate to let me know.