void resetup(IReadOnlyList <TSectionInfo> newSectionInfo) { if (newSectionInfo == null) { setupDisp.Disposable = Disposable.Empty; return; } // Disposable that holds garbage from this method. var disp = new CompositeDisposable(); setupDisp.Disposable = disp; // Disposable that holds the subscriptions to individual sections. var subscrDisp = new SerialDisposable(); disp.Add(subscrDisp); // Decide when we should check for section changes. var reactiveSectionInfo = newSectionInfo as IReactiveNotifyCollectionChanged; var sectionChanged = reactiveSectionInfo == null?Observable.Return(Unit.Default) : reactiveSectionInfo .Changed .Select(_ => Unit.Default) .ObserveOn(RxApp.MainThreadScheduler) .StartWith(Unit.Default); // ^ ObserveOn before StartWith is important, // this means that we'll bind the new Source // right away instead of waiting to be scheduled. if (reactiveSectionInfo == null) { this.Log().Warn("New section info does not implement IReactiveNotifyCollectionChanged."); } // Add section change listeners. Always will run once right away // due to sectionChanged's construction. disp.Add(sectionChanged.Subscribe(_ => { // TODO: Instead of listening to Changed events and then reseting, // we could listen to more specific events and avoid some reloads. var disp2 = new CompositeDisposable(); subscrDisp.Disposable = disp2; for (int i = 0; i < newSectionInfo.Count; i++) { var section = i; var current = newSectionInfo[i].Collection; disp2.Add(current .Changed .Buffer(TimeSpan.FromMilliseconds(250), RxApp.MainThreadScheduler) .Subscribe( xs => sectionCollectionChanged(section, xs), ex => this.Log().ErrorException("Error while watching section " + i + "'s Collection.", ex))); } adapter.ReloadData(); })); }
void AttachToSectionInfo(IReadOnlyList <TSectionInfo> newSectionInfo) { this.Log().Debug("SectionInfo changed to {0}, resetup data and bindings...", newSectionInfo); if (newSectionInfo == null) { this.Log().Debug("Null SectionInfo, done!"); setupDisp.Disposable = Disposable.Empty; return; } // Disposable that holds garbage from this method. var disp = new CompositeDisposable(); setupDisp.Disposable = disp; // Disposable that holds the subscriptions to individual sections. var subscrDisp = new SerialDisposable(); disp.Add(subscrDisp); // Decide when we should check for section changes. var reactiveSectionInfo = newSectionInfo as IReactiveNotifyCollectionChanged <TSectionInfo>; var sectionChanging = reactiveSectionInfo == null?Observable.Never <Unit>() : reactiveSectionInfo .Changing .Select(_ => Unit.Default); var sectionChanged = reactiveSectionInfo == null?Observable.Return(Unit.Default) : reactiveSectionInfo .Changed .Select(_ => Unit.Default) .StartWith(Unit.Default); if (reactiveSectionInfo == null) { this.Log().Warn("New section info {0} does not implement IReactiveNotifyCollectionChanged.", newSectionInfo); } // NOTE: the implicit use of the immediate scheduler in various places below is intentional // without it, iOS can interject its own logic amongst ours and therefore could see an inconsistent view of the data // Add section change listeners. Always will run once right away // due to sectionChanged's construction. // // TODO: Instead of listening to Changed events and then reseting, // we could listen to more specific events and avoid some reloads. disp.Add(sectionChanging.Subscribe(_ => { // Dispose the old bindings. Ensures that old events won't // arrive while we morph into the new data. this.Log().Debug("{0} is about to change, disposing section bindings...", newSectionInfo); subscrDisp.Disposable = Disposable.Empty; })); disp.Add(sectionChanged.Subscribe(x => { var disp2 = new CompositeDisposable(); subscrDisp.Disposable = disp2; var sectionChangedWhilstNotReloadingList = new List <IObservable <NotifyCollectionChangedEventArgs> >(); var sectionChangedList = new List <IObservable <NotifyCollectionChangedEventArgs> >(); for (int i = 0; i < newSectionInfo.Count; i++) { var section = i; var current = newSectionInfo[i].Collection; this.Log().Debug("Setting up section {0} binding...", section); sectionChangedWhilstNotReloadingList.Add(adapter .IsReloadingData .DistinctUntilChanged() .Do(y => this.Log().Debug("IsReloadingData: {0} (for section {1})", y, section)) .Select(y => y ? Observable.Empty <NotifyCollectionChangedEventArgs>() : current.Changed) .Switch()); sectionChangedList.Add(current.Changed); } var anySectionChangedWhilstNotReloading = Observable.Merge(sectionChangedWhilstNotReloadingList); var anySectionChanged = Observable.Merge(sectionChangedList); disp2.Add(adapter .IsReloadingData .DistinctUntilChanged() .Select(y => y ? anySectionChanged : Observable.Never <NotifyCollectionChangedEventArgs>()) .Switch() .Subscribe(_ => { adapter.ReloadData(); pendingChanges.Clear(); })); disp2.Add(anySectionChangedWhilstNotReloading .Subscribe(_ => { if (!isCollectingChanges) { isCollectingChanges = true; RxApp.MainThreadScheduler.Schedule(() => this.ApplyPendingChanges()); } })); for (var section = 0; section < sectionChangedWhilstNotReloadingList.Count; ++section) { var sectionNum = section; var specificSectionChanged = sectionChangedWhilstNotReloadingList[section]; disp2.Add( specificSectionChanged .Subscribe( xs => this.pendingChanges.Add(Tuple.Create(sectionNum, xs)), ex => this.Log().Error("Error while watching section {0}'s Collection: {1}", sectionNum, ex))); } this.Log().Debug("Done resetuping section data and bindings!"); // Tell the view that the data needs to be reloaded. this.Log().Debug("Calling ReloadData()...", newSectionInfo); adapter.ReloadData(); pendingChanges.Clear(); })); this.Log().Debug("Done resetuping all bindings!"); }
public CommonReactiveSource( IUICollViewAdapter <TUIView, TUIViewCell> adapter, IEnumerable <ISectionInformation <TUIView, TUIViewCell> > sectionInfo) { this.adapter = adapter; this.sectionInfo = sectionInfo.ToList(); for (int i = 0; i < this.sectionInfo.Count; i++) { var current = this.sectionInfo[i].Collection; var section = i; var disp = current.Changed.Buffer(TimeSpan.FromMilliseconds(250), RxApp.MainThreadScheduler).Subscribe(xs => { if (xs.Count == 0) { return; } var resetOnlyNotification = new [] { new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset) }; this.Log().Info("Changed contents: [{0}]", String.Join(",", xs.Select(x => x.Action.ToString()))); if (xs.Any(x => x.Action == NotifyCollectionChangedAction.Reset)) { this.Log().Info("About to call ReloadData"); adapter.ReloadData(); didPerformUpdates.OnNext(resetOnlyNotification); return; } var updates = xs.Select(ea => Tuple.Create(ea, getChangedIndexes(ea))).ToList(); var allChangedIndexes = updates.SelectMany(u => u.Item2).ToList(); // Detect if we're changing the same cell more than // once - if so, issue a reset and be done if (allChangedIndexes.Count != allChangedIndexes.Distinct().Count()) { this.Log().Info("Detected a dupe in the changelist. Issuing Reset"); adapter.ReloadData(); didPerformUpdates.OnNext(resetOnlyNotification); return; } this.Log().Info("Beginning update"); adapter.PerformBatchUpdates(() => { foreach (var update in updates.AsEnumerable().Reverse()) { var changeAction = update.Item1.Action; var changedIndexes = update.Item2; switch (changeAction) { case NotifyCollectionChangedAction.Add: doUpdate(adapter.InsertItems, changedIndexes, section); break; case NotifyCollectionChangedAction.Remove: doUpdate(adapter.DeleteItems, changedIndexes, section); break; case NotifyCollectionChangedAction.Replace: doUpdate(adapter.ReloadItems, changedIndexes, section); break; case NotifyCollectionChangedAction.Move: // NB: ReactiveList currently only supports single-item // moves var ea = update.Item1; this.Log().Info("Calling MoveRow: {0}-{1} => {0}{2}", section, ea.OldStartingIndex, ea.NewStartingIndex); adapter.MoveItem( NSIndexPath.FromRowSection(ea.OldStartingIndex, section), NSIndexPath.FromRowSection(ea.NewStartingIndex, section)); break; default: this.Log().Info("Unknown Action: {0}", changeAction); break; } } this.Log().Info("Ending update"); didPerformUpdates.OnNext(xs); }); }); innerDisp.Add(disp); } }