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
valueand 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 = lastVdoesn’t propagate to the Slidervalue, or a binding/update cycle overwrites it.Verify the Slider’s
valueimmediately after the second Cancel and confirm thatv → valuepropagation 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%')
}
}
Solution / Approach
Bind the Slider’s
valuewith 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
vto Elderly, then updatelastV = v. - On Cancel: set
v = lastV; two-way binding returns the Slider to the validated state on every cancel.
- On attempt to enable Elderly: open the confirmation dialog without changing
While the dialog is open, suppress Slider change handling to prevent UI→model race conditions.
Ensure discrete tiers via
min/max/stepso programmatic reverts always snap to valid values.
Slider({
value: $$this.v,
min: 0,
max: 3,
step: 1,
})
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.
Top comments (0)