Home > Blockchain >  Nested CollectionView null reference exception upon refreshing
Nested CollectionView null reference exception upon refreshing

Time:06-08

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.

Null Reference Exception

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.

  • Related