Search code examples
swiftrx-swift

What is the proper way to update values of two Behavior Relays when one is subscribed to other?


I have two BehaviorRelay-s, one is binded to second one. What is the proper way to update values of both inside one function? For example:

class SetupProfileVM {
   let disposeBag = DisposeBag()
   let name= BehaviorRelay<String?>(value: nil)
   let username = BehaviorRelay<String?>(value: nil)

   name.bind(to: username).dispose(by: disposeBag)

   func setNewValues(name: String, username: String) {
       name.accept(name)
       username.accept(username)
   }
}

In this case i want username to be same as user when value is updated by textfield. But i also need this function to set new values to both name & username. What is the proper way of doing that? Can i be sure that username will be updated firstly by it's binding and only after that by it's new value?


Solution

  • I'm not sure why you even want to use BahaviorRelays here. For the behavior you are describing, I would expect to see something like this:

    struct SetupProfileInputs {
        let nameField: Observable<String>
        let newValues: Observable<(name: String, username: String)>
    }
    
    struct SetupProfileVM {
    
        let name: Observable<String>
        let username: Observable<String>
    
        init(_ inputs: SetupProfileInputs) {
            name = Observable.merge(inputs.nameField, inputs.newValues.map { $0.name })
            username = Observable.merge(inputs.nameField, inputs.newValues.map { $0.username })
        }
    }
    

    Or possibly this (depending on what style of view model you are using.)

    struct SetupProfileVM {
    
        struct Inputs {
            let nameField: Observable<String>
            let newValues: Observable<(name: String, username: String)>
        }
    
        struct Outputs {
            let name: Observable<String>
            let username: Observable<String>
        }
    
        func transform(_ inputs: Inputs) -> Outputs {
            let name = Observable.merge(inputs.nameField, inputs.newValues.map { $0.name })
            let username = Observable.merge(inputs.nameField, inputs.newValues.map { $0.username })
            return Outputs(name: name, username: username)
        }
    }
    

    In either case, you attach the text field to the nameField input. Whatever was calling the SetupProfileVM.setNewValues(name:username:) method needs to be converted to an Observable and connected to the newValues input.

    Once you do that, every time the text field is updated, both SetupProfileVM.name and SetupProfileVM.username will emit the same value. Every time the newValues Observable emits a value, name and username will emit different values.