The issue
Here’s my situation : I have a page in my Maui application with a CollectionView bound to an ObservableCollection. I dynamically add at several points in my app items to this Collection through a custom WeakReferenceMessenger.
Especially, I have a button in the same page which triggers several message additions to the collection.
Here comes my issue : when I press this button on an android environment, everything works just fine, I see my messages added to the collection without any problem. But when I test it on iOS, either on an emulator or on a physical device, the application crashes and sends me the following exception :
Exception
Objective-C exception thrown. Name: NSInternalInconsistencyException Reason: request for index path for global index 12 when there are only 12 items in the collection view
Native stack trace:
0 CoreFoundation 0x0000000197c87228 7821F73C-378B-3A10-BE90-EF526B7DBA93 + 1155624
1 libobjc.A.dylib 0x0000000195121abc objc_exception_throw + 88
2 Foundation 0x0000000196f85670 34DE055D-8683-380A-9198-C3347211D13D + 7988848
3 UIKitCore 0x000000019a790ff8 96636F64-106F-30C8-A780-82DCEBB0F443 + 3362808
4 UIKitCore 0x000000019a9363cc 96636F64-106F-30C8-A780-82DCEBB0F443 + 5088204
5 UIKitCore 0x000000019a9362d4 96636F64-106F-30C8-A780-82DCEBB0F443 + 5087956
6 UIKitCore 0x000000019a9688a0 96636F64-106F-30C8-A780-82DCEBB0F443 + 5294240
7 UIKitCore 0x000000019a9684ec 96636F64-106F-30C8-A780-82DCEBB0F443 + 5293292
8 UIKitCore 0x000000019a4912b4 96636F64-106F-30C8-A780-82DCEBB0F443 + 217780
9 UIKitCore 0x000000019a75eec8 96636F64-106F-30C8-A780-82DCEBB0F443 + 3157704
10 UIKitCore 0x000000019a75d06c 96636F64-106F-30C8-A780-82DCEBB0F443 + 3149932
11 UIKitCore 0x000000019aa1bcec 96636F64-106F-30C8-A780-82DCEBB0F443 + 6028524
12 UIKitCore 0x000000019aa1b9d0 96636F64-106F-30C8-A780-82DCEBB0F443 + 6027728
13 UBScoring 0x0000000104977648 xamarin_dyn_objc_msgSendSuper + 164
14 UBScoring 0x0000000104b332b0 do_icall + 200
15 UBScoring 0x0000000104b318f4 do_icall_wrapper + 348
16 UBScoring 0x0000000104b24e28 mono_interp_exec_method + 2580
17 UBScoring 0x0000000104b221dc interp_entry_from_trampoline + 656
18 UBScoring 0x0000000104949970 native_to_interp_trampoline + 112
19 UBScoring 0x0000000104b8d458 -[__MonoMac_NSAsyncActionDispatcher xamarinApplySelector] + 96
20 Foundation 0x000000019685d574 34DE055D-8683-380A-9198-C3347211D13D + 484724
21 CoreFoundation 0x0000000197b7ca8c 7821F73C-378B-3A10-BE90-EF526B7DBA93 + 64140
22 CoreFoundation 0x0000000197b7c8a4 7821F73C-378B-3A10-BE90-EF526B7DBA93 + 63652
23 CoreFoundation 0x0000000197b7c700 7821F73C-378B-3A10-BE90-EF526B7DBA93 + 63232
24 CoreFoundation 0x0000000197b7d080 7821F73C-378B-3A10-BE90-EF526B7DBA93 + 65664
25 CoreFoundation 0x0000000197b7ec3c CFRunLoopRunSpecific + 572
26 GraphicsServices 0x00000001e4d5d454 GSEventRunModal + 168
27 UIKitCore 0x000000019a591274 96636F64-106F-30C8-A780-82DCEBB0F443 + 1266292
28 UIKitCore 0x000000019a55ca28 UIApplicationMain + 336
29 UBScoring 0x00000001049601f4 xamarin_UIApplicationMain + 60
30 UBScoring 0x0000000104b33324 do_icall + 316
31 UBScoring 0x0000000104b318f4 do_icall_wrapper + 348
32 UBScoring 0x0000000104b24e28 mono_interp_exec_method + 2580
33 UBScoring 0x0000000104b229e0 interp_runtime_invoke + 236
34 UBScoring 0x0000000104af11a8 mono_jit_runtime_invoke + 1244
35 UBScoring 0x0000000104a9891c mono_runtime_invoke_checked + 148
36 UBScoring 0x0000000104a9e820 mono_runtime_exec_main_checked + 116
37 UBScoring 0x0000000104af7be4 mono_jit_exec + 356
38 UBScoring 0x00000001049760ac xamarin_main + 2032
39 UBScoring 0x0000000104b64634 main + 64
40 dyld 0x00000001bea53f08 86D5253D-4FD1-36F3-B4AB-25982C90CBF4 + 257800
Code
The CollectionView in the page’s architecture
The code when the ViewModel is reacting to the message
[ObservableProperty] public partial ObservableRangeCollection SessionLog { get; set; } = new();
public void RegisterLogging()
{
WeakReferenceMessenger.Default.Register(this, (r, m) => MainThread.BeginInvokeOnMainThread(() =>
{
SessionLog.Add($"{DateTime.Now:HH:mm:ss} | {m.Value}");
}));
}
public class UserLogMessage(string message) : ValueChangedMessage(message)
{
public UserLogMessage(string CompetitionName, LoadCompetitionStatus status) : this(StatusToMessage(CompetitionName, status)) { }
private static string StatusToMessage(string CompetitionName, LoadCompetitionStatus status)
{
return $"{CompetitionName} - {status}";
}
}
The function called when clicking on the Button causing the crash
[RelayCommand]
public void ReloadCompetitionInfo()
{
MainThread.BeginInvokeOnMainThread(async () =>
{
IsBusy = true;
IsSyncing = true;
count_i = 0;
await AppCore.Instance.ReloadCompetitionInfo();
AppData.Instance.WriteLogFile("Competition reloaded from server.");
CanBroadcast = AppData.Instance.AppDataCompetitions.LoggedOnUser.CanBroadcast;
Broadcasting = AppData.Instance.AppDataSettings.Broadcast;
IsSyncing = false;
IsBusy = false;
});
}
The LoadCompetitionFromApiResponse, called from within ReloadCompetitionInfo
private static UBModels.Competition LoadCompetitionFromApiResponse(UBApiModels.CompetitionData CompetitionDataFromUBAPI, bool IsMatchDirector)
{
UBModels.Competition AppCompetition = new();
WeakReferenceMessenger.Default.Send(new UserLogMessage(CompetitionName: AppCompetition.Name, LoadCompetitionStatus.Init));
// Every Load[...]FromUBAPI method ends with a new UserLogMessage being sent
if (CompetitionDataFromUBAPI.squads.Count != 0) LoadSquadsFromApiResponse(AppCompetition, CompetitionDataFromUBAPI);
if (CompetitionDataFromUBAPI.teams.Count != 0) LoadTeamsFromApiResponse(AppCompetition, CompetitionDataFromUBAPI);
if (CompetitionDataFromUBAPI.competitors.Count != 0) LoadCompetitorsFromApiResponse(AppCompetition, CompetitionDataFromUBAPI);
if (CompetitionDataFromUBAPI.draws.Count != 0) LoadBracketsFromApiResponse(AppCompetition, CompetitionDataFromUBAPI);
if (CompetitionDataFromUBAPI.rounds.Count != 0) LoadRoundsFromApiResponse(AppCompetition, CompetitionDataFromUBAPI);
if (CompetitionDataFromUBAPI.targets.Count != 0) LoadTargetsFromApiResponse(AppCompetition, CompetitionDataFromUBAPI);
if (CompetitionDataFromUBAPI.contest_results.Count != 0) LoadResultsFromApiResponse(AppCompetition, CompetitionDataFromUBAPI);
if (CompetitionDataFromUBAPI.duels.Count != 0) LoadDuelsFromApiResponse(AppCompetition, CompetitionDataFromUBAPI);
WeakReferenceMessenger.Default.Send(new UserLogMessage(AppCompetition.Name, LoadCompetitionStatus.CompetitionLoaded));
return AppCompetition;
}
Workaround
I actually found a potential workaround : every time I send a message through the messenger in ReloadCompetitionInfo , I added a call to a new method WaitIOS before. However it feels like it’s not the correct approach, and that I shouldn’t need to add a delay to every single message in here.
private static void WaitiOS(int delayMs = 1000)
{
#if IOS
Task.Delay(delayMs).Wait();
#endif
}