UWP App Crashes when populating an ObservableCollection<MyClass> in OnNavigatedTo()

0

this is a very weird crash I think.

The UWP app fails "sometimes" when FirstPage is selected. This page has an AdaptativeGridView where each item is a FlipView. The ItemsSource of all FlipViews is the same, a unique ObservableCollection in code-behind.

UI capture example: ui-example

The issue is happening in UWP apps, in the async version of OnNavigatedTo() method, when you load data in an ObservableCollection.

In case you use a fully sync OnNavigatedTo() to load the ObservableCollection, I haven't seen the issue so far.

The output error is:

'Proj.UWP.Blank.NavigationTest.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Program Files\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.29301.2_x86__8wekyb3d8bbwe\System.Diagnostics.Debug.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
The thread 0x39c has exited with code 0 (0x0).
The thread 0x3284 has exited with code 0 (0x0).
'Proj.UWP.Blank.NavigationTest.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Program Files\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.29301.2_x86__8wekyb3d8bbwe\System.Diagnostics.StackTrace.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'Proj.UWP.Blank.NavigationTest.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Program Files\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.29301.2_x86__8wekyb3d8bbwe\System.Collections.Concurrent.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'Proj.UWP.Blank.NavigationTest.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Program Files\WindowsApps\Microsoft.NET.CoreFramework.Debug.2.2_2.2.29301.2_x86__8wekyb3d8bbwe\System.Reflection.Metadata.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
Exception thrown: 'System.Exception' in System.ObjectModel.dll
An exception of type 'System.Exception' occurred in System.ObjectModel.dll but was not handled in user code
The operation attempted to access data outside the valid range (Exception from HRESULT: 0x8000000B)

'Proj.UWP.Blank.NavigationTest.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\PrivateAssemblies\Runtime\Microsoft.VisualStudio.Debugger.Runtime.NetCoreApp.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'Proj.UWP.Blank.NavigationTest.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Program Files\WindowsApps\Microsoft.NET

The stacktrace is:

   at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ObservableCollection`1.InsertItem(Int32 index, T item)
   at System.Collections.ObjectModel.Collection`1.Add(T item)
   at Proj.UWP.Blank.NavigationTest.Pages.FirstPage.FillFlipViewData(List`1 filenames) in C:\Users\**\source\repos\Sol.UWP.Blank.NavigationTest\Proj.UWP.Blank.NavigationTest\Pages\FirstPage.xaml.cs:line 71
   at Proj.UWP.Blank.NavigationTest.Pages.FirstPage.<OnNavigatedTo>d__9.MoveNext() in C:\Users\**\source\repos\Sol.UWP.Blank.NavigationTest\Proj.UWP.Blank.NavigationTest\Pages\FirstPage.xaml.cs:line 61

The code-behind is:

    public sealed partial class FirstPage : Page
    {

        public ObservableCollection<MyAdaptativeGridViewItem> AdaptativeGridViewData { get; set; } = new ObservableCollection<MyAdaptativeGridViewItem>()
        {
            new MyAdaptativeGridViewItem(1, "Item 1"),
            new MyAdaptativeGridViewItem(2, "Item 2"),
            new MyAdaptativeGridViewItem(3, "Item 3"),
            new MyAdaptativeGridViewItem(4, "Item 4"),
        };

        public ObservableCollection<MyFlipViewItem> FlipViewData { get; set; } = new ObservableCollection<MyFlipViewItem>();

        public FirstPage()
        {
            // Note that every time we navigate to this Page
            // the full class is instantiated from scratch
            // Hence, the constructor is always executed
            // and all properties are initialized
            this.InitializeComponent();
        }

        #region Async version
        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            // Get in a List<string> all image locations
            var filenames = await LoadDataAsync();

            // This function generates the FlipViewData ObservableCollection
            // that is binded to the UI
            FillFlipViewData(filenames);
        }

        private void FillFlipViewData(List<string> filenames)
        {
            // Note that each time .Add() is used in a ObservableCollection
            // the UI "is notified" of the change
            for (int i = 0; i < filenames.Count; i++)
            {
                var name = filenames[i];
                FlipViewData.Add(new MyFlipViewItem(i, "/Assets/Images/" + name));
            }
        }

        public async Task<List<string>> LoadDataAsync()
        {
            List<string> result = new List<string>();
            string absPath = Windows.ApplicationModel.Package.Current.InstalledLocation.Path
                + "\\Assets\\Images";
            var storageFolder = await StorageFolder.GetFolderFromPathAsync(absPath);
            var files = await storageFolder.GetFilesAsync();
            foreach (var f in files)
                result.Add(f.Name);

            return result;
        }
        #endregion

    }

The UI is:

    <Page.Resources>

        <Thickness x:Key="MediumLeftRightMargin">24,0,24,0</Thickness>
        <Thickness x:Key="XSmallLeftTopRightBottomMargin">8, 8, 8, 8</Thickness>
        <Thickness x:Key="XXSmallTopMargin">0, 4, 0, 0</Thickness>

        <x:Double x:Key="MediumFontSize">16</x:Double>

        <Style x:Key="BodyTextStyle" TargetType="TextBlock">
            <Setter Property="FontWeight" Value="Normal" />
            <Setter Property="FontSize" Value="{StaticResource MediumFontSize}" />
            <Setter Property="TextTrimming" Value="CharacterEllipsis" />
            <Setter Property="TextWrapping" Value="Wrap" />
        </Style>

        <DataTemplate x:Name="FlipView_ItemTemplate" x:DataType="models:MyFlipViewItem">
            <Grid>
                <Image Source="{x:Bind ImageLocation, Mode=OneWay}" Stretch="Uniform" HorizontalAlignment="Center" Width="100"/>
            </Grid>
        </DataTemplate>

        <DataTemplate x:Key="AdaptativeGridView_ItemTemplate" x:DataType="models:MyAdaptativeGridViewItem">
            <Grid x:Name="itemThumbnail"
                  Padding="{StaticResource XSmallLeftTopRightBottomMargin}"
                  Background="{ThemeResource SystemControlPageBackgroundChromeLowBrush}">

                <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">

                    <FlipView Width="150" Height="150"
                          ItemTemplate="{StaticResource FlipView_ItemTemplate}"
                          ItemsSource="{Binding ElementName=ThisPrincipalPage, Path=FlipViewData, Mode=OneWay}"/>

                    <TextBlock
                                Margin="{StaticResource XXSmallTopMargin}"
                                HorizontalAlignment="Center"
                                Style="{ThemeResource BodyTextStyle}"
                                Text="{x:Bind PresetName, Mode=OneWay}" />
                </StackPanel>
            </Grid>
        </DataTemplate>


    </Page.Resources>


    <Grid x:Name="ContentArea" Margin="{StaticResource MediumLeftRightMargin}">
        <Grid
            Background="{ThemeResource SystemControlPageBackgroundChromeLowBrush}">
            <!--
                The SystemControlPageBackgroundChromeLowBrush background represents where you should place your content. 
                Place your content here.
            -->
            <StackPanel>

                <GridView
                Padding="{StaticResource MediumLeftRightMargin}"
                IsItemClickEnabled="True"
                ItemTemplate="{StaticResource AdaptativeGridView_ItemTemplate}"
                ItemsSource="{x:Bind AdaptativeGridViewData, Mode=OneWay}"
                SelectionMode="None"/>

            </StackPanel>
        </Grid>
    </Grid>

Feel free to find the code here and test it.

Extra observations:

  • In the github project you will have a fully sync version (just comment/uncomment the region to test it).

  • In the sync version the code never fails, and UI state when the items are added (just before FlipViewData.Add()) looks always like a blank content.

UI-unloaded-in-aync-and-sync

However, in the async version, the UI sometimes is blank and sometimes is partially loaded like:

UI-partially-loaded-in-async

I have noticed that the issue only happens when we add items in this UI state.

  • The error always happens after adding the first item to FlipViewData.

Visual-Studio-capture

Final though:

Maybe, this suggest some incogruent data (the collection has "different" number of items depending on who is looking like a threading issue) when the ObservableColletion tries to notify the change after FlipViewData.Add(). However, the addition is happening in the UI Thread.

Alternative

There are several alternatives to make the same UI without the described issue. This is not what I want to find. However, one of the approaches might give some extra light into the described issue. Specifically, in the described approach, the GridView is binded to an ObservableCollection, and each GridView item is itselft binded to another ObervableCollection. Both ObservableCollections are two different Properties in the code-behind.

The described error does not seem to happen if the data model is redefined consisting in a single Property in the code-behind, i.e., a nested ObservableCollection, i.e.,

the new data model

    public class MyGridViewItem
    {
        public int PresetIndex { get; set; }
        public string PresetName { get; set; }
        public ObservableCollection<MyFlipViewItem> FlipViewData { get; set; }
    }

and the single Property in code-behind

public ObservableCollection<MyGridViewItem> GridViewData { get; set; } = new ObservableCollection<MyGridViewItem>();

With this approach, and using x:Bind in all the bindings in the XAML file (no longer classic Binding needed), the issue does not seem to appear.

Therefore, it seems the nested ObservableCollection is working fine but using two separate ObservableCollections (one for the root UI GridView element, and another separate for the child UI FlipView elements) is not. Maybe this is related with the notes in the official documentation:

ObservableCollection must be the root element, because the x:TypeArguments attribute that must be used to specify the constrained type of the generic ObservableCollection is only supported on the object element for the root element.

c#
uwp
observablecollection
asked on Stack Overflow Apr 20, 2021 by oddikaro • edited Apr 29, 2021 by oddikaro

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0