{"record":{"id":"644fc6283ca1427e","repo":"dotnet/maui","slug":"index-math-max-e-newstartingindex-e-oldstarting","errorCode":null,"errorMessage":"Index '{Math.Max(e.NewStartingIndex, e.OldStartingIndex)}' is greater than the number of rows '{lastIndex}'.","messagePattern":"Index '(.+?)' is greater than the number of rows '(.+?)'\\.","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/Compatibility/Core/src/iOS/Renderers/ListViewRenderer.cs","lineNumber":568,"sourceCode":"\t\t{\n\t\t\tvar exArgs = e as NotifyCollectionChangedEventArgsEx;\n\t\t\tif (exArgs != null)\n\t\t\t\t_dataSource.Counts[section] = exArgs.Count;\n\n\t\t\t// This means the UITableView hasn't rendered any cells yet\n\t\t\t// so there's no need to synchronize the rows on the UITableView\n\t\t\tif (Control.IndexPathsForVisibleRows == null && e.Action != NotifyCollectionChangedAction.Reset)\n\t\t\t\treturn;\n\n\t\t\tvar groupReset = resetWhenGrouped && Element.IsGroupingEnabled;\n\n\t\t\t// We can't do this check on grouped lists because the index doesn't match the number of rows in a section.\n\t\t\t// Likewise, we can't do this check on lists using RecycleElement because the number of rows in a section will remain constant because they are reused.\n\t\t\tif (!groupReset && Element.CachingStrategy == ListViewCachingStrategy.RetainElement)\n\t\t\t{\n\t\t\t\tvar lastIndex = Control.NumberOfRowsInSection(section);\n\t\t\t\tif (e.NewStartingIndex > lastIndex || e.OldStartingIndex > lastIndex)\n\t\t\t\t\tthrow new ArgumentException(\n\t\t\t\t\t\t$\"Index '{Math.Max(e.NewStartingIndex, e.OldStartingIndex)}' is greater than the number of rows '{lastIndex}'.\");\n\t\t\t}\n\n\t\t\tswitch (e.Action)\n\t\t\t{\n\t\t\t\tcase NotifyCollectionChangedAction.Add:\n\t\t\t\t\tif (e.NewStartingIndex == -1 || groupReset)\n\t\t\t\t\t\tgoto case NotifyCollectionChangedAction.Reset;\n\n\t\t\t\t\tInsertRows(e.NewStartingIndex, e.NewItems.Count, section);\n\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase NotifyCollectionChangedAction.Remove:\n\t\t\t\t\tif (e.OldStartingIndex == -1 || groupReset)\n\t\t\t\t\t\tgoto case NotifyCollectionChangedAction.Reset;\n\n\t\t\t\t\tDeleteRows(e.OldStartingIndex, e.OldItems.Count, section);","sourceCodeStart":550,"sourceCodeEnd":586,"githubUrl":"https://github.com/dotnet/maui/blob/f377ff1c5ee04d334d8a925f50c83a6b7afddf03/src/Compatibility/Core/src/iOS/Renderers/ListViewRenderer.cs#L550-L586","documentation":"ListViewRenderer's CellsOnCollectionChanged validates that a NotifyCollectionChangedEventArgs index (NewStartingIndex/OldStartingIndex) does not exceed the UITableView's current row count for the section. This only fires for non-grouped, RetainElement lists. The check exists because iOS UITableView will crash if asked to insert/update rows past its reported count - so MAUI surfaces a clearer ArgumentException first.","triggerScenarios":"An ObservableCollection bound to ListView.ItemsSource raises a collection-changed event with a NewStartingIndex or OldStartingIndex larger than the number of rows currently in that section. Common with batched/range edits, multi-threaded mutation, or an incorrect NotifyCollectionChangedEventArgs constructed manually.","commonSituations":"Calling CollectionChanged with Reset-vs-Add semantics incorrectly; mutating the source collection off the main thread; replacing the ItemsSource while a pending change is in flight; using RetainElement with a source that reports stale indices.","solutions":["Always mutate the bound ObservableCollection on the main UI thread.","For bulk edits, use a single Reset notification or replace the entire ItemsSource rather than per-item Add with indices.","Switch the ListView to RecycleElement (the check is skipped for that strategy) if appropriate.","If raising collection changes manually, ensure NewStartingIndex/OldStartingIndex are accurate and within bounds."],"exampleFix":"// before\n_items.Add(item); // fired from background thread with stale index\n\n// after\nDevice.BeginInvokeOnMainThread(() => _items.Add(item));\n// or for bulk:\n_items = new ObservableCollection<Item>(newItems);\nlist.ItemsSource = _items;","handlingStrategy":"validation","validationCode":"// Ensure all ItemsSource mutations occur on the main thread:\nDevice.BeginInvokeOnMainThread(() => collection.Add(item));","typeGuard":null,"tryCatchPattern":"try { /* navigation/mutation */ }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"number of rows\"))\n{\n    // collection state desynced; refresh the source\n    list.ItemsSource = null;\n    list.ItemsSource = collection;\n}","preventionTips":["Mutate bound collections only on the UI thread.","Prefer replacing ItemsSource for bulk edits over index-based adds.","Avoid mixing RetainElement with rapidly-changing sources."],"tags":["argumentexception","ios","listview","collection-changed","threading","retain-element"],"backgroundTag":null,"analyzedSha":"f377ff1c5ee04d334d8a925f50c83a6b7afddf03","analyzedAt":"2026-08-13T14:26:18.069Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}