1
votes

Directly to the point. My list view looks like the following xaml (Not 100% the same but should be detailed enough)

<ListView ItemsSource="{Binding Result}"
                          SelectedItem="{Binding Selected}"
                          HasUnevenRows="True">My content...</ListView>

This is bound to my view model which looks like this

private CancellationTokenSource cts = null
private CancellationToken ct = CancellationToken.None
private List<Task> tasks = new List<Task>()
public ObservableCollection<T> Source = new ObservableCollection<T>()
public ObservableCollection<T> Result = new ObservableCollection<T>()
public string Query

It also have the following method. This method is where my problem is. The method should behave like this:

  • Get all elements from source (doesn't matter from where)
  • Start a task which will run in the background (to build the list step by step, to reduce the number of at the same generated listview items, performance)
    • So it will add the elements to the result step by step (In my case 100 elements max at the same time)

And here is the code Note: Source has been successfully loaded and the elements are in there

            cts.Cancel();
            cts.Dispose();
            Task.WaitAll(tasks.ToArray());
            tasks.Clear();
            cts = new CancellationTokenSource();
            ct = cts.Token;
            Result.Clear();
            // Create background task
            tasks.Add(Task.Factory.StartNew(() =>
            {
                var validItems = Source.Where(c => ((ISearchQueryViewModel)c).SearchData.RegexContains(Query)).OrderByDescending(c => ((ISearchQueryViewModel)c).Aktive).ToList();
                if (ct.IsCancellationRequested)
                    return;
                var numberOfValidItemsToAdd = validItems.Count;
                var currentIndex = 0;
                while (numberOfValidItemsToAdd > 0)
                {
                    if (ct.IsCancellationRequested)
                        return;
                    var numberOfIndexes = 100;
                    if (numberOfValidItemsToAdd < 100)
                        numberOfIndexes = numberOfValidItemsToAdd;
                    for (int i = 0; i < numberOfIndexes; i++)
                    {
                        currentIndex++;
                        if (ct.IsCancellationRequested)
                            return;
                        Result.Add(validItems[i]);
                    }
                    numberOfValidItemsToAdd -= numberOfIndexes;
                    for (int i = 0; i < 100; i++)
                    {
                        if (ct.IsCancellationRequested)
                            return;
                        Thread.Sleep(50);
                    }
                }
            }, ct));

This result the error

Unhandled Exception:

Foundation.MonoTouchException: Objective-C exception thrown. Name: NSInternalInconsistencyException Reason: Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). Native stack trace: 0
CoreFoundation 0x00000001104da12b __exceptionPreprocess + 171 1 libobjc.A.dylib 0x000000011dda6f41 objc_exception_throw + 48 2 CoreFoundation
0x00000001104df2f2 +[NSException raise:format:arguments:] + 98 3
Foundation 0x0000000111109d69 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 193 4
UIKit 0x0000000114652209 -[UITableView _endCellAnimationsWithContext:] + 19416 5 UIKit 0x000000011466d075 -[UITableView endUpdates] + 75 6
MobileClient.iOS 0x000000010fdfd9f9 xamarin_dyn_objc_msgSend + 217 7 ???
0x000000013f12d452 0x0 + 5353165906

4
I think you are hitting bug bugzilla.xamarin.com/show_bug.cgi?id=59896 - even though the titel of the bug is very specific, it seems more scenario's trigger it, reading the comments. - Depechie
@Depechie thanks, It resolved the error. (Little workaround) I posted it as answer, hope you are okey with this c: Grateful thanks and have a nice day c: - user7488587
@Depechie well^^ after a few tests, the same error came^^ If you know a good work around it would be great c: thank you and have a nice day c: - user7488587

4 Answers

1
votes

This worked for me. You have to set these 2 flags to false, clear the list it is bound to, turn the flags to true and then rebuild the list with new content.

                ListView.HasUnevenRows = false;
                ListView.IsGroupingEnabled = false;

                BindableGroupedObservableCollection.Clear();

                ListView.HasUnevenRows = true;
                ListView.IsGroupingEnabled = true;

                BindableGroupedObservableCollection.Rebuild();
0
votes

Note: This answer is outdated, I will keep you ongoing if I found a good way to resolve this problem.

Great thanks to @Depechie,

It was exactly this bug. I resolved it like this:

cts = new CancellationTokenSource();
ct = cts.Token;
//Result.Clear(); <-- I only changed this to
//Result = new ObservableCollection<T>(); // <-- this
// Outdated, currently using Result.RemoveAt(0); <-- See code edit

Hope the bug will be fixed in the next version of Xamarin.Forms.

And Note: I created a new list because of performance issues. It would take a rather long time to manually remove item by item.

This was the answer that gave me that idea from the link below

while(myCollection.Count > 0) 
      myCollection.RemoveAt(0);

Link: https://bugzilla.xamarin.com/show_bug.cgi?id=59896

The code above (in the question sould work like this after the next few updates^^ hopefully)

Thanks again and have a nice day c:

Edit:

I also could get rid of this wired looking method, so my code looks now exactly like this:

            cts.Cancel();
            cts.Dispose();
            Task.WaitAll(tasks.ToArray());
            tasks.Clear();
            cts = new CancellationTokenSource();
            ct = cts.Token;
            // Result.Clear();
            // Workaround see: https://stackguides.com/questions/48491781/xamarin-forms-ios-build-list-view-items-dynamically-observablecollection-and/48505622#48505622
            // Bug: https://bugzilla.xamarin.com/show_bug.cgi?id=59896
            // Result = new ObservableCollection<T>();
            while (Result.Count > 0) // <-- Temporary fix, low performance on > 10000 elements and may result the same error
                Result.RemoveAt(0);
            // Create background task
            tasks.Add(Task.Factory.StartNew(() =>
            {
                var validItems = Source.Where(c => ((ISearchQueryViewModel)c).SearchData.RegexContains(Query)).OrderByDescending(c => ((ISearchQueryViewModel)c).Aktive).ToList();
                if (ct.IsCancellationRequested)
                    return;
                for (int i = 0; i < validItems.Count; i++)
                {
                    if (ct.IsCancellationRequested)
                        return;
                    Result.Add(validItems[i]);
                    Thread.Sleep(25);
                }
            }, ct));

Edit 2:

Status update: Well the workaround doesn't work every time, i will keep you on going if I found a possible way to solve the problem. The code above will be kept actual right away.

Edit 3:

Currently investigating the System.Collection namespace. If someone is intrested this is the officially code from the collection namespace from ms

http://referencesource.microsoft.com/#System/compmod/system/collections/objectmodel/observablecollection.cs

http://referencesource.microsoft.com/#mscorlib/system/collections/objectmodel/collection.cs,281923b8611114ec

0
votes

Just in case anyone runs into this like I did, I'll post my solution. If you are trying to use .Clear() use this:

while(MyObserableCollection.Count > 0)
{
    MyObserableCollection.RemoveAt(0);
}

If you are trying to use .Add() this will also cause an NS exception. I did this:

MyObservableCollection.Insert(MyObservableCollection.Count, myNewObject);

Both the .Insert() and .Remove() methods work so this was my workaround. If anyone else finds more info on this please comment on this post.

Happy Coding!

0
votes

in iOS it throws MonoTouchException exception when we work with ObservableCollection with Clear() and Add() functions of ObservableCollection. The workaround for this is,

After

Clear()

method invocation of ObservableCollection.

ReInitialise the Collection,

Result = new ObservableCollection<T>(GetNewList());