We use essential cookies for the website to function, as well as analytics cookies for analyzing and creating statistics of the website performance. To agree to the use of analytics cookies, click "Accept All". You can manage your preferences at any time by clicking "Cookie Settings" on the footer. More Information.

Only Essential Cookies
Accept All
GuidesApplication FrameworkArkUIUI Development (ArkTS-based Declarative Development Paradigm)Basic Syntax of UI ParadigmsComponent Extension@Builder Decorator: Custom Builder Function

@Builder Decorator: Custom Builder Function

ArkUI provides a lightweight UI element reuse mechanism @Builder. The internal UI structure of ArkUI is fixed and only data is transferred with the user. Developers can abstract UI elements that are repeatedly used as functions and call them in the build function.

Functions decorated by @Builder are also called custom build functions.

Before reading this topic, you are advised to read Basic Syntax Overview, Declarative UI Description, and Creating a Custom Component.

The differences between the @Builder decorator and @Component decorator in functions and usage are as follows:

  1. The @Builder decorator is used to encapsulate reusable UI structures and extract repeated layout code to improve development efficiency. It is strictly prohibited to define state variable or use custom-component-lifecycle in the decorator. Data interaction must be completed through parameter transfer or access to the state variable of the component to which the decorator belongs.

  2. In the ArkUI framework, the @Component decorator is the core mechanism for encapsulating complex UI components. It allows developers to combine multiple basic components to build a reusable composite UI. This decorator not only supports the definition of internal state variables, but also manages the lifecycle of components.

NOTE

The initial APIs of this module are supported since API version 7.

This decorator can be used in ArkTS widgets since API version 9.

This decorator can be used in atomic services since API version 11.

How to Use

The @Builder decorator can be used in two modes: as a private custom builder function within a custom component, or as a global custom builder function defined at global scope.

Private Custom Builder Function

The following is an example:

Collapse
Word wrap
Dark theme
Copy code
  1. @Entry
  2. @Component
  3. struct BuilderDemo {
  4. @Builder
  5. showTextBuilder() {
  6. // @Builder decorates this function so that the Text component can be configured and built in chain mode.
  7. Text('Hello World')
  8. .fontSize(30)
  9. .fontWeight(FontWeight.Bold)
  10. }
  11. @Builder
  12. showTextValueBuilder(param: string) {
  13. Text(param)
  14. .fontSize(30)
  15. .fontWeight(FontWeight.Bold)
  16. }
  17. build() {
  18. Column() {
  19. // Without parameter.
  20. this.showTextBuilder()
  21. // With a parameter.
  22. this.showTextValueBuilder('Hello @Builder')
  23. }
  24. }
  25. }

Invocation pattern:

  • One or more @Builder functions can be defined in a custom component. These functions are treated as private and special member functions of the owning component.

  • Private custom builder functions can be called in custom components, build(), and other custom builder functions.

  • In a custom component, this indicates the component to which the component belongs. The status variables of the component can be accessed in the custom builder functions. It is recommended that this be used to access the status variable of the component instead of being transferred through parameters.

Global Custom Builder Function

The following is an example:

Collapse
Word wrap
Dark theme
Copy code
  1. // Global custom builder function showTextBuilder.
  2. @Builder
  3. function showTextBuilder() {
  4. Text('Hello World')
  5. .fontSize(30)
  6. .fontWeight(FontWeight.Bold)
  7. }
  8. @Entry
  9. @Component
  10. struct BuilderSample {
  11. build() {
  12. Column() {
  13. showTextBuilder()
  14. }
  15. }
  16. }
  • If the component status variable does not change, you are advised to use the global custom build function.

  • Global custom build functions can be called in the build function and other custom build functions.

Parameter Passing Rules

Parameters for custom builder functions can be passed by callback, by reference, or by value. The following rules must be followed:

  • The parameter type of the function decorated by @Builder cannot be undefined, null, or an expression that returns undefined or null.

  • All parameters must be immutable inside the custom builder function decorated by @Builder.

  • The @Builder function body follows the same syntax rules as the build() function.

  • The UI components in the @Builder function can be updated during callback-based transfer and reference-based transfer. Passing by reference takes effect only when one parameter is passed and the parameter is directly passed to the object literal. If there are multiple parameters, the UI component in the @Builder function cannot be refreshed.

  • When a reference is passed, the attribute of the parameter cannot be modified in the @Builder function. However, when UIUtils.makeBinding is used and the write callback is passed, the attribute can be modified in the @Builder function and synchronized to the component that calls the @Builder function.

Passing Parameters by Callback

From API version 20, you can use the UIUtils.makeBinding() function, the Binding class, and the MutableBinding class to refresh state variables in the @Builder function. For details, see State Variables Can Be Refreshed in the @Builder.

Use UIUtils.makeBinding() to wrap the callback function for reading status variables and transfer the callback function as a parameter to the @Builder function. The UI component in the @Builder function can be refreshed. The callback function of the write status variable transferred in UIUtils.makeBinding() can transfer the parameter changes in @Builder to the component that calls the @Builder function.

Collapse
Word wrap
Dark theme
Copy code
  1. import { Binding, MutableBinding, UIUtils } from '@kit.ArkUI';
  2. @Builder
  3. function customButton(num1: Binding<number>, num2: MutableBinding<number>) {
  4. Row() {
  5. Column() {
  6. Text(`number1: ${num1.value}, number2: ${num2.value}`)
  7. Button(`only change number2`)
  8. .onClick(() => {
  9. // Assign a value of the MutableBinding type and transfer the modification to the parent component.
  10. num2.value += 1;
  11. })
  12. }
  13. }
  14. }
  15. @Entry
  16. @ComponentV2
  17. struct ParameterMakeBinding {
  18. @Local number1: number = 5;
  19. @Local number2: number = 12;
  20. build() {
  21. Column() {
  22. customButton(
  23. // Use makeBinding to pass parameters. The read callback needs to be passed. The Binding type is returned. The UI of the component in @Builder can be refreshed.
  24. UIUtils.makeBinding<number>(() => this.number1),
  25. // The MutableBinding type is returned when the write callback is passed to makeBinding. The UI of the component in @Builder can be refreshed and the attribute modification can be synchronized.
  26. UIUtils.makeBinding<number>(
  27. () => this.number2,
  28. (val: number) => {
  29. this.number2 = val;
  30. })
  31. )
  32. }
  33. }
  34. }

By-Reference Parameter Passing

In by-reference parameter passing, state variables can be passed, and the change of these state variables causes the UI re-rendering in the @Builder function.

Collapse
Word wrap
Dark theme
Copy code
  1. class Tmp {
  2. public paramA1: string = '';
  3. }
  4. @Builder
  5. function overBuilderByReference(params: Tmp) {
  6. Row() {
  7. Text(`UseStateVarByReference: ${params.paramA1} `)
  8. }
  9. }
  10. @Entry
  11. @Component
  12. struct ParameterReference {
  13. @State label: string = 'Hello';
  14. build() {
  15. Column() {
  16. // When the overBuilderByReference component is called in the parent component:
  17. // Transfer this.label to the overBuilderByReference component by reference.
  18. overBuilderByReference({ paramA1: this.label })
  19. Button('Click me').onClick(() => {
  20. // After you click "Click me", the UI text changes from "Hello" to "ArkUI".
  21. this.label = 'ArkUI';
  22. })
  23. }
  24. }
  25. }

By-Value Parameter Passing

By default, parameters in the @Builder decorated functions are passed by value. If the transferred parameter is a status variable, the change of the status variable does not cause the UI update in the @Builder function. Therefore, when using state variables, you are advised to use Passing Parameters By Callback or Passing Parameters By Reference.

Collapse
Word wrap
Dark theme
Copy code
  1. @Builder
  2. function overBuilderByValue(paramA1: string) {
  3. Row() {
  4. Text(`UseStateVarByValue: ${paramA1} `)
  5. }
  6. }
  7. @Entry
  8. @Component
  9. struct ParameterValue {
  10. @State label: string = 'Hello';
  11. build() {
  12. Column() {
  13. // Pass parameters by value. Changes to state variables will not trigger UI refresh inside overBuilderByValue.
  14. overBuilderByValue(this.label)
  15. }
  16. }
  17. }

Constraints

  1. @If MutableBinding is not used in a function decorated by Builder, the parameter value cannot be modified. The modification does not trigger UI update. If passing parameters by reference and only one parameter is passed, modifying the internal attributes of the parameter will throw a runtime error. You can use MutableBinding to modify parameter values in the function decorated by @Builder. For details, see Changing the Input Parameters in the @Builder Decorated Function.

  2. When an @Builder passes a parameter by reference, dynamic UI rendering can be triggered. Refer to By-Reference Parameter Passing.

  3. If an @Builder receives two or more parameters and does not pass parameters by callback, dynamic UI rendering will not be triggered. For details, see Multiple Parameters in @Builder.

  4. If the parameters passed to @Builder contain both value passing and reference passing, dynamic UI rendering will not be triggered. For details, see Multiple Parameters in @Builder.

  5. Modifying properties of a parameter inside an @Builder function is not allowed; doing so will cause a runtime error. Starting from API version 23, error code 140109 will be returned. For an example, see Changing the Input Parameters in the @Builder Decorated Function.

Use Cases

Using Custom Builder Function in Custom Component

Create a private @Builder function and use this.builder() to call the function in Column. Update builderValue through the aboutToAppear lifecycle function and button click event, implementing dynamic UI rendering.

Collapse
Word wrap
Dark theme
Copy code
  1. @Entry
  2. @Component
  3. struct PrivateBuilder {
  4. @State builderValue: string = 'Hello';
  5. @Builder
  6. builder() {
  7. Column() {
  8. Text(this.builderValue)
  9. .width(230)
  10. .height(40)
  11. .backgroundColor('#ffeae5e5')
  12. .borderRadius(20)
  13. .margin(12)
  14. .textAlign(TextAlign.Center)
  15. }
  16. }
  17. aboutToAppear(): void {
  18. setTimeout(() => {
  19. this.builderValue = 'Hello World';
  20. }, 2000);
  21. }
  22. build() {
  23. Row() {
  24. Column() {
  25. Text(this.builderValue)
  26. .width(230)
  27. .height(40)
  28. .backgroundColor('#ffeae5e5')
  29. .borderRadius(20)
  30. .textAlign(TextAlign.Center)
  31. this.builder()
  32. // Click the button to update builderValue to update the text display.
  33. Button('Click to change the builderValue')
  34. .onClick(() => {
  35. this.builderValue = 'builderValue was clicked';
  36. })
  37. }
  38. .height('100%')
  39. .width('100%')
  40. }
  41. }
  42. }

Effect

Global Custom Builder Function

Create a global @Builder function and call it in overBuilder() mode in Column. When transferring parameters, you can use the object literal form. Any change of the value will trigger the refresh of the UI, regardless of the simple or complex type.

Collapse
Word wrap
Dark theme
Copy code
  1. class ChildTmp {
  2. public val: number = 1;
  3. }
  4. class ParamTmp {
  5. public strValue: string = 'Hello';
  6. public numValue: number = 0;
  7. public tmpValue: ChildTmp = new ChildTmp();
  8. public arrayTmpValue: Array<ChildTmp> = [];
  9. }
  10. @Builder
  11. function overBuilder(param: ParamTmp) {
  12. Column() {
  13. Text(`strValue: ${param.strValue}`)
  14. .width(230)
  15. .height(40)
  16. .margin(12)
  17. .backgroundColor('#0d000000')
  18. .fontColor('#e6000000')
  19. .borderRadius(20)
  20. .textAlign(TextAlign.Center)
  21. Text(`numValue: ${param.numValue}`)
  22. .width(230)
  23. .height(40)
  24. .margin(12)
  25. .backgroundColor('#0d000000')
  26. .fontColor('#e6000000')
  27. .borderRadius(20)
  28. .textAlign(TextAlign.Center)
  29. Text(`tmpValue: ${param.tmpValue.val}`)
  30. .width(230)
  31. .height(40)
  32. .margin(12)
  33. .backgroundColor('#0d000000')
  34. .fontColor('#e6000000')
  35. .borderRadius(20)
  36. .textAlign(TextAlign.Center)
  37. ForEach(param.arrayTmpValue, (item: ChildTmp) => {
  38. ListItem() {
  39. Text(`arrayTmpValue: ${item.val}`)
  40. .width(230)
  41. .height(40)
  42. .margin(12)
  43. .backgroundColor('#0d000000')
  44. .fontColor('#e6000000')
  45. .borderRadius(20)
  46. .textAlign(TextAlign.Center)
  47. }
  48. }, (item: ChildTmp) => JSON.stringify(item))
  49. }
  50. }
  51. @Entry
  52. @Component
  53. struct ParentDemo {
  54. @State objParam: ParamTmp = new ParamTmp();
  55. build() {
  56. Column() {
  57. Text('UI Rendered via @Builder')
  58. .fontSize(20)
  59. .margin(12)
  60. // Call the global @Builder function overBuilder.
  61. overBuilder({
  62. strValue: this.objParam.strValue,
  63. numValue: this.objParam.numValue,
  64. tmpValue: this.objParam.tmpValue,
  65. arrayTmpValue: this.objParam.arrayTmpValue
  66. })
  67. // Click the button to update objParam and trigger the refresh of components in overBuilder.
  68. Button('Update Values').onClick(() => {
  69. this.objParam.strValue = 'Hello World';
  70. this.objParam.numValue = 1;
  71. this.objParam.tmpValue.val = 8;
  72. const childValue: ChildTmp = {
  73. val: 2
  74. }
  75. this.objParam.arrayTmpValue.push(childValue);
  76. })
  77. }
  78. .height('100%')
  79. .width('100%')
  80. }
  81. }

Effect

Changing the Variables Decorated by the Decorator Triggers UI Re-rendering

In this scenario, @Builder defines the Text component layout but does not handle dynamic UI updates. UI re-rendering occurs when decorator-observed values change, not through @Builder's reactive capabilities.

Collapse
Word wrap
Dark theme
Copy code
  1. class ChildrenTmp {
  2. public strValue: string = 'Hello';
  3. }
  4. @Entry
  5. @Component
  6. struct ParentSample {
  7. @State objParam: ChildrenTmp = new ChildrenTmp();
  8. @State label: string = 'World';
  9. @Builder
  10. privateBuilder() {
  11. Column() {
  12. Text(`wrapBuilder strValue: ${this.objParam.strValue}`)
  13. .width(350)
  14. .height(40)
  15. .margin(12)
  16. .backgroundColor('#0d000000')
  17. .fontColor('#e6000000')
  18. .borderRadius(20)
  19. .textAlign(TextAlign.Center)
  20. Text(`wrapBuilder num: ${this.label}`)
  21. .width(350)
  22. .height(40)
  23. .margin(12)
  24. .backgroundColor('#0d000000')
  25. .fontColor('#e6000000')
  26. .borderRadius(20)
  27. .textAlign(TextAlign.Center)
  28. }
  29. }
  30. build() {
  31. Column() {
  32. Text('UI Rendered via @Builder')
  33. .fontSize(20)
  34. this.privateBuilder()
  35. // Click the button to update label and trigger the refresh of Text components.
  36. Button('Update Values').onClick(() => {
  37. this.objParam.strValue = 'strValue Hello World';
  38. this.label = 'label Hello World';
  39. })
  40. }
  41. .height('100%')
  42. .width('100%')
  43. }
  44. }

Effect

Using Functions Decorated with @Builder as CustomBuilder Types

When the parameter type is CustomBuilder, the defined @Builder function can be passed. CustomBuilder is actually of the Function(() => any) or void type, and @Builder is also of the Function type. Therefore, a specific effect can be implemented by transferring @Builder.

When the global @Builder function is transferred as the CustomBuilder type, the this context needs to be bound. You can directly call the global @Builder function. The compilation tool chain automatically generates the code bound to the this context.

Collapse
Word wrap
Dark theme
Copy code
  1. @Builder
  2. function overBuilderDemo() {
  3. Row() {
  4. Text('Global Builder')
  5. .fontSize(30)
  6. .fontWeight(FontWeight.Bold)
  7. }
  8. }
  9. @Entry
  10. @Component
  11. struct customBuilderDemo {
  12. @State arr: number[] = [0, 1, 2, 3, 4];
  13. @Builder
  14. privateBuilder() {
  15. Row() {
  16. Text('Private Builder')
  17. .fontSize(30)
  18. .fontWeight(FontWeight.Bold)
  19. }
  20. }
  21. build() {
  22. Column() {
  23. List({ space: 10 }) {
  24. ForEach(this.arr, (item: number) => {
  25. ListItem() {
  26. Text(`${item}`)
  27. .width('100%')
  28. .height(100)
  29. .fontSize(16)
  30. .textAlign(TextAlign.Center)
  31. .borderRadius(10)
  32. .backgroundColor(0xFFFFFF)
  33. }
  34. .swipeAction({
  35. start: {
  36. builder: overBuilderDemo() // The compilation toolchain automatically binds this context.
  37. },
  38. end: {
  39. builder: () => {
  40. // When the local @Builder is called in the arrow function, the this context is automatically bound and does not need to be processed by the compilation toolchain.
  41. this.privateBuilder()
  42. }
  43. }
  44. })
  45. }, (item: number) => JSON.stringify(item))
  46. }
  47. }
  48. }
  49. }

Effect

Multi-Layer @Builder Function Nesting

Invoke customized components or other @Builder functions in the @Builder function to implement nested use of multiple @Builders. To implement the dynamic UI refresh function of the @Builder at the innermost layer, the @Builder must be called by reference at each layer. Here, $$ is not a mandatory parameter. You can replace it with another name.

Collapse
Word wrap
Dark theme
Copy code
  1. class ThisTmp {
  2. public paramA1: string = '';
  3. }
  4. @Builder
  5. function parentBuilder($$: ThisTmp) {
  6. Row() {
  7. Column() {
  8. Text(`parentBuilder===${$$.paramA1}`)
  9. .width(300)
  10. .height(40)
  11. .margin(10)
  12. .backgroundColor('#0d000000')
  13. .fontColor('#e6000000')
  14. .borderRadius(20)
  15. .textAlign(TextAlign.Center)
  16. // Call the custom component HelloComponent.
  17. HelloComponent({ message: $$.paramA1 })
  18. // Call the global @Builder function childBuilder.
  19. childBuilder({ paramA1: $$.paramA1 })
  20. }
  21. }
  22. }
  23. @Component
  24. struct HelloComponent {
  25. @Prop message: string = '';
  26. build() {
  27. Row() {
  28. Text(`HelloComponent===${this.message}`)
  29. .width(300)
  30. .height(40)
  31. .margin(10)
  32. .backgroundColor('#0d000000')
  33. .fontColor('#e6000000')
  34. .borderRadius(20)
  35. .textAlign(TextAlign.Center)
  36. }
  37. }
  38. }
  39. @Builder
  40. function childBuilder($$: ThisTmp) {
  41. Row() {
  42. Column() {
  43. Text(`childBuilder===${$$.paramA1}`)
  44. .width(300)
  45. .height(40)
  46. .margin(10)
  47. .backgroundColor('#0d000000')
  48. .fontColor('#e6000000')
  49. .borderRadius(20)
  50. .textAlign(TextAlign.Center)
  51. // Call the custom component HelloChildComponent.
  52. HelloChildComponent({ message: $$.paramA1 })
  53. // Call the global @Builder function grandsonBuilder.
  54. grandsonBuilder({ paramA1: $$.paramA1 })
  55. }
  56. }
  57. }
  58. @Component
  59. struct HelloChildComponent {
  60. @Prop message: string = '';
  61. build() {
  62. Row() {
  63. Text(`HelloChildComponent===${this.message}`)
  64. .width(300)
  65. .height(40)
  66. .margin(10)
  67. .backgroundColor('#0d000000')
  68. .fontColor('#e6000000')
  69. .borderRadius(20)
  70. .textAlign(TextAlign.Center)
  71. }
  72. }
  73. }
  74. @Builder
  75. function grandsonBuilder($$: ThisTmp) {
  76. Row() {
  77. Column() {
  78. Text(`grandsonBuilder===${$$.paramA1}`)
  79. .width(300)
  80. .height(40)
  81. .margin(10)
  82. .backgroundColor('#0d000000')
  83. .fontColor('#e6000000')
  84. .borderRadius(20)
  85. .textAlign(TextAlign.Center)
  86. // Call the custom component HelloGrandsonComponent.
  87. HelloGrandsonComponent({ message: $$.paramA1 })
  88. }
  89. }
  90. }
  91. @Component
  92. struct HelloGrandsonComponent {
  93. @Prop message: string;
  94. build() {
  95. Row() {
  96. Text(`HelloGrandsonComponent===${this.message}`)
  97. .width(300)
  98. .height(40)
  99. .margin(10)
  100. .backgroundColor('#0d000000')
  101. .fontColor('#e6000000')
  102. .borderRadius(20)
  103. .textAlign(TextAlign.Center)
  104. }
  105. }
  106. }
  107. @Entry
  108. @Component
  109. struct ParentExample {
  110. @State label: string = 'Hello';
  111. build() {
  112. Column() {
  113. // Call the global @Builder function parentBuilder.
  114. parentBuilder({ paramA1: this.label })
  115. Button('Click me').onClick(() => {
  116. this.label = 'ArkUI';
  117. })
  118. }
  119. .height('100%')
  120. .width('100%')
  121. }
  122. }

Effect

@Builder Function Union V2 Decorator

Class object instances decorated by @ObservedV2 and @Trace have the capability of deeply observing attribute changes. In a custom component decorated by @ComponentV2, when the global builder or local builder is called and parameters are transferred in value transfer mode, modifying the object attributes decorated by @Trace can trigger UI refresh.

Collapse
Word wrap
Dark theme
Copy code
  1. @ObservedV2
  2. class Info {
  3. @Trace public name: string;
  4. @Trace public age: number;
  5. constructor(name: string, age: number) {
  6. this.name = name;
  7. this.age = age;
  8. }
  9. }
  10. @Builder
  11. function overBuilderTest(param: Info) {
  12. Column() {
  13. Text(`Global@Builder name: ${param.name}`)
  14. Text(`Global@Builder age: ${param.age}`)
  15. }
  16. .width(230)
  17. .height(40)
  18. .margin(10)
  19. .padding({ left: 20 })
  20. .backgroundColor('#0d000000')
  21. .borderRadius(20)
  22. }
  23. @ComponentV2
  24. struct ChildPage {
  25. @Require @Param childInfo: Info;
  26. build() {
  27. Column() {
  28. // The value must be transferred. If the reference transfer mode is used, the request will be intercepted by the ArkTS syntax.
  29. overBuilderTest(this.childInfo)
  30. }
  31. }
  32. }
  33. @Entry
  34. @ComponentV2
  35. struct ParentPage {
  36. info1: Info = new Info('Tom', 25);
  37. info2: Info = new Info('Tom', 25);
  38. @Builder
  39. privateBuilder() {
  40. Column() {
  41. Text(`Private@Builder name: ${this.info1.name}`)
  42. Text(`Private@Builder age: ${this.info1.age}`)
  43. }
  44. .width(230)
  45. .height(40)
  46. .margin(10)
  47. .backgroundColor('#0d000000')
  48. .borderRadius(20)
  49. }
  50. build() {
  51. Column() {
  52. Flex() {
  53. Column() {
  54. Text(`info1: ${this.info1.name} ${this.info1.age}`) // Text1
  55. Text(`info2: ${this.info2.name} ${this.info2.age}`) // Text2
  56. }
  57. }
  58. .width(230)
  59. .height(40)
  60. .margin(10)
  61. .padding({ left: 60 })
  62. .backgroundColor('#0d000000')
  63. .borderRadius(20)
  64. // Call the local @Builder.
  65. this.privateBuilder()
  66. // Call the global @Builder. The value must be transferred. If the value is transferred by reference, it will be intercepted by the ArkTS syntax.
  67. overBuilderTest(this.info2)
  68. ChildPage({ childInfo: this.info1 }) // Call the custom component.
  69. ChildPage({ childInfo: this.info2 }) // Call the custom component.
  70. Button('change info1&info2')
  71. .onClick(() => {
  72. this.info1.name = 'Cat'; // Change the name value of info1 displayed in Text1.
  73. this.info1.age = 18; // Change the age value of info1 displayed in Text1.
  74. this.info2.name = 'Cat'; // Change the name value of info2 displayed in Text2.
  75. this.info2.age = 18; // Change the age value of info2 displayed in Text2.
  76. })
  77. }
  78. .height('100%')
  79. .width('100%')
  80. }
  81. }

Effect

When passing parameters to @Builder by reference, if the parameter is an object decorated with @Local, assigning a value to the entire object triggers a UI refresh within @Builder.

Collapse
Word wrap
Dark theme
Copy code
  1. class LocalInfo {
  2. public name: string = 'Tom';
  3. public age: number = 25;
  4. }
  5. @Builder
  6. function overBuilderLocal(param: LocalInfo) {
  7. Column() {
  8. Text(`Global@Builder name: ${param.name}`)
  9. Text(`Global@Builder age: ${param.age}`)
  10. }
  11. .width(230)
  12. .height(40)
  13. .margin(10)
  14. .padding({ left: 20 })
  15. .backgroundColor('#0d000000')
  16. .borderRadius(20)
  17. }
  18. @ComponentV2
  19. struct ChildLocalPage {
  20. @Require @Param childLocalInfo: LocalInfo;
  21. build() {
  22. Column() {
  23. // By-reference parameter passing is used.
  24. overBuilderLocal({ name: this.childLocalInfo.name, age: this.childLocalInfo.age })
  25. }
  26. }
  27. }
  28. @Entry
  29. @ComponentV2
  30. struct ParentLocalPage {
  31. LocalInfo1: LocalInfo = { name: 'Tom', age: 25 };
  32. @Local LocalInfo2: LocalInfo = { name: 'Tom', age: 25 };
  33. @Builder
  34. privateBuilder() {
  35. Column() {
  36. Text(`Private@Builder name: ${this.LocalInfo1.name}`)
  37. Text(`Private@Builder age: ${this.LocalInfo1.age}`)
  38. }
  39. .width(230)
  40. .height(40)
  41. .margin(10)
  42. .backgroundColor('#0d000000')
  43. .borderRadius(20)
  44. }
  45. build() {
  46. Column() {
  47. Flex() {
  48. Column() {
  49. Text(`LocalInfo1: ${this.LocalInfo1.name} ${this.LocalInfo1.age}`) // Text1
  50. Text(`LocalInfo2: ${this.LocalInfo2.name} ${this.LocalInfo2.age}`) // Text2
  51. }
  52. }
  53. .width(230)
  54. .height(40)
  55. .margin(10)
  56. .padding({ left: 60 })
  57. .backgroundColor('#0d000000')
  58. .borderRadius(20)
  59. // Call the local @Builder.
  60. this.privateBuilder()
  61. // Call the global @Builder. Here, the reference transfer mode is used.
  62. overBuilderLocal({ name: this.LocalInfo2.name, age: this.LocalInfo2.age })
  63. ChildLocalPage({ childLocalInfo: this.LocalInfo1 }) // Invoke the custom component.
  64. ChildLocalPage({ childLocalInfo: this.LocalInfo2 }) // Invoke the custom component.
  65. Button('change LocalInfo1&LocalInfo2')
  66. .onClick(() => {
  67. this.LocalInfo1 = { name: 'Cat', age: 18 }; // Text1 is not re-rendered because no decorator is used to listen for value changes.
  68. this.LocalInfo2 = { name: 'Cat', age: 18 }; // Text2 is re-rendered because a decorator is used to listen for value changes.
  69. })
  70. }
  71. .height('100%')
  72. .width('100%')
  73. }
  74. }

Effect

Global @Builder Reused Across Components

In the cross-component scenario, the global @Builder is called to transfer parameters by reference to implement the dynamic UI update function.

Collapse
Word wrap
Dark theme
Copy code
  1. class ReusableTmp {
  2. public componentName: string = 'Child';
  3. }
  4. @Builder
  5. function itemBuilder(params: ReusableTmp) {
  6. Column() {
  7. Text(`Builder ===${params.componentName}`)
  8. .width(300)
  9. .height(40)
  10. .margin(10)
  11. .backgroundColor('#0d000000')
  12. .fontColor('#e6000000')
  13. .borderRadius(20)
  14. .textAlign(TextAlign.Center)
  15. }
  16. }
  17. @Entry
  18. @Component
  19. struct ReusablePage {
  20. @State switchFlag: boolean = true;
  21. build() {
  22. Column() {
  23. if (this.switchFlag) {
  24. // Invoke the customized component ReusableChildPage.
  25. ReusableChildPage({ message: 'Child' })
  26. } else {
  27. // Invoke the customized component ReusableChildTwoPage.
  28. ReusableChildTwoPage({ message: 'ChildTwo' })
  29. }
  30. Button('Click me')
  31. .onClick(() => {
  32. this.switchFlag = !this.switchFlag;
  33. })
  34. }
  35. .height('100%')
  36. .width('100%')
  37. }
  38. }
  39. @Reusable
  40. @Component
  41. struct ReusableChildPage {
  42. @State message: string = 'Child';
  43. aboutToReuse(params: Record<string, ESObject>): void {
  44. console.info('Recycle ====Child');
  45. this.message = params.message;
  46. }
  47. build() {
  48. Column() {
  49. Text(`ReusableChildPage ===${this.message}`)
  50. .width(300)
  51. .height(40)
  52. .margin(10)
  53. .backgroundColor('#0d000000')
  54. .fontColor('#e6000000')
  55. .borderRadius(20)
  56. .textAlign(TextAlign.Center)
  57. // Invoke the global @Builder function itemBuilder.
  58. itemBuilder({ componentName: this.message })
  59. }
  60. }
  61. }
  62. @Reusable
  63. @Component
  64. struct ReusableChildTwoPage {
  65. @State message: string = 'ChildTwo';
  66. aboutToReuse(params: Record<string, ESObject>): void {
  67. console.info('Recycle ====ChildTwo');
  68. this.message = params.message;
  69. }
  70. build() {
  71. Column() {
  72. Text(`ReusableChildTwoPage ===${this.message}`)
  73. .width(300)
  74. .height(40)
  75. .margin(10)
  76. .backgroundColor('#0d000000')
  77. .fontColor('#e6000000')
  78. .borderRadius(20)
  79. .textAlign(TextAlign.Center)
  80. // Invoke the global @Builder function itemBuilder.
  81. itemBuilder({ componentName: this.message })
  82. }
  83. }
  84. }

Effect

State Variables Can Be Refreshed in the @Builder

From API version 20, you can use the UIUtils.makeBinding() function, the Binding class, and the MutableBinding class to refresh state variables in the @Builder function. For details, see makeBinding.

Collapse
Word wrap
Dark theme
Copy code
  1. import { Binding, MutableBinding, UIUtils } from '@kit.ArkUI';
  2. @ObservedV2
  3. class ClassA {
  4. @Trace public props: string = 'Hello';
  5. }
  6. @Builder
  7. function customButton(num1: Binding<number>, num2: MutableBinding<number>) {
  8. Row() {
  9. Column() {
  10. Text(`number1 === ${num1.value}, number2 === ${num2.value}`)
  11. .width(300)
  12. .height(40)
  13. .margin(10)
  14. .backgroundColor('#0d000000')
  15. .fontColor('#e6000000')
  16. .borderRadius(20)
  17. .textAlign(TextAlign.Center)
  18. Button(`only change number2`)
  19. .onClick(() => {
  20. num2.value += 1;
  21. })
  22. }
  23. }
  24. }
  25. @Builder
  26. function customButtonObj(obj1: MutableBinding<ClassA>) {
  27. Row() {
  28. Column() {
  29. Text(`props === ${obj1.value.props}`)
  30. .width(300)
  31. .height(40)
  32. .margin(10)
  33. .backgroundColor('#0d000000')
  34. .fontColor('#e6000000')
  35. .borderRadius(20)
  36. .textAlign(TextAlign.Center)
  37. Button(`change props`)
  38. .onClick(() => {
  39. obj1.value.props += 'Hi';
  40. })
  41. }
  42. }
  43. }
  44. @Entry
  45. @ComponentV2
  46. struct Single {
  47. @Local number1: number = 5;
  48. @Local number2: number = 12;
  49. @Local classA: ClassA = new ClassA();
  50. build() {
  51. Column() {
  52. Button(`change both number1 and number2`)
  53. .onClick(() => {
  54. this.number1 += 1;
  55. this.number2 += 2;
  56. })
  57. Text(`number1 === ${this.number1}`)
  58. .width(300)
  59. .height(40)
  60. .margin(10)
  61. .backgroundColor('#0d000000')
  62. .fontColor('#e6000000')
  63. .borderRadius(20)
  64. .textAlign(TextAlign.Center)
  65. Text(`number2 === ${this.number2}`)
  66. .width(300)
  67. .height(40)
  68. .margin(10)
  69. .backgroundColor('#0d000000')
  70. .fontColor('#e6000000')
  71. .borderRadius(20)
  72. .textAlign(TextAlign.Center)
  73. // Call the global @Builder function customButton.
  74. customButton(
  75. UIUtils.makeBinding<number>(() => this.number1), // Use the UIUtils.makeBinding() function to update the state variables in the @Builder function.
  76. UIUtils.makeBinding<number>(
  77. () => this.number2,
  78. (val: number) => {
  79. this.number2 = val;
  80. })
  81. )
  82. Text(`classA.props === ${this.classA.props}`)
  83. .width(300)
  84. .height(40)
  85. .margin(10)
  86. .backgroundColor('#0d000000')
  87. .fontColor('#e6000000')
  88. .borderRadius(20)
  89. .textAlign(TextAlign.Center)
  90. // Call the global @Builder function customButtonObj.
  91. customButtonObj(
  92. UIUtils.makeBinding<ClassA>( // Use the UIUtils.makeBinding () function to refresh state variables in the @Builder function.
  93. () => this.classA,
  94. (val: ClassA) => {
  95. this.classA = val;
  96. })
  97. )
  98. }
  99. .width('100%')
  100. .height('100%')
  101. .alignItems(HorizontalAlign.Center)
  102. .justifyContent(FlexAlign.Center)
  103. }
  104. }

Effect

FAQs

Multiple Parameters in @Builder

When @Builder functions have two or more parameters, UI re-rendering is not triggered by value changes, even when parameters are passed via object literals.

Incorrect Usage

Collapse
Word wrap
Dark theme
Copy code
  1. class GlobalTmp1 {
  2. public strValue: string = 'Hello';
  3. }
  4. @Builder
  5. function overBuilder1(param: GlobalTmp1, num: number) {
  6. Column() {
  7. Text(`strValue: ${param.strValue}`)
  8. Text(`num: ${num}`)
  9. }
  10. }
  11. @Entry
  12. @Component
  13. struct Parent1 {
  14. @State objParam: GlobalTmp1 = new GlobalTmp1();
  15. @State num: number = 0;
  16. build() {
  17. Column() {
  18. Text('UI Rendered via @Builder')
  19. .fontSize(20)
  20. // Two parameters are used, which is incorrect.
  21. overBuilder1({ strValue: this.objParam.strValue }, this.num)
  22. Line()
  23. .width('100%')
  24. .height(10)
  25. .backgroundColor('#000000').margin(10)
  26. Button('Update Values').onClick(() => {
  27. this.objParam.strValue = 'Hello World';
  28. this.num = 1;
  29. })
  30. }
  31. }
  32. }

Incorrect Usage

Collapse
Word wrap
Dark theme
Copy code
  1. class GlobalTmp2 {
  2. public strValue: string = 'Hello';
  3. }
  4. class SecondTmp {
  5. public numValue: number = 0;
  6. }
  7. @Builder
  8. function overBuilder2(param: GlobalTmp2, num: SecondTmp) {
  9. Column() {
  10. Text(`strValue: ${param.strValue}`)
  11. Text(`num: ${num.numValue}`)
  12. }
  13. }
  14. @Entry
  15. @Component
  16. struct Parent2 {
  17. @State strParam: GlobalTmp2 = new GlobalTmp2();
  18. @State numParam: SecondTmp = new SecondTmp();
  19. build() {
  20. Column() {
  21. Text('UI Rendered via @Builder')
  22. .fontSize(20)
  23. // Two parameters are used, which is incorrect.
  24. overBuilder2({ strValue: this.strParam.strValue }, { numValue: this.numParam.numValue })
  25. Line()
  26. .width('100%')
  27. .height(10)
  28. .backgroundColor('#000000').margin(10)
  29. Button('Update Values').onClick(() => {
  30. this.strParam.strValue = 'Hello World';
  31. this.numParam.numValue = 1;
  32. })
  33. }
  34. }
  35. }

@Builder functions accept only one parameter, which must be passed as an object literal. Changes to this parameter's value trigger UI re-rendering.

Correct Usage

Collapse
Word wrap
Dark theme
Copy code
  1. class GlobalTmp3 {
  2. public strValue: string = 'Hello';
  3. public numValue: number = 0;
  4. }
  5. @Builder
  6. function overBuilder3(param: GlobalTmp3) {
  7. Column() {
  8. Text(`strValue: ${param.strValue}`)
  9. Text(`num: ${param.numValue}`)
  10. }
  11. }
  12. @Entry
  13. @Component
  14. struct Parent3 {
  15. @State objParam: GlobalTmp3 = new GlobalTmp3();
  16. build() {
  17. Column() {
  18. Text('UI Rendered via @Builder')
  19. .fontSize(20)
  20. // Input a parameter. This is a correct usage.
  21. overBuilder3({ strValue: this.objParam.strValue, numValue: this.objParam.numValue })
  22. Line()
  23. .width('100%')
  24. .height(10)
  25. .backgroundColor('#000000').margin(10)
  26. Button('Update Values').onClick(() => {
  27. this.objParam.strValue = 'Hello World';
  28. this.objParam.numValue = 1;
  29. })
  30. }
  31. }
  32. }

Dynamic Re-rendering with @ComponentV2

In @ComponentV2 decorated components, combine @ObservedV2 and @Trace decorators to enable UI re-rendering through value-based change detection.

Incorrect Usage

In the custom component decorated with @ComponentV2, using primitive data types cannot trigger UI refresh.

Collapse
Word wrap
Dark theme
Copy code
  1. @ObservedV2
  2. class ParamTemp {
  3. @Trace public count : number = 0;
  4. }
  5. @Builder
  6. function renderNumber(paramNum: number) {
  7. Text(`paramNum : ${paramNum}`)
  8. .fontSize(30)
  9. .fontWeight(FontWeight.Bold)
  10. }
  11. @Entry
  12. @ComponentV2
  13. struct PageBuilderIncorrectUsage {
  14. @Local classValue: ParamTemp = new ParamTemp();
  15. // Using primitive data type cannot trigger UI re-rendering.
  16. @Local numValue: number = 0;
  17. private progressTimer: number = -1;
  18. aboutToAppear(): void {
  19. this.progressTimer = setInterval(() => {
  20. if (this.classValue.count < 100) {
  21. this.classValue.count += 5;
  22. this.numValue += 5;
  23. } else {
  24. clearInterval(this.progressTimer);
  25. }
  26. }, 500);
  27. }
  28. build() {
  29. Column() {
  30. renderNumber(this.numValue)
  31. }
  32. .width('100%')
  33. .height('100%')
  34. .padding(50)
  35. }
  36. }

Correct Usage

In a custom component decorated by @ComponentV2, only the ParamTmpClass class decorated by @ObservedV2 and the count attribute decorated by @Trace can trigger UI refresh.

Collapse
Word wrap
Dark theme
Copy code
  1. @ObservedV2
  2. class ParamTmpClass {
  3. @Trace public count: number = 0;
  4. }
  5. @Builder
  6. function renderText(param: ParamTmpClass) {
  7. Column() {
  8. Text(`param : ${param.count}`)
  9. .fontSize(20)
  10. .fontWeight(FontWeight.Bold)
  11. }
  12. }
  13. @Builder
  14. function renderMap(paramMap: Map<string, number>) {
  15. Text(`paramMap : ${paramMap.get('name')}`)
  16. .fontSize(20)
  17. .fontWeight(FontWeight.Bold)
  18. }
  19. @Builder
  20. function renderSet(paramSet: Set<number>) {
  21. Text(`paramSet : ${paramSet.size}`)
  22. .fontSize(20)
  23. .fontWeight(FontWeight.Bold)
  24. }
  25. @Builder
  26. function renderNumberArr(paramNumArr: number[]) {
  27. Text(`paramNumArr : ${paramNumArr[0]}`)
  28. .fontSize(20)
  29. .fontWeight(FontWeight.Bold)
  30. }
  31. @Entry
  32. @ComponentV2
  33. struct PageBuilderCorrectUsage {
  34. @Local builderParams: ParamTmpClass = new ParamTmpClass();
  35. @Local mapValue: Map<string, number> = new Map();
  36. @Local setValue: Set<number> = new Set([0]);
  37. @Local numArrValue: number[] = [0];
  38. private progressTimer: number = -1;
  39. aboutToAppear(): void {
  40. this.progressTimer = setInterval(() => {
  41. if (this.builderParams.count < 100) {
  42. // builderParams is a ParamTmpClass class decorated with @ObservedV2.
  43. // The count attribute is decorated with @Trace.
  44. // The count change will trigger UI re-rendering.
  45. this.builderParams.count += 5;
  46. this.mapValue.set('name', this.builderParams.count);
  47. this.setValue.add(this.builderParams.count);
  48. this.numArrValue[0] = this.builderParams.count;
  49. } else {
  50. clearInterval(this.progressTimer);
  51. }
  52. }, 500);
  53. }
  54. @Builder
  55. localBuilder() {
  56. Column() {
  57. Text(`localBuilder : ${this.builderParams.count}`)
  58. .fontSize(20)
  59. .fontWeight(FontWeight.Bold)
  60. }
  61. }
  62. build() {
  63. Column() {
  64. this.localBuilder()
  65. Text(`builderParams :${this.builderParams.count}`)
  66. .fontSize(20)
  67. .fontWeight(FontWeight.Bold)
  68. renderText(this.builderParams)
  69. renderText({ count: this.builderParams.count })
  70. renderMap(this.mapValue)
  71. renderSet(this.setValue)
  72. renderNumberArr(this.numArrValue)
  73. }
  74. .width('100%')
  75. .height('100%')
  76. }
  77. }

Component Creation in @Builder Prevents Parameter Update Propagation

Create the customized component HelloComponent1 in the parentBuilder1 function, transfer the class object as the parameter, and change the value in the object. The UI does not trigger the refresh function.

Incorrect Usage

Collapse
Word wrap
Dark theme
Copy code
  1. class Tmp4 {
  2. public name: string = 'Hello';
  3. public age: number = 16;
  4. }
  5. @Builder
  6. function parentBuilder1(params: Tmp4) {
  7. Row() {
  8. Column() {
  9. Text(`parentBuilder1===${params.name}===${params.age}`)
  10. .fontSize(20)
  11. .fontWeight(FontWeight.Bold)
  12. // This is not a reference-based transfer mode. The usage is incorrect. As a result, the UI is not refreshed.
  13. HelloComponent1({ info: params })
  14. }
  15. }
  16. }
  17. @Component
  18. struct HelloComponent1 {
  19. @Prop info: Tmp4 = new Tmp4();
  20. build() {
  21. Row() {
  22. Text(`HelloComponent1===${this.info.name}===${this.info.age}`)
  23. .fontSize(20)
  24. .fontWeight(FontWeight.Bold)
  25. }
  26. }
  27. }
  28. @Entry
  29. @Component
  30. struct ParentPage1 {
  31. @State nameValue: string = 'Zhang San';
  32. @State ageValue: number = 18;
  33. build() {
  34. Column() {
  35. parentBuilder1({ name: this.nameValue, age: this.ageValue })
  36. Button('Click me')
  37. .onClick(() => {
  38. // The modification does not cause the change of HelloComponent1.
  39. this.nameValue = 'Li Si';
  40. this.ageValue = 20;
  41. })
  42. }
  43. .height('100%')
  44. .width('100%')
  45. }
  46. }

Create the customized component HelloComponent2 in the parentBuilder2 function, transfer the parameter in the object literal format, and change the value in the object. The UI triggers the refresh function.

Correct Usage

Collapse
Word wrap
Dark theme
Copy code
  1. class Tmp5 {
  2. public name: string = 'Hello';
  3. public age: number = 16;
  4. }
  5. @Builder
  6. function parentBuilder2(params: Tmp5) {
  7. Row() {
  8. Column() {
  9. Text(`parentBuilder2===${params.name}===${params.age}`)
  10. .fontSize(20)
  11. .fontWeight(FontWeight.Bold)
  12. // Split the entire object into simple types, which is transferred by reference. Modifying attributes can trigger UI refresh.
  13. HelloComponent2({ childName: params.name, childAge: params.age })
  14. }
  15. }
  16. }
  17. @Component
  18. struct HelloComponent2 {
  19. @Prop childName: string = '';
  20. @Prop childAge: number = 0;
  21. build() {
  22. Row() {
  23. Text(`HelloComponent2===${this.childName}===${this.childAge}`)
  24. .fontSize(20)
  25. .fontWeight(FontWeight.Bold)
  26. }
  27. }
  28. }
  29. @Entry
  30. @Component
  31. struct ParentPage2 {
  32. @State nameValue: string = 'Zhang San';
  33. @State ageValue: number = 18;
  34. build() {
  35. Column() {
  36. parentBuilder2({ name: this.nameValue, age: this.ageValue })
  37. Button('Click me')
  38. .onClick(() => {
  39. // Modifying the content here will cause the change of HelloComponent2.
  40. this.nameValue = 'Li Si';
  41. this.ageValue = 20;
  42. })
  43. }
  44. .height('100%')
  45. .width('100%')
  46. }
  47. }

Invoking the @Builder function or method outside the UI statement affects the normal refreshing of the node.

When the @Builder method assigns a value to a variable or array, the method cannot be used in the UI method, and the node display is abnormal during refresh.

Incorrect Usage

Collapse
Word wrap
Dark theme
Copy code
  1. @Entry
  2. @Component
  3. struct BackGround1 {
  4. @Builder
  5. myImages() {
  6. Column() {
  7. // Load the image resource named startIcon from the media directory of the application. 'app.media.startIcon' is only an example. Replace it with the actual one.
  8. Image($r('app.media.startIcon')).width('100%').height('100%')
  9. }
  10. };
  11. @Builder
  12. myImages2() {
  13. Column() {
  14. // Load the image resource named startIcon from the media directory of the application. 'app.media.startIcon' is only an example. Replace it with the actual one.
  15. Image($r('app.media.startIcon')).width('100%').height('100%')
  16. }
  17. };
  18. private bgList: Array<CustomBuilder> = [this.myImages(), this.myImages2()]; // Incorrect usage. Do not invoke the @Builder method outside the UI method.
  19. @State bgBuilder: CustomBuilder = this.myImages(); // Incorrect usage. Do not call the @Builder method outside the UI method.
  20. @State bgColor: ResourceColor = Color.Orange;
  21. @State bgColor2: ResourceColor = Color.Orange;
  22. @State index: number = 0;
  23. build() {
  24. Column({ space: 10 }) {
  25. Text('1').width(100).height(50)
  26. Text('2').width(100).height(50)
  27. Text('3').width(100).height(50)
  28. Text('4-1').width(100).height(50).fontColor(this.bgColor)
  29. Text('5-1').width(100).height(50)
  30. Text('4-2').width(100).height(50)
  31. Text('5-2').width(100).height(50)
  32. Stack() {
  33. Column() {
  34. Text('Vsync2')
  35. }
  36. .size({ width: '100%', height: '100%' })
  37. .border({ width: 1, color: Color.Black })
  38. }
  39. .size({ width: 100, height: 80 })
  40. .backgroundColor('#ffbbd4bb')
  41. Button('change').onClick((event: ClickEvent) => {
  42. this.index = 1;
  43. this.bgColor = Color.Red;
  44. this.bgColor2 = Color.Red;
  45. })
  46. }
  47. .margin(10)
  48. }
  49. }

The @Builder method cannot be used in UI methods after it is assigned to a variable or array. You should avoid using the @Builder method after it is assigned to a variable or array.

Correct Usage

Collapse
Word wrap
Dark theme
Copy code
  1. @Entry
  2. @Component
  3. struct BackGround2 {
  4. @Builder
  5. myImages() {
  6. Column() {
  7. // Load the image resource named startIcon from the media directory of the application. 'app.media.startIcon' is only an example. Replace it with the actual one.
  8. Image($r('app.media.startIcon')).width('100%').height('100%')
  9. }
  10. }
  11. @Builder
  12. myImages2() {
  13. Column() {
  14. // Load the image resource named startIcon from the media directory of the application. 'app.media.startIcon' is only an example. Replace it with the actual one.
  15. Image($r('app.media.startIcon')).width('100%').height('100%')
  16. }
  17. }
  18. @State bgColor: ResourceColor = Color.Orange;
  19. @State bgColor2: ResourceColor = Color.Orange;
  20. @State index: number = 0;
  21. build() {
  22. Column({ space: 10 }) {
  23. Text('1').width(100).height(50)
  24. Text('2').width(100).height(50).background(this.myImages) // Directly pass the @Builder method.
  25. Text('3').width(100).height(50).background(this.myImages()) // Directly calls the @Builder method.
  26. Text('4-1').width(100).height(50).fontColor(this.bgColor)
  27. Text('5-1').width(100).height(50)
  28. Text('4-2').width(100).height(50)
  29. Text('5-2').width(100).height(50)
  30. Stack() {
  31. Column() {
  32. Text('Vsync2')
  33. }
  34. .size({ width: '100%', height: '100%' })
  35. .border({ width: 1, color: Color.Black })
  36. }
  37. .size({ width: 100, height: 80 })
  38. .backgroundColor('#ffbbd4bb')
  39. Button('change').onClick((event: ClickEvent) => {
  40. this.index = 1;
  41. this.bgColor = Color.Red;
  42. this.bgColor2 = Color.Red;
  43. })
  44. }
  45. .margin(10)
  46. }
  47. }

The Set Accessor Is Not Passed When MutableBinding Is Used in the @Builder Method

When MutableBinding is used in the @Builder method definition, the set accessor is not passed to the MutableBinding type parameter during construction. As a result, a runtime error occurs when the set accessor is triggered.

Incorrect Usage

Collapse
Word wrap
Dark theme
Copy code
  1. import { UIUtils, Binding, MutableBinding } from '@kit.ArkUI';
  2. @ObservedV2
  3. class GlobalTmp1 {
  4. @Trace public strValue: string = 'Hello';
  5. }
  6. @Builder
  7. function builderWithTwoParams1(param1: Binding<GlobalTmp1>, param2: MutableBinding<number>) {
  8. Column() {
  9. Text(`strValue: ${param1.value.strValue}`)
  10. Button(`num: ${param2.value}`)
  11. .onClick(() => {
  12. param2.value += 1; // Click the button to trigger the set accessor, which causes a runtime error.
  13. })
  14. }.borderWidth(1)
  15. }
  16. @Entry
  17. @ComponentV2
  18. struct MakeBindingTest1 {
  19. @Local GlobalTmp1: GlobalTmp1 = new GlobalTmp1();
  20. @Local num: number = 0;
  21. build() {
  22. Column() {
  23. Text(`${this.GlobalTmp1.strValue}`)
  24. builderWithTwoParams1(UIUtils.makeBinding(() => this.GlobalTmp1),
  25. UIUtils.makeBinding<number>(() => this.num)) // SetterCallback is not passed when a parameter of the MutableBinding type is constructed.
  26. Button('Update Values').onClick(() => {
  27. this.GlobalTmp1.strValue = 'Hello World 2025';
  28. this.num = 1;
  29. })
  30. }
  31. }
  32. }

For details, see MutableBinding in the statement management API reference.

Correct Usage

Collapse
Word wrap
Dark theme
Copy code
  1. import { UIUtils, Binding, MutableBinding } from '@kit.ArkUI';
  2. @ObservedV2
  3. class GlobalTmp2 {
  4. @Trace public strValue: string = 'Hello';
  5. }
  6. @Builder
  7. function builderWithTwoParams2(param1: Binding<GlobalTmp2>, param2: MutableBinding<number>) {
  8. Column() {
  9. Text(`strValue: ${param1.value.strValue}`)
  10. Button(`num: ${param2.value}`)
  11. .onClick(() => {
  12. param2.value += 1; // The value attribute of the MutableBinding parameter is modified.
  13. })
  14. }.borderWidth(1)
  15. }
  16. @Entry
  17. @ComponentV2
  18. struct MakeBindingTest2 {
  19. @Local GlobalTmp2: GlobalTmp2 = new GlobalTmp2();
  20. @Local num: number = 0;
  21. build() {
  22. Column() {
  23. Text(`${this.GlobalTmp2.strValue}`)
  24. builderWithTwoParams2(UIUtils.makeBinding(() => this.GlobalTmp2),
  25. UIUtils.makeBinding<number>(() => this.num,
  26. val => {
  27. this.num = val;
  28. }))
  29. Button('Update Values').onClick(() => {
  30. this.GlobalTmp2.strValue = 'Hello World 2025';
  31. this.num = 1;
  32. })
  33. }
  34. }
  35. }

Changing the Input Parameters in the @Builder Decorated Function

If MutableBinding is not used, the modification of parameter values in the function decorated by @Builder does not take effect and may cause runtime errors. Since API version 23, error code 140109 will be returned.

Incorrect Usage

Collapse
Word wrap
Dark theme
Copy code
  1. @Builder
  2. function myGlobalBuilder(value: string) {
  3. Column() {
  4. Text(`myGlobalBuilder: ${value} `)
  5. .fontSize(16)
  6. .onClick(() => {
  7. // Modify parameters in the @Builder function of the simple type transferred by value. The UI does not refresh but does not exit unexpectedly.
  8. value = 'value change';
  9. })
  10. }.borderWidth(1)
  11. }
  12. interface TempMod1 {
  13. paramA: string;
  14. }
  15. @Builder
  16. function overBuilderMod1(param: TempMod1) {
  17. Row() {
  18. Column() {
  19. Button(`overBuilderMod1 === ${param.paramA}`)
  20. .onClick(() => {
  21. // Incorrect format. The attributes of object type parameters cannot be modified in the function decorated by @Builder. As a result, the system crashes and the UI is not refreshed.
  22. param.paramA = 'Yes';
  23. })
  24. Button('change')
  25. .onClick(() => {
  26. // Incorrect format. The reference of the object type parameter cannot be modified in the function decorated by @Builder. The UI does not refresh but does not exit unexpectedly.
  27. param = { paramA: 'change trial' };
  28. })
  29. }
  30. }
  31. }
  32. @Entry
  33. @Component
  34. struct ParentMod1 {
  35. @State label: string = 'Hello';
  36. @State message1: string = 'Value Passing';
  37. @Builder
  38. extendBlank() {
  39. Row() {
  40. Blank()
  41. }
  42. .height(20)
  43. }
  44. build() {
  45. Column() {
  46. // Passing by reference can implement UI refresh when parameters change, but parameters cannot be modified in the @Builder function.
  47. overBuilderMod1({ paramA: this.label });
  48. this.extendBlank();
  49. Button('click me')
  50. .onClick(() => {
  51. this.label = 'ArkUI';
  52. })
  53. this.extendBlank();
  54. myGlobalBuilder(this.message1);
  55. }
  56. }
  57. }

Correct use of MutableBinding helps developers modify parameter values in functions decorated by @Builder.

Correct Usage

Collapse
Word wrap
Dark theme
Copy code
  1. import { UIUtils, MutableBinding } from '@kit.ArkUI';
  2. // Use MutableBinding to modify parameter values in the @Builder decorated function.
  3. @Builder
  4. function myGlobalBuilderMod(str: MutableBinding<string>) {
  5. Column() {
  6. Text(`Mod--MyGlobalBuilder: ${str.value}`)
  7. .fontSize(16)
  8. .onClick(() => {
  9. str.value = 'value change mod';
  10. })
  11. }
  12. }
  13. interface TempMod2 {
  14. paramA: string;
  15. }
  16. // Use MutableBinding to modify parameter values in the function decorated by @Builder.
  17. @Builder
  18. function overBuilderMod2(param: MutableBinding<TempMod2>) {
  19. Column() {
  20. Button(`Mod--overBuilder === ${param.value.paramA}`)
  21. .onClick(() => {
  22. param.value.paramA = 'Yes';
  23. })
  24. Button(`change`)
  25. .onClick(() => {
  26. param.value = { paramA: 'trialOne' };
  27. })
  28. }
  29. }
  30. @Entry
  31. @Component
  32. struct ParentMod2 {
  33. @State label: string = 'Hello';
  34. @State message1: string = 'Value Passing';
  35. @State objectOne: TempMod2 = {
  36. paramA: this.label
  37. };
  38. @Builder
  39. extendBlank() {
  40. Row() {
  41. Blank()
  42. }
  43. .height(20)
  44. }
  45. build() {
  46. Column() {
  47. // When MutableBinding is used, the object literal cannot be transferred. You need to extract the literal object as a state variable first.
  48. overBuilderMod2(
  49. UIUtils.makeBinding<TempMod2>(
  50. () => this.objectOne,
  51. value => {
  52. this.objectOne = value; // SetterCallback must be transferred. Otherwise, a runtime error occurs when this function is triggered.
  53. }
  54. )
  55. )
  56. this.extendBlank();
  57. Button('click me')
  58. .onClick(() => {
  59. this.objectOne.paramA = 'ArkUI';
  60. })
  61. this.extendBlank();
  62. myGlobalBuilderMod(
  63. UIUtils.makeBinding<string>(
  64. () => this.message1,
  65. value => {
  66. this.message1 = value; // SetterCallback must be transferred. Otherwise, a runtime error occurs when this function is triggered.
  67. }
  68. )
  69. );
  70. }
  71. }
  72. }

Execute the @Builder function in the @Watch function.

If the @Builder function is executed in the @Watch function, the UI refresh is abnormal.

Incorrect Usage

Collapse
Word wrap
Dark theme
Copy code
  1. @Entry
  2. @Component
  3. struct Child1 {
  4. @Provide @Watch('provideWatch') content: string = 'Index: hello world';
  5. @Builder
  6. watchBuilder(content: string) {
  7. Row() {
  8. Text(`${content}`)
  9. }
  10. }
  11. provideWatch() {
  12. this.watchBuilder(this.content); // Incorrect format. Use the @Builder function in the @Watch function.
  13. }
  14. build() {
  15. Column() {
  16. Button(`content value: ${this.content}`)
  17. .onClick(() => {
  18. this.content += '_world';
  19. })
  20. this.watchBuilder(this.content);
  21. }
  22. }
  23. }

The UI of the button may be abnormal. Therefore, you need to avoid using the @Builder function in the @Watch function.

Correct Usage

Collapse
Word wrap
Dark theme
Copy code
  1. @Entry
  2. @Component
  3. struct Child2 {
  4. @Provide @Watch('provideWatch') content: string = 'Index: hello world';
  5. @Builder
  6. watchBuilder(content: string) {
  7. Row() {
  8. Text(`${content}`)
  9. }
  10. }
  11. provideWatch() {
  12. // Correct format. Do not use the @Builder function in the @Watch function.
  13. console.info(`content value has changed.`);
  14. }
  15. build() {
  16. Column() {
  17. Button(`content value: ${this.content}`)
  18. .onClick(() => {
  19. this.content += '_world';
  20. })
  21. this.watchBuilder(this.content);
  22. }
  23. }
  24. }
Search in Guides
Enter a keyword.