Cysharp / R3

The new future of dotnet/reactive and UniRx.
MIT License
1.72k stars 70 forks source link

Combining ObservableList list of observables #178

Closed karthikr-vti closed 3 months ago

karthikr-vti commented 3 months ago

How to combine a list of class with a ReactiveProperty and create a new property?

For example:

public class Operation
    {
        public ReactiveProperty<bool> IsitOkay { get; } = new();
    }

    public class Operations
    {
        private ObservableList<Operation> operations = new();

        public Operations()
        {
            IsAllOK = Observable.CombineLatest(operations.Select(x => x.IsitOkay)).Select(x => x.All(x => x))
                .ToReadOnlyReactiveProperty();
        }

        public ReadOnlyReactiveProperty<bool> IsAllOK { get;  }
    }

But this would only combine operations list during the time of construction.

My current approach is to have a new backend property and recreate subscription on every list update:

public class Operations : IDisposable
    {
        private readonly ObservableList<Operation> operations = new();
        private readonly ReactiveProperty<bool> isAllOk = new();
        private IDisposable disposable;

        public Operations()
        {
            IDisposable register()
            {
                isAllOk.Value = operations.Select(x => x.IsitOkay.CurrentValue).All(x => x);
                return Observable.CombineLatest(operations.Select(x => x.IsitOkay))
                    .Select(x => x.All(v => v))
                    .Subscribe(isAllOk.AsObserver());
            }
            disposable = register();
            operations.ObserveCountChanged()
                .AsUnitObservable()
                .Merge(operations
                    .ObserveReplace()
                    .AsUnitObservable())
                .Debounce(TimeSpan.FromMilliseconds(10))
                .Subscribe(_ =>
            {
                disposable.Dispose();
                disposable = register();
            });
        }

        public ReadOnlyReactiveProperty<bool> IsAllOK => isAllOk;

        public void Dispose()
        {
            disposable.Dispose();
        }
    }

Is there any easier way to do this?

neuecc commented 3 months ago

I had previously created extension methods like this. https://github.com/runceel/ReactiveProperty/blob/6808dc5abfb55feef695399a12295ad7d31be49c/Source/ReactiveProperty.NETStandard/Extensions/CombineLatestEnumerableExtensions.cs#L16

I think you probably want to do something similar, but it certainly feels like it's become more difficult to implement it straightforwardly in R3 compared to Rx. Let me adjust it a bit.

karthikr-vti commented 3 months ago

Thank you for the response.

https://github.com/runceel/ReactiveProperty/blob/6808dc5abfb55feef695399a12295ad7d31be49c/Source/ReactiveProperty.NETStandard/Extensions/CombineLatestEnumerableExtensions.cs#L16

Given this scenario, how would the CombineLatest Observable be in sync with the list? Since it only accepts IEnumerable<>, any modifications to the list after CombineLatest won’t be reflected, and running it again would create a new IObservable

neuecc commented 3 months ago

Converting to IEnumerable in the constructor and using CombineLatest doesn't handle dynamic changes for constructing ReadOnlyReactiveProperty. This is more of a collection issue rather than Rx or R3. You can certainly do it manually, but if you want something that handles everything automatically, DynamicData likely has a solution for that.