5

How can I ensure that a subscriber to an Observable will receive the onNext event after another subscriber?

My example is the following:

let firstNameObservable = firstName.asObservable().shareReplay(1)
let lastNameObservable = lastName.asObservable().shareReplay(1)
let bioObservable = bio.asObservable().shareReplay(1)
let websiteObservable = website.asObservable().shareReplay(1)

firstNameObservable.subscribe(onNext: { [unowned self] firstName in self.accountDetails.firstName = firstName }).disposed(by: disposeBag)
lastNameObservable.subscribe(onNext: { [unowned self] lastName in self.accountDetails.lastName = lastName }).disposed(by: disposeBag)
bioObservable.subscribe(onNext: { [unowned self] bio in self.accountDetails.bio = bio }).disposed(by: disposeBag)
websiteObservable.subscribe(onNext: { [unowned self] website in self.accountDetails.website = website }).disposed(by: disposeBag)

Observable.combineLatest(firstNameObservable, lastNameObservable, bioObservable, websiteObservable)
    { [unowned self] _, _, _, _ in return self.accountDetails.validForSignUp }
    .bind(to: isValid)
    .disposed(by: disposeBag)

I would like for that last combineLatest binding to trigger after any of the 4 subscriptions above it have already executed. This is because it is only after the properties on accountDetails have been set that the validForSignUp will be accurate.

I am aware that a solution to this problem would be to make validForSignUp a Variable and observe it, but let's assume that is not possible.

Infinity James
  • 4,667
  • 5
  • 23
  • 36

2 Answers2

2

I would do something more like this:

let accountDetails = AccountDetails(firstName: firstName.orEmpty.asObserable(), lastName: lastName.orEmpty.asObservable(), bio: bio.orEmpty.asObservable(), website: website.orEmpty.asObservable())

accountDetails.validForSignup
    .bind(to: isValid)
    .disposed(by: bag)

struct AccountDetails {

    let validForSignup: Observable<Bool>

    init(firstName: Observable<String>, lastName: Observable<String>, bio: Observable<String>, website: Observable<String>) {

        let firstNameValid = firstName.map { $0.isValidFirstName }
        let lastNameValid = lastName.map { $0.isValidLastName }
        let bioValid = bio.map { $0.isValidBio }
        let websiteValid = website.map { $0.isValidWebsite }

        validForSignup = Observable.combineLatest([firstNameValid, lastNameValid, bioValid, websiteValid]) { $0.allTrue() }
    }
}

extension String {
    var isValidFirstName: Bool {
        return !isEmpty // put approprate code here
    }

    var isValidLastName: Bool {
        return !isEmpty // put approprate code here
    }

    var isValidBio: Bool {
        return !isEmpty // put approprate code here
    }

    var isValidWebsite: Bool {
        return !isEmpty // put approprate code here
    }

}

extension Sequence where Iterator.Element == Bool {

    func allTrue() -> Bool {
        return !contains(false)
    }
}
Daniel T.
  • 32,821
  • 6
  • 50
  • 72
1

You can handle your data validation with a single subscribe. Here's how it can be done where I'll bind the combined events to an Observable that performs validation of the combined results.

let fields = [firstNameObservable, 
              lastNameObservable, 
              bioObservable, 
              websiteObservable]
let validFields = Observable.combineLatest(fields).bind(to: validateFields)

The Observable that will deliver the validation of the combined results could look something like the following function.

func validateFields<T>(allFields: Observable<[T]>) -> Observable<Bool>
{
    return allFields.map { fieldData in
        var result: Bool = false

        /* Is the data valid? */

        return result;
    }
}

The Observable validFields will be updated with a validation result every time there is new data from any of the field Observables. With combineLatest, you have the added benefit that the order of your source Observables is maintained. This solution is accomplished without resorting to storage of state making it completely reactive.

Daniel Zhang
  • 5,778
  • 2
  • 23
  • 28
  • My aim was actually to ensure the execution of the validation following the setting of the `AccountDetails` properties. Whilst your answer is interesting, it doesn't necessarily give me a solution my specific problem. – Infinity James May 27 '17 at 10:25