antoninbiret / RxEureka

This library is a small RxSwift wrapper around Eureka
MIT License
37 stars 9 forks source link

Default values don't get set correctly when binding order changes. #18

Closed sedwo closed 6 years ago

sedwo commented 6 years ago

It's not clear to me why this happens.

When you init:

class ViewController: FormViewController {
  let viewModel: ViewModeling = ViewModel(model: Model(text: "RxEureka", phone: "0000"))
...

within private func _configureBindings() { when the order of binding sets up the Variable first and then the row:

    self.model.text
      .asObservable()
      .bind(to: self._textRow.rx.value)
      .disposed(by: self._disposeBag)

    self._textRow.rx.value
      .asObservable()
      .bind(to: self.model.text)
      .disposed(by: self._disposeBag)

The form field correctly displays the default value: image

But when you swap the order, by binding the row first and then the Variable:

    self._textRow.rx.value
        .asObservable()
        .bind(to: self.model.text)
        .disposed(by: self._disposeBag)

    self.model.text
      .asObservable()
      .bind(to: self._textRow.rx.value)
      .disposed(by: self._disposeBag)

The form field is missing its default value: image

antoninbiret commented 6 years ago

Hi @sedwo.

This is a normal behaviour. In the second example (the one where the row stays empty), before binding the model with the row, the value of self._textRow.rx.value is nil. So when the first binding is setup, this nil value propagated to the self.model.text (which was "RxEureka" before). Then, the second binding just throws back the nil value of model's text property to the TextRow value.

In order to avoid this :

sedwo commented 6 years ago

I see. But the init() method already executed setting the string value.

Is there a way to force the binding to refresh itself, allowing the already set value to propagate again? (without setting a new value, again)

sedwo commented 6 years ago

I noticed in your example that you use .bind(to: as well as .subscribe( for the same variables. Which I've followed as a template but now wonder if .bind(to: is really necessary when the following still works?

    private func configureBindingsWithViewModel() {
        // Bind viewModel Variable to--> 'email' row
//        self.viewModel.usernameText
//            .asObservable()
//            .bind(to: self.emailRow!.rx.value)
//            .disposed(by: self.disposeBag)

        // Update UI row value when data changes within viewModel.
        self.viewModel.usernameText
            .asObservable()
            .subscribe(onNext: { value in
                self.emailRow?.value = value
                self.emailRow?.updateCell() // refresh UI control
            }, onError: { error in
                DDLogError("RxError = \(error)")
            }, onDisposed: {
                DDLogError("onDisposed")
            }).disposed(by: self.disposeBag)

The reason for .subscribe( being necessary is that Eureka won't refresh the UI row for you. You must call .updateCell(). Is it safe to remove the .bind(to: step then, as it doesn't seem to make any difference here.

antoninbiret commented 6 years ago

If you just need to synchronise your values between your Eureka row and your RxSwift Variable in your viewModel, just use the 2 ways binding pattern.

In your exemple this could be :

self.viewModel.usernameText
      .asObservable()
      .bind(to: self.emailRow.rx.value)
      .disposed(by: self.disposeBag)

self.emailRow.rx.value
      .asObservable()
      .bind(to: self.viewModel.usernameText)
      .disposed(by: self.disposeBag)

In the project sample I subscribe to the viewModel's variables just to have a log feedback of the user interaction within the Eureka rows.

RxEureka update the cell for you, so you don't have to subscribe to your viewModel changes and refresh manually the corresponding row.

I hope i have been clear enough.

sedwo commented 6 years ago

Got it. Thank you.