DEV Community

HarmonyOS
HarmonyOS

Posted on

Bidirectional binding problem

Read the original article:Bidirectional binding problem

Context

  • HarmonyOS font-size setting uses a Slider (standard / large / extra-large) and an Elderly version that requires a confirmation dialog.
  • On Confirm, switch to Elderly; on Cancel, revert to the last confirmed font size.
  • UI uses two-way data binding between the Slider value and the backing variable.

Description

  • Bug: The first time the user selects Elderly and taps Cancel, the Slider correctly reverts. On a second attempt (select Elderly → Cancel), the Slider does not revert to the previous size.

  • Likely causes: after Cancel, setting v = lastV doesn’t propagate to the Slider value, or a binding/update cycle overwrites it.

  • Verify the Slider’s value immediately after the second Cancel and confirm that v → value propagation occurs.

  • Here is problematic code:

  @Entry
  @Component
  struct Index {
    @State v: number = 0;
    lastV: number = 0;

    build() {
      Column() {
        Slider({
          value: this.v,
          min: 0,
          max: 3,
          step: 1,
        })
          .width('100%')
          .showSteps(true)
          .stepSize(40)
          .onChange((value: number, mode: SliderChangeMode) => {
            if (mode === SliderChangeMode.End) {
              if (this.lastV === 3 && value < 3) {
                this.getUIContext().showAlertDialog({
                  title: 'Prompt',
                  message: 'Disable the senior version?',
                  primaryButton: {
                    value: 'Confirm',
                    action: () => {
                      this.lastV = value;
                    }
                  },
                  secondaryButton: {
                    value: 'Cancel',
  // If the user clicks cancel, it needs to revert to the previously set version.
                    action: () => {
                      this.v = this.lastV;
                    }
                  }
                })
              }
  // When a user previously set up a non-senior version and now wants to switch to the senior version, a pop-up prompt should appear asking whether to enable the senior version.
              else if (this.lastV < 3 && value === 3) {
                this.getUIContext().showAlertDialog({
                  title: 'Prompt',
                  message: 'Activate the Elderly Version?',
                  primaryButton: {
                    value: 'Confirm',
                    action: () => {
                      this.lastV = value;
                    }
                  },
                  secondaryButton: {
                    value: 'Cancel',
                    action: () => {
                      this.v = this.lastV;
                    }
                  }
                })
              } else {
                this.lastV = value;
              }
            }

          })
        Flex({ justifyContent: FlexAlign.SpaceBetween }) {
          Text('Standard').fontSize(20)
          Text('Large').fontSize(30)
          Text('Extra Large').fontSize(40)
          Text('Elder').fontSize(50)
        }.margin({ top: 10 })
      }
      .justifyContent(FlexAlign.Center)
      .height('100%')
      .width('100%')
    }
  }
Enter fullscreen mode Exit fullscreen mode

Solution / Approach

  • Bind the Slider’s value with two-way binding to the state variable ($$this.v) so UI and state remain synchronized.

  • Keep a single source of truth: v = current selection; lastV = last confirmed selection.

  • Flow control:

    • On attempt to enable Elderly: open the confirmation dialog without changing lastV.
    • On Confirm: set v to Elderly, then update lastV = v.
    • On Cancel: set v = lastV; two-way binding returns the Slider to the validated state on every cancel.
  • While the dialog is open, suppress Slider change handling to prevent UI→model race conditions.

  • Ensure discrete tiers via min/max/step so programmatic reverts always snap to valid values.

  Slider({
    value: $$this.v,
    min: 0,
    max: 3,
    step: 1,
  })
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Two-way binding ($$this.v) eliminates desynchronization between Slider position and state.
  • Cancel operations revert to the last confirmed setting; behavior remains idempotent across repeated cancels.
  • A single source of truth prevents drift from direct UI property mutations.
  • Dialog gating avoids stale or competing updates during confirmation.
  • Properly constrained Slider steps ensure reliable, repeatable reversion.

Written by Fatih Turan Gundogdu

Top comments (0)