RxSwift Reverse observable aka two way binding
When we hear Reactive programming we usually think about listeners of Observable sequences, transformers and combining data, and reacting on changes.
So.. RxSwift is about passing data from the business logic to views, right? but how about passing events in both directions
TextField <------> Observable <------> TextField
We’ll look at the following two use cases:
- binding 2 textfields and subscribing to each other’s
text
control property (when change the text in one of them, the other automatically updates) - go next level and make first/last/full name form that updates like on the picture above
Let’s get started!
Existing libraries and approaches
Before starting out and coding I sometimes like to check if I did’n reinvent the hot water — do we have some existing libraries or something else done related to the topic.
RxBiBinding
And… I found this library
that does excellent job. I just have to to connect two textfields like so
and it will listen for changes in the textfields, and update textfields texts in both directions.
And it is good enough for the most simple use case - sending text between textFieldFirst
and textFieldSecond
and back as it is. This library does not provide a way to map and modify the passed sequence of Strings
.
And in the real world we don’t pass observable sequences as it is, we often map/transform it (for example: if I would like to pass only numbers and filter out letters…)
RxSwift
main repository examples folder.
The next approach I found was in the examples folder of RxSwift https://github.com/ReactiveX/RxSwift/blob/master/RxExample/RxExample/Operators.swift#L17
It binds the BehaviourRelay
property and ControlPropery
to each other, and it sends updates in both directions to properties as expected.
I was worried that it will cause loop (because of binding properties to each other)(relay send events to control property, and control property send the same to subscribed relay, then relay send to control property same event ….forever), but it appears that ControlProperty
have built-in mechanism that stops event to not be emitted twice
-> BehaviourRelay -> ConrolProperty ----> X -----> BehaviourRelay
How does it work
When send send event on BehaviourRelay
, ControlPropery
updates because of the binding
When we send event on ControlPropery
, BehaviourRelay
updates because of the subscription
// value flow
trigger the state of control (e.g. `UITextField`) ->
ControlProperty emits event ->
value assigned to BehaviourRelay ->
BehaviourRelay emits event ->
ControlProperty receives event ->
value assigned to underlying control property (e.g. `text` for `UITextField`)
So a simple why there is no loop:
- a value from a control is emitted once some kind of
UIControlEvent
is triggered - when a value is assigned directly to the control property, the control doesn’t trigger a change event so there’s no loop.
This approach satisfies our needs and we could modify text before subscribing the BehaviourRelay
property.
Our Example
However the previous example won’t work well if we bind to each other two BehaviourRelay
properties— it will cause event loop
-> textFirst BehaviourRelay -> textSecond BehaviourRelay -> textFirst BehaviourRelay -> textSecond BehaviourRelay -> textFirst BehaviourRelay -> textSecond BehaviourRelay .....
Apparently if BehaviourRelay
does not have built-in way to stop passing same event to it’s subscribers over and over so we are going to build that mechanism.
Previous Observable value
I did this little convenience helper to get previous sequence values of Observable
I need this because I need to tell which Observable
(TextField text
) was changed
The example
I have two textfields and I’ll have to get the value from changed field (with old value != current) and update textfield that is unchanged (with old value == current)
And If I don’t want to cause forever loop I’ll have to check that the current values of the fields are equal, to stop propagating evens (filter
RxSwift operator)
and then use it like that
More complex Example
This is the full code for two way binding between first and last and full name text fields (like on the animated gif on top)
When we enter text in textFirst
and textSecond
the lastname field (textFull
) is updated with concatenated first and last name texts.
Link to the Example repository https://github.com/vaderdan/Example2WayBinding