J'espère que quelqu'un peut m'aider avec ce qui suit, parce que je suis complètement coincé."System.InvalidOperationException: Collection a été modifiée" dans MvvmCross TableView binding
Je reçois l'exception ci-dessous dans mon application MvvmCross Xamarin.iOS lorsque je lie sur mon TableView. Cela ne se produit que lorsque je change la source de données (chaque fois que je change la date, le TableView doit être mis à jour).
Incident Identifier: 7E7C2B15-7CC4-4AE7-9891-C4FD82358009
CrashReporter Key: 46CC21C0-DDE1-4313-9658-EC79D767939B
Hardware Model: iPhone7,2
Process: UurwerkiOS [4326]
Path: /var/containers/Bundle/Application/75969477-A516-44C3-A5A3-5B24DDDC89C8/UurwerkiOS.app/UurwerkiOS
Identifier: com.route2it.uurwerk
Version: 1.0 (1.0.96)
Code Type: ARM-64
Parent Process: ??? [1]
Date/Time: 2016-07-04T13:16:38Z
Launch Time: 2016-07-04T13:16:31Z
OS Version: iPhone OS 9.3.2 (13F69)
Report Version: 104
Exception Type: SIGABRT
Exception Codes: #0 at 0x1816ac11c
Crashed Thread: 5
Application Specific Information:
*** Terminating app due to uncaught exception 'System.AggregateException', reason: 'System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. ---> System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
at System.ThrowHelper.ThrowInvalidOperationException (ExceptionResource resource) <0x10044bec0 + 0x00024> in <filename unknown>:0
at System.Collections.Generic.List`1+Enumerator[T].MoveNextRare() <0x1003bf900 + 0x0002f> in <filename unknown>:0
at System.Collections.Generic.List`1+Enumerator[T].MoveNext() <0x1003bf830 + 0x0009f> in <filename unknown>:0
at MvvmCross.Binding.BindingContext.MvxTaskBasedBindingContext.<OnDataContextChange>b__20_0() <0x1007c1990 + 0x0023f> in <filename unknown>:0
at System.Threading.Tasks.Task.InnerInvoke() <0x10043f1f0 + 0x0005f> in <filename unknown>:0
at System.Threading.Tasks.Task.Execute() <0x10043ea20 + 0x00043> in <filename unknown>:0
--- End of inner exception stack trace ---
---> (Inner Exception #0) System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
at System.ThrowHelper.ThrowInvalidOperationException (ExceptionResource resource) <0x10044bec0 + 0x00024> in <filename unknown>:0
at System.Collections.Generic.List`1+Enumerator[T].MoveNextRare() <0x1003bf900 + 0x0002f> in <filename unknown>:0
at System.Collections.Generic.List`1+Enumerator[T].MoveNext() <0x1003bf830 + 0x0009f> in <filename unknown>:0
at MvvmCross.Binding.BindingContext.MvxTaskBasedBindingContext.<OnDataContextChange>b__20_0() <0x1007c1990 + 0x0023f> in <filename unknown>:0
at System.Threading.Tasks.Task.InnerInvoke() <0x10043f1f0 + 0x0005f> in <filename unknown>:0
at System.Threading.Tasks.Task.Execute() <0x10043ea20 + 0x00043> in <filename unknown>:0
Au début, je pensais qu'il était lié à l'un de mes méthodes Async (qui peut-être pas terminé dans le temps alors que le prochain était déjà en cours d'exécution). J'ai donc supprimé tout mon code asynchrone, mais l'exception se produit toujours. J'ai également veillé à ne pas modifier moi-même la collection énumérable. Je récupère les données (qui sont simplement un tableau dans la mémoire) et les renvoie en tant que nouvelle liste à la propriété à laquelle TableView est lié. Voici les extraits de code qui composent la liaison (il est beaucoup d'informations, mais je voulais être aussi complète que possible):
CalendarViewController:
public override void ViewDidLoad()
{
base.ViewDidLoad();
if (NavigationController != null)
NavigationController.NavigationBarHidden = false;
InitCalendar();
InitNavigationItem();
InitTableView();
ApplyConstraints();
var shiftForDateTableViewSource = new MvxSimpleTableViewSource(_tableView, CalendarTableViewCell.Key, CalendarTableViewCell.Key);
shiftForDateTableViewSource.DeselectAutomatically = true;
_tableView.RowHeight = 45;
_tableView.Source = shiftForDateTableViewSource;
var set = this.CreateBindingSet<CalendarView, CalendarViewModel>();
set.Bind(shiftForDateTableViewSource).To(vm => vm.ShiftsForSelectedDate);
set.Bind(shiftForDateTableViewSource).For(vm => vm.SelectionChangedCommand).To(vm => vm.ShiftSelectedCommand);
set.Apply();
_tableView.ReloadData();
}
private void InitTableView()
{
_tableView = new UITableView();
_tableView.RegisterClassForCellReuse(typeof(UITableViewCell), CalendarTableViewCell.Key);
Add(_tableView);
}
CalendarTableViewCell:
public partial class CalendarTableViewCell : MvxTableViewCell
{
public static readonly NSString Key = new NSString("CalendarTableViewCell");
public static readonly UINib Nib;
static CalendarTableViewCell()
{
Nib = UINib.FromName("CalendarTableViewCell", NSBundle.MainBundle);
}
protected CalendarTableViewCell(IntPtr handle) : base(handle)
{
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
var set = this.CreateBindingSet<CalendarTableViewCell, Shift>();
set.Bind(StartTimeLabel).To(vm => vm.StartDate).WithConversion("StringFormat", "HH:mm");
set.Bind(EndTimeLabel).To(vm => vm.EndDate).WithConversion("StringFormat", "HH:mm");
set.Bind(ColorBarView).For("BackgroundColor").To(vm => vm.Color).WithConversion("RGB");
set.Bind(TitleLabel).To(vm => vm).WithConversion("ConcatenatedEventTitle");
set.Bind(LocationLabel).To(vm => vm.Location);
set.Apply();
}
}
CalendarViewModèle:
public class CalendarViewModel
: MvxViewModel
{
private readonly IShiftService _shiftService;
public CalendarViewModel(IShiftService shiftService)
{
if (shiftService == null)
throw new ArgumentNullException(nameof(shiftService));
_shiftService = shiftService;
}
public override void Start()
{
base.Start();
Shifts = _shiftService.GetShiftsForEmployeeAsync(1);
}
private IEnumerable<Shift> _shifts;
public IEnumerable<Shift> Shifts
{
get { return _shifts; }
set
{
SetProperty(ref _shifts,
value,
nameof(Shifts));
}
}
private IEnumerable<Shift> _shiftsForSelectedDate;
public IEnumerable<Shift> ShiftsForSelectedDate
{
get { return _shiftsForSelectedDate; }
private set
{
if (_shiftsForSelectedDate == value)
return;
SetProperty(ref _shiftsForSelectedDate,
value,
nameof(ShiftsForSelectedDate));
}
}
private DateTime? _selectedDate;
public DateTime? SelectedDate
{
get { return _selectedDate; }
set
{
if (_selectedDate == value)
return;
SetProperty(ref _selectedDate,
value,
nameof(SelectedDate));
if (_selectedDate.HasValue)
FetchShiftsForSelectedDate();
}
}
private void FetchShiftsForSelectedDate()
{
ShiftsForSelectedDate = _shiftService.GetShiftsForSelectedDateAsync(_selectedDate.Value);
}
}
MockShiftService (implémente l'interface IShiftService):
public class MockShiftService
: IShiftService
{
private IList<Shift> _shifts;
public MockShiftService()
{
Initialize();
}
public IEnumerable<Shift> GetShiftsForEmployeeAsync(int employeeId)
{
return _shifts;
}
public IEnumerable<Shift> GetShiftsForSelectedDateAsync(DateTime selectedDate)
{
var endDate = selectedDate.Date.Add(new TimeSpan(23, 59, 59));
return _shifts
.Where(s => s.StartDate <= endDate && s.EndDate >= selectedDate)
.ToList();
}
public Shift GetShiftByIdAsync(int shiftId)
{
return _shifts.First((shift) => shift.Id == shiftId);
}
private void Initialize()
{
var shifts = new List<Shift>();
// The in memory array gets populated here which
// is straight forward creating instances of the
// 'Shift' class and assigning it's properties before
// adding it to the 'shifts' collection. I left
// this code out to keep it as short as possible.
}
}
MISE À JOUR:
J'ai référencé mon projet directement aux assemblées de débogage de MvvmCross et compris que l'exception est levée sur la ligne 127 de la classe MvxTaskBasedBindingContext
et arrive toujours à la deuxième itération. De ceci je conclus que la collection est changée pendant la première itération. Malheureusement, je ne peux pas comprendre pourquoi ou comment.
J'ai remarqué que le MvxTaskBasedBindingContext
remplace le MvxBindingContext
(modifié par softlion le 11-5-2016). Quand je force ma demande à utiliser la classe MvxBindingContext
tout fonctionne bien (bien qu'un peu laggy). Cela me fait croire que le problème est dans le MvxTaskBasedBindingContext
mais je ne peux vraiment pas comprendre pourquoi, toute aide serait grandement appréciée.
MISE À JOUR 2:
Après un peu plus de débogage et bidouiller j'ai découvert que l'exception est liée aux liaisons établies par ma CalendarTableViewCell
classe (qui devrait fournir la mise en page pour chaque élément dans le tableview défini dans mon CalendarViewController
Lorsque je commente les liaisons dans la classe CalendarTableViewCell
l'exception ne se produit pas (voir mon code ci-dessus) Je ne sais toujours pas ce qui pourrait être faux cependant.
Quelle version de MvvmCross? Je pense que quelque chose comme ça a déjà été résolu. – Cheesebaron
Je rencontre ce problème dans les versions 4.2.0, 4.2.1 et 4.2.2. –
Je ne connais pas trop iOS, mais une chose que j'ai remarquée est qu'ils ont tendance à utiliser DelayBind. Donc, peut-être envelopper votre liaison MvxTableViewCell dans un 'this.DelayBind (() => {/ * votre code pour la liaison dans LayoutSubviews ici * /});' peut aider? Pure deviner, je n'ai aucune idée comment iOS fonctionne lol. – Plac3Hold3r