On initial load of the page, everything is fine and displays all data correctly.
But when I update the page (either by switching page and switching back, or Pull to refresh) I get null reference exception
. I don't know how to debug this or how to fix it.
About my page: My page displays a list of Displays. Each Display has a list of videos and a list of images.
XAML (DisplaysPage.xaml):
<StackLayout>
<Button Text=" Create new display" Command="{Binding UploadDisplayCommand}" CornerRadius="0" BackgroundColor="ForestGreen" FontAttributes="Bold" />
<Label Text="No displays found in database." TextColor="White" FontSize="20" HorizontalTextAlignment="Center" IsVisible="{Binding IsEmpty}" Padding="20" />
<RefreshView IsRefreshing="{Binding IsRefreshing}" RefreshColor="Cyan" Command="{Binding LoadDisplaysCommand}" Margin="0, 5, 0, 1">
<CollectionView ItemsSource="{Binding Displays}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Display">
<Grid Padding="5">
<Frame CornerRadius="5" Padding="10">
<StackLayout Spacing="10">
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Name}" FontAttributes="Bold" FontSize="23" TextColor="White" />
<StackLayout Padding="0, -10, 0, -10" HorizontalOptions="EndAndExpand">
<Switch IsToggled="{Binding IsOn}" Toggled="Switch_Toggled" OnColor="LightGreen" ThumbColor="White" />
</StackLayout>
</StackLayout>
<StackLayout BindableLayout.ItemsSource="{Binding Videos}">
<BindableLayout.ItemTemplate>
<DataTemplate x:DataType="model:Video">
<Grid Padding="0, 5">
<Frame CornerRadius="5" Padding="0" BackgroundColor="AliceBlue">
<Image Source="{Binding ThumbnailPath}" Aspect="AspectFill" />
</Frame>
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
<StackLayout BindableLayout.ItemsSource="{Binding Images}">
<BindableLayout.ItemTemplate>
<DataTemplate x:DataType="model:Video">
<Grid Padding="0, 5">
<Frame CornerRadius="5" Padding="0" BackgroundColor="AliceBlue">
<Image Source="{Binding FilePath}" Aspect="AspectFill" />
</Frame>
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
<Label Text="{Binding Description}" FontSize="16" TextColor="White" />
<StackLayout Orientation="Horizontal">
<Label Text="Number of videos associated with the display: " FontSize="16" TextColor="White" />
<Label Text="{Binding Videos.Count}" FontSize="16" TextColor="White" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Number of images associated with the display: " FontSize="16" TextColor="White" />
<Label Text="{Binding Images.Count}" FontSize="16" TextColor="White" />
</StackLayout>
</StackLayout>
</Frame>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
</StackLayout>
ViewModel (DisplaysViewModel.cs)
[INotifyPropertyChanged]
public partial class DisplaysViewModel
{
private readonly Service<Display> displayService;
[ObservableProperty]
private ObservableCollection<Display> displays = new();
[ObservableProperty]
private bool isEmpty;
[ObservableProperty]
private bool isRefreshing;
public DisplaysViewModel(Service<Display> displayService)
{
this.displayService = displayService;
}
[ICommand]
internal async Task LoadDisplaysAsync()
{
#if ANDROID || IOS || tvOS || Tizen
UserDialogs.Instance.ShowLoading("Loading displays from the database...");
#endif
if (Displays.Count is not 0)
{
Displays.Clear();
}
try
{
await foreach (Display display in displayService.GetAllAsync().OrderBy(x => x.Id))
{
Displays.Add(display);
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
finally
{
if (Displays.Count is 0)
{
IsEmpty = true;
}
else
{
IsEmpty = false;
}
IsRefreshing = false;
#if ANDROID || IOS || tvOS
UserDialogs.Instance.HideLoading();
#endif
}
}
[ICommand]
private async Task UploadDisplayAsync()
{
await Shell.Current.DisplayAlert("Create a new display", "Under construction!", "OK");
}
}
Edit:
From inspiration from Jason, I wrapped my code with try-catch and debugged it, and it showed the issue. It was in my code-behind DisplaysPage.xaml.cs
and the null reference is on my call to GetByIdAsync method inside Switch_Toggled event:
public partial class DisplaysPage : ContentPage
{
private readonly Service<Display> displayService;
private readonly Service<Log> logService;
public DisplaysPage(DisplaysViewModel displaysViewModel, Service<Display> displayService, Service<Log> logService)
{
InitializeComponent();
this.displayService = displayService;
this.logService = logService;
BindingContext = displaysViewModel;
}
protected override async void OnAppearing()
{
await ((DisplaysViewModel)BindingContext).LoadDisplaysAsync();
}
private async void Switch_Toggled(object sender, ToggledEventArgs e)
{
Switch displaySwitch = sender as Switch;
Display selectedDisplay = displaySwitch.BindingContext as Display;
try
{
Display displayToUpdate = await displayService.GetByIdAsync(selectedDisplay.Id);
displayToUpdate.IsOn = displaySwitch.IsToggled;
string isOnString = displaySwitch.IsToggled ? "on" : "off";
await displayService.UpdateAsync(displayToUpdate);
await logService.CreateAsync(new Log()
{
Description = $"{JwtParser.ParseClaimsFromJwt(await SecureStorage.GetAsync("JWT")).Where(x => x.Type == ClaimTypes.Name).FirstOrDefault()?.Value} has {isOnString} the display.",
UserId = JwtParser.ParseClaimsFromJwt(await SecureStorage.GetAsync("JWT")).Where(x => x.Type == ClaimTypes.NameIdentifier).FirstOrDefault()?.Value
});
}
catch (Exception)
{
throw new Exception("null exception");
}
}
}
CodePudding user response:
This sort of exception as shown in the post was found by wrapping code with try-catch and doing a null-check.