Exemple #1
0
        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!");
        }
Exemple #3
0
        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);
            }
        }