@Provide and @Consume Macros: Two-Way Synchronization with Descendant Components
Note:
Currently in the beta phase.
@Provide and @Consume are used for two-way data synchronization with descendant components, applicable in scenarios where state data needs to be passed across multiple levels. Unlike the parent-child component parameter passing mechanism mentioned earlier, @Provide and @Consume break free from the constraints of parameter passing, enabling cross-level data transfer.
The variable decorated with @Provide resides in the ancestor component and can be understood as a state variable "provided" to descendant components. The variable decorated with @Consume resides in descendant components and "consumes (binds)" the variable provided by the ancestor component.
@Provide / @Consume enables two-way synchronization across component hierarchies. Before reading the documentation on @Provide and @Consume, developers are advised to have a basic understanding of UI paradigm syntax and custom components. It is recommended to read the following in advance: Basic Syntax Overview, Declarative UI Description, Custom Components - Creating Custom Components.
Overview
State variables decorated with @Provide / @Consume have the following characteristics:
-
The state variable decorated with @Provide is automatically available to all its descendant components, meaning the variable is "provided" to its descendants. This demonstrates the convenience of @Provide, as developers do not need to pass variables multiple times between components.
-
Descendants use @Consume to access the variable provided by @Provide, establishing two-way data synchronization between @Provide and @Consume. Unlike @State / @Link, the former can be passed across multi-level parent-child components.
-
@Provide and @Consume can be bound using the same variable name or alias, but their types must match. Otherwise, implicit type conversion may occur, leading to abnormal application behavior.
// Binding via the same variable name
@Provide
var age: Int64 = 0;
@Consume
var age: Int64;
// Binding via the same variable alias
@Provide["a"]
var id: Float64 = 0.0;
@Consume["a"]
var age: Float64;
When @Provide and @Consume are bound using the same variable name or alias, the variable decorated with @Provide and the variable decorated with @Consume form a one-to-many relationship. If multiple @Provide-decorated variables with the same name or alias are declared within the same custom component (including its child components), the @Consume-decorated variable will search upward and match the nearest @Provide-decorated variable.
Additionally, if an alias is declared in the @Provide annotation, the @Consume variable must be bound using the corresponding alias. The @Provide variable cannot be found by variable name alone.
Macro Description
The rules for @State also apply to @Provide. The difference is that @Provide also serves as a synchronization source for multi-level descendants.
| @Provide | Description |
|---|---|
| Macro Parameter | Alias: A constant string, optional. If specified, the variable is bound via the alias; if not, it is bound via the variable name. |
| Synchronization Type | Two-way synchronization. Data synchronization from the @Provide variable to all @Consume variables and vice versa. The two-way synchronization behavior is the same as the combination of @State and @Link. |
| Allowed Variable Types | Cangjie built-in types include basic data types (except Nothing) and custom types, as well as arrays of these types. Function types and DateTime types are supported. The types of @Provide and @Consume variables must match. For supported types, refer to Observing Changes. |
| Not recommended decorated variable types | Not recommended to decorate function types. |
| Initial Value of Decorated Variable | The type must be specified, and the initial value must be provided. |
| @Provide Supports Duplicate Names | Allowed. @Consume will search upward and match the nearest @Provide. |
| @Consume | Description |
|---|---|
| Macro Parameter | Alias: A constant string, optional. If an alias is provided, there must be a @Provide variable with the same alias for successful matching. If no alias is provided, the variable name and type must match. |
| Synchronization Type | Two-way: From the @Provide variable (see @Provide) to all @Consume variables and vice versa. The two-way synchronization behavior is the same as the combination of @State and @Link. |
| Allowed Variable Types | Cangjie built-in types include basic data types (except Nothing) and custom types, as well as arrays of these types. Function types and DateTime types are supported. The types of @Provide and @Consume variables must match. For supported types, refer to Observing Changes. |
| Not recommended decorated variable types | Not recommended to decorate function types. |
| Initial Value of Decorated Variable | The type must be specified, but no initial value is allowed. |
Variable Passing/Access Rules
| @Provide Passing/Access | Description |
|---|---|
| Initialization and Update from Parent Component | Optional. Allows regular variables in the parent component (assigning regular variables to @Provide only initializes the value; changes to regular variables do not trigger UI updates. Only state variables can trigger UI updates), @State, @Link, @Prop, @Provide, @Consume, @Publish, @StorageLink, @StorageProp, @LocalStorageLink, and @LocalStorageProp to initialize child component @Provide. |
| Initializing Child Components | Allowed. Can be used to initialize @State, @Link, @Prop, @Provide. |
| Synchronization with Parent Component | No. |
| Synchronization with Descendant Components | Two-way synchronization with @Consume. |
| Access Outside Component | Private. Can only be accessed within the component. |
| @Consume Passing/Access | Description |
|---|---|
| Initialization and Update from Parent Component | Prohibited. Initialized from @Provide via the same variable name or alias. |
| Initializing Child Components | Allowed. Can be used to initialize @State, @Link, @Prop, @Provide. |
| Synchronization with Ancestor Component | Two-way synchronization with @Provide. |
| Access Outside Component | Private. The decorated variable follows the visibility of the private modifier. |
Observing Changes and Behavior
Observing Changes
-
When the decorated data type is integer, float, boolean, character, or string, changes in value can be observed.
-
When the decorated object is a tuple, array, or range, updates to the array elements can be observed.
-
When the decorated object is DateTime, the overall assignment of DateTime can be observed. Additionally, DateTime properties can be updated by calling its functions:
addDays(Int64),addHours(Int64),addMinutes(Int64),addMonths(Int64),addNanoseconds(Int64),addSeconds(Int64),addWeeks(Int64),addYears(Int64).
package ohos_app_cangjie_entry
import kit.ArkUI.*
import ohos.arkui.state_macro_manage.*
import std.time.*
// Parent component
@Entry
@Component
class EntryView{
@Provide
var selectedDate: DateTime = DateTime.of(year:2021,month:8,dayOfMonth:8)
public func build(){
Column(){
Button("parent increase the day by 1")
.margin(10)
.onClick({ event
=> this.selectedDate = this.selectedDate.addDays(1)
})
Button("parent update the new date")
.margin(10)
.onClick({ event
=> this.selectedDate = DateTime.of(year:2023,month:7,dayOfMonth:7)
})
DatePicker(
start: DateTime.of(year:1970,month:1,dayOfMonth:1),
end: DateTime.of(year:2100,month:1,dayOfMonth:1),
selected: this.selectedDate
)
Child()
}
}
}
@Component
class Child{
@Consume
var selectedDate: DateTime;
public func build(){
Column(){
Button("child increase the day by 1")
.onClick({ event
=> this.selectedDate = this.selectedDate.addDays(1)
})
Button("child update the new date")
.margin(10)
.onClick({ event
=> this.selectedDate = DateTime.of(year:2023,month:9,dayOfMonth:9)
})
DatePicker(
start: DateTime.of(year:1970,month:1,dayOfMonth:1),
end: DateTime.of(year:2100,month:1,dayOfMonth:1),
selected: this.selectedDate
)
}
}
}

-
When the decorated variable is a HashMap, the overall assignment of the HashMap can be observed. Additionally, the Map's value can be updated by calling its interfaces:
set(),clear(),remove(). See Decorating Map-Type Variables. -
When the decorated variable is a HashSet, the overall assignment of the HashSet can be observed. Additionally, the Set's value can be updated by calling its interfaces:
add(),clear(),remove(). See Decorating Set-Type Variables. -
Function-type variables can be decorated, and changes in their output values can be observed.
package ohos_app_cangjie_entry
import kit.ArkUI.*
import ohos.arkui.state_macro_manage.*
public func returnAdd(a: Int64, b:Int64): Int64{
return a+b
}
public func returnMinus(a: Int64, b:Int64): Int64{
return a-b
}
@Entry
@Component
class EntryView{
@Provide["calc"]
var Func1: (Int64, Int64) -> Int64 = returnAdd
@Provide
var isAdd : Bool = true
func build(){
Column{
Text("Parent")
Text(
if(isAdd == true){
"1+1 = ${Func1(1,1)}"
}else{
"1-1 = ${Func1(1,1)}"}
)
Button(
if(isAdd == true){
"Switch to minus"
}else{
"Switch to add"
}
).onClick({ evt =>
if(isAdd == true){ Func1 = returnMinus }
else{ Func1 = returnAdd }
isAdd = !isAdd
})
Divider()
Child()
}
}
}
@Component
class Child{
@Consume["calc"]
var Func2: (Int64, Int64) -> Int64
@Consume
var isAdd: Bool
func build(){
Column{
Text("Child")
Text(
if(isAdd == true){
"1+1 = ${Func2(1,1)}"
}else{
"1-1 = ${Func2(1,1)}"}
)
Button(
if(isAdd == true){
"Switch to minus"
}else{
"Switch to add"
}
).onClick({ evt =>
if(isAdd == true){ Func2 = returnMinus }
else{ Func2 = returnAdd }
isAdd = !isAdd
})
}
}
}

Framework Behavior
-
Initial Rendering: a. The variable decorated with @Provide is passed to all child components of the @Provide-decorated component in the form of a Map. b. If a child component uses a @Consume variable, it will search the Map for a @Provide variable with the corresponding name. If not found, a runtime error will be thrown. c. When initializing the @Consume variable, if a @Provide variable with the corresponding name/alias is found in the Map, the process is similar to @State/@Link. The @Consume variable will save the corresponding @Provide variable and register itself with @Provide.
-
When the @Provide-decorated data changes: a. As seen in the initial rendering step, the child component @Consume has already registered itself with the parent component. After the parent component @Provide variable changes, it will traverse and update all dependent system components (elementId) and state variables (@Consume). b. After notifying @Consume of the update, all system components (elementId) in the child component that depend on @Consume will be notified. This achieves @Provide-to-@Consume state data synchronization.
-
When the @Consume-decorated data changes: As seen in the initial rendering step, the child component @Consume holds an instance of @Provide. After @Consume updates, it calls the update method of @Provide to synchronize the updated value back to @Provide, achieving @Consume-to-@Provide synchronization.
Constraints
-
The key parameter for @Provide / @Consume must be a String literal; otherwise, a compilation error will occur.
Counterexample:
let change: Int64 = 10; @Provide[change] var message: String = "Hello" let change: String = "10" @Provide[change] var message: String = "Hello"Correct Example:
@Provide["10"] var message: String = "Hello" -
The variable decorated with @Consume cannot be initialized locally or via constructor parameters. Initialization of @Consume variables has no effect. @Consume can only be initialized by matching the corresponding @Provide variable via key.
Counterexample:
// Parent component @Entry @Component class EntryView { @Provide var message: String = "Hello"; func build() { Column() { Child() } } } @Component class Child { // Incorrect: Cannot initialize locally @Consume var msg: String = "Hello"; func build() { Text(this.msg) } }Correct Example:
@Component class Child { @Consume var num: Int64; func build() { Column() { Text("num value: ${this.num}") } } } // Parent component @Entry @Component class EntryView { // Correct @Provide var num: Int64 = 10; func build() { Column() { Text("num value: ${this.num}") Child() } } } -
When @Provide keys (variable names or aliases) are duplicated, @Consume will search upward in the component tree and bind to the nearest @Provide.
// If variable names are duplicated, \@Consume will bind to the last \@Provide-decorated variable. // Types must match; otherwise, the framework will throw a runtime error. @Provide var count: Int32 = 10 @Provide var count: Int64 = 10 // If aliases are duplicated, \@Consume will bind to the last \@Provide-decorated variable. // Similarly, types must match. @Provide["a"] var count: Int32 = 10 @Provide["a"] var num: Int64 = 10 -
When initializing a @Consume variable, if the developer does not define a corresponding @Provide variable with the matching key, the framework will throw a runtime error, indicating that initialization failed because no @Provide variable with the corresponding key (variable name or alias) could be found.
Counterexample:
@Component class Child{ @Consume var num: Int64 func build(){ Column(){ Text("num value: ${this.num}") } } } // Parent component @Entry @Component class EntryView{ // Incorrect: Missing @Provide var num: Int64 = 10; func build(){ Column(){ Text("num value: ${this.num}") Child() } } }Correct Example:
@Component class Child{ @Consume var num: Int64 func build(){ Column(){ Text("num value: ${this.num}") } } } // Parent component @Entry @Component class EntryView{ // Correct @Provide var num: Int64 = 10; func build(){ Column(){ Text("num value: ${this.num}") Child() } } } ```## Usage Scenarios
The following example demonstrates a two-way state synchronization scenario between parent and child components using @Provide and @Consume. When clicking the Button within the EntryView and ToDoItem components respectively, changes to count will be bidirectionally synchronized between EntryView and ToDoItem.
package ohos_app_cangjie_entry
import kit.ArkUI.*
import ohos.arkui.state_macro_manage.*
@Component
class ToDoItem {
// The @Consume-decorated variable binds to the @Provide-decorated variable in its ancestor component EntryView through the same property name
@Consume
var count: Int64;
func build(){
Column{
Text("count(${this.count})")
Button("count(${this.count}), count + 1")
.onClick({
evt => this.count += 1
})
}.width(100.percent)
}
}
@Component
class ToDoList{
func build(){
Row(space: 5){
ToDoItem()
ToDoItem()
}
}
}
@Component
class ToDoDemo{
func build(){
Column{
ToDoList()
}
}
}
@Entry
@Component
class EntryView{
// The @Provide-decorated variable index is provided by the entry component EntryView to its descendant components
@Provide
var count: Int64 = 0;
func build(){
Column{
Button("count(${this.count}), count + 1")
.onClick({
evt => this.count += 1
})
ToDoDemo()
}
}
}

Decorating Map-Type Variables
In the following example, message is of type HashMap<Int64, String>. Clicking the Button to change the value of message will trigger view updates.
package ohos_app_cangjie_entry
import kit.ArkUI.*
import ohos.arkui.state_macro_manage.*
import std.collection.HashMap
@Component
class Child {
@Consume
var message: HashMap<Int64, String> = HashMap<Int64, String>()
@Consume
var arr: Array<(Int64, String)>
func build(){
Column(){
ForEach(arr,
{ item: (Int64, String), idx: Int64 =>
Text("key: ${item[0]} value: ${item[1]}").fontSize(30)
Divider()
},
keyGeneratorFunc: {item: (Int64, String), idx: Int64
=> "${idx}_${item[0]}" + item[1]
})
Button("Consume init map").onClick({
evt =>
this.message = HashMap<Int64, String>([(0,"a"),(1,"b"),(3,"c")])
arr = message.toArray()
})
Button("Consume set new one").onClick({
evt =>
this.message.add(4,"d")
arr = message.toArray()
})
Button("Consume clear").onClick({
evt =>
this.message.clear()
arr = message.toArray()
})
Button("Consume replace the first item").onClick({
evt =>
this.message.add(0,"aa")
arr = message.toArray()
})
Button("Consume delete the first item").onClick({
evt =>
this.message.remove(0)
arr = message.toArray()
})
}
}
}
@Entry
@Component
class EntryView {
@Provide
var message: HashMap<Int64, String> = HashMap<Int64, String>([(0,"a"),(1,"b"),(3,"c")])
@Provide
var arr: Array<(Int64, String)> = []
public override func onPageShow(){
arr = message.toArray()
}
func build(){
Row(){
Column(){
Button("Provide init map").onClick({
evt =>
this.message = HashMap<Int64,String>([(0,"a"),(1,"b"),(3,"c"),(4,"d")])
arr = message.toArray()
})
Child()
}.width(100.percent)
}.height(100.percent)
}
}

Decorating Set-Type Variables
In the following example, message is of type HashSet<Int64>. Clicking the Button to change the value of message will trigger view updates.
package ohos_app_cangjie_entry
import kit.ArkUI.*
import ohos.arkui.state_macro_manage.*
import std.collection.*
@Component
class Child{
@Consume
var message: HashSet<Int64>
@Consume
var arr: Array<Int64>
func build(){
Column{
ForEach(arr,{item: Int64, idx: Int64 =>
Text("${item}").fontSize(30)
Divider()
})
Button("Consume init set").onClick({
evt =>
this.message = HashSet<Int64>([0,1,2,3,4])
this.arr = this.message.toArray()
})
Button("Consume set new one").onClick({
evt =>
this.message.add(5)
this.arr = this.message.toArray()
})
Button("Consume clear").onClick({
evt =>
this.message.clear()
this.arr = this.message.toArray()
})
Button("Consume delete the first one").onClick({
evt =>
this.message.remove(0)
this.arr = this.message.toArray()
})
}
}
}
@Entry
@Component
class EntryView{
public override func onPageShow(){
arr = message.toArray()
}
@Provide
var message: HashSet<Int64> = HashSet([0,1,2,3,4])
@Provide
var arr: Array<Int64> = []
func build(){
Row(){
Column(){
Button("Provide init Set").onClick({
evt =>
this.message = HashSet<Int64>([0,1,2,3,4,5])
this.arr = this.message.toArray()
})
Child()
}.width(100.percent)
}.height(100.percent)
}
}
