5

Let say we have array of InvoiceDataModel

private let invoices Variable<[InvoiceDataModel]> = Variable([])

class InvoiceDataModel { 
    let id: Variable<Int>
    var entity: Variable<InvoiceDto>
    var isSelected: Variable<Bool> 
}

On tap on checkbox I am changing value of isSelected. What I want to achieve is to react on isSelect change to:

  • calculate total amount of selected items (each entity has var amount: Double)
  • detect if all items in collection are selected

Is it possible to observe whole array and react on single property from element change? Not sure how am I supposed to achieve this.

Probably my approach to this case is totally wrong. However I am not sure how am I supposed to operate here in a different way.

christopher.online
  • 2,614
  • 3
  • 28
  • 52
Piotr Gawłowski
  • 189
  • 1
  • 10

2 Answers2

2

The entity and isSelected variables need to be lets and not vars.

Here's the solution I came up with:

let selectedsAndAmounts = invoices
    .asObservable()
    .flatMapLatest {
        Observable.combineLatest($0.map {
            Observable.combineLatest($0.isSelected.asObservable(), Observable.just($0.amount)) { (isSelected: $0, amount: $1) }
        })
    }

let allSelected = selectedsAndAmounts
    .map { $0.map { $0.isSelected } }
    .map { $0.contains(false) == false }

let amountOfSelected = selectedsAndAmounts
    .map { $0.map { $0.isSelected ? $0.amount : 0 } }
    .map { $0.reduce(0, +) }

Most of the complexity in this solution (the selectedsAndAmounts observable) comes from having to unwrap the observables inside observables. It would be better if you could break this up a bit, or remove the Variables from inside the InvoiceDataModel.

Daniel T.
  • 32,821
  • 6
  • 50
  • 72
0

First off, note that Variable is deprecated in RxCocoa (use BehaviorRelay instead).

A brief solution would to combine the isSelected observables into a single, amount emitting observable. I just threw this snippet together, but it should point you in the right direction.

invoices
    // whenever the list changes, subscribe to the combined observable (and dispose of any previous subscriptions)
    .flatMapLatest { list in
        // merge all isSelected observables together
        return Observable.merge(list.map { $0.isSelected })
    }
    // when an element is emitted, that means some `isSelected` observable has changed its value
    // get the latest invoices array 
    .withLatestFrom(invoices)
    // convert it from a InvoiceDataModel array to a summed amount of all elements
    .map { $0.reduce(0) { $0 + $1.amount } }
    // log all events passing through
    .debug()
    .subscribe()
    .disposed(by: yourDisposeBag)

You can use a similar approach to subscribe to an Observable that emits if all items are selected. (Don't forget about .distinctUntilChanged(), otherwise you're going to see a lot of emitted elements.)

CloakedEddy
  • 1,965
  • 15
  • 27