The metadata binding framework, developed based on the HarmonyOS SDK, supports binding between UI components and data sources. With this framework, you can bind UI components to data sources without a need to develop complex, duplicate code.
By walking through this codelab, you will learn how to use the APIs of the metadata binding framework to implement UI component and data source binding with a small amount of code. This codelab provides examples of binding simple UI components, UI container components, custom UI components, and custom data sources.
The procedure is as follows:
This codelab illustrates only the core code. You can download the complete code by referring to 10 References. The following figure shows the code structure of the entire project.
In dependencies of the build.gradle file of the module, add the reference to the metadata binding framework.
implementation 'com.huawei.middleplatform:ohos-metadata-annotation:1.0.0.0'
implementation 'com.huawei.middleplatform:ohos-metadata-binding:1.0.0.0'
annotationProcessor 'com.huawei.middleplatform:ohos-metadata-processor:1.0.0.0'
Add compilation options and enable annotation.
ohos {
compileOptions {
annotationEnabled true
}
}
Use the annotation @MetaDataApplication provided by the metadata binding framework in the declaration of the MyApplication class. In the annotation, set requireData to true and exportData to false, indicating that the application obtains data from but does not provide data to external systems.
@MetaDataApplication(requireData = true, exportData = false)
public class MyApplication extends AbilityPackage {
...
}
In the onInitialize method of the MyApplication class, call the initialization method of the metadata binding framework and pass the MyApplication.this context. This enables the framework to be initialized when the application starts.
@Override
public void onInitialize() {
super.onInitialize();
context = this.getContext();
MetaDataFramework.init(this);
}
By now, the environment is ready, and the introduction and initialization of the metadata binding framework are complete. You can then proceed to develop services in the desired scenario.
Define the metadata structure in the alarm_schema.json file.
{
"id": "com.example.meta-data.alarm",
"title": "alarm schema",
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "alarm description",
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"hour": {
"type": "integer"
},
"minutes": {
"type": "integer"
},
"daysofweek": {
"type": "integer"
},
"enabled": {
"type": "integer"
},
"message": {
"type": "string"
}
}
}
The metadata includes the ID, hour, minute, day of the week, alarm clock status, and alarm clock information. The day of the week is represented by a seven-digit binary integer, standing for Monday to Sunday from the least significant bit to the most significant bit. If the value of the nth digit is 1, the alarm clock is enabled in the nth day of the week; otherwise, the alarm clock is disabled in that day.
The following figure shows the UI effect to be implemented.
Create the layout file single_alarm.xml and add the namespace of the metadata binding framework to the definition of the outermost UI component in this layout file.
xmlns:metaDataBinding="http://schemas.huawei.com/res/metaDatabinding"
Create a metadata entity.
<request-meta-data
name="ClockMetaData"
schema="com.example.meta-data.alarm"
uri="dataability:///com.huawei.metadatabindingdemo.db.AlarmsDataAbility"/>
Use the metadata in the UI component.
<TimePicker
ohos:id="$+id:time_picker"
...
metaDataBinding:hour="@={ClockMetaData.hour}"
metaDataBinding:minute="@={ClockMetaData.minutes}"/>
<TextField
ohos:id="$+id:clock_name_text"
...
metaDataBinding:text="@={ClockMetaData.message}"/>
<Text
ohos:id="$+id:show_alarm_name"
ohos:align_parent_left="true"
...
metaDataBinding:text="@{ClockMetaData.message}"/>
<Text
ohos:id="$+id:show_alarm_time"
ohos:align_parent_right="true"
...
metaDataBinding:text="@{ClockMetaData.hour} + ':' + @{ClockMetaData.minutes}"/>
Request binding in the onStart method in alarm/simple_ui/SingleAlarmSlice.java.
// Create a metadata request object.
MetaDataRequestInfo request = new MetaDataRequestInfo.Builder()
.setMetaDataClass("ClockMetaData", ClockRowMetaData.class)
.setSyncRequest("ClockMetaData", true)
.build();
MetaDataBinding binding;
Component mainComponent;
try {
// Request binding.
binding = SinglealarmMetaDataBinding.requestBinding(this, request, null);
// Obtain the UI component to bind.
mainComponent = binding.getLayoutComponent();
} catch (DataSourceConnectionException e) {
mainComponent = LayoutScatter.getInstance(this)
.parse(ResourceTable.Layout_default_error, null, false);
}
setUIContent((ComponentContainer) mainComponent);
In this way, the data source is bound to the simple UI, as shown below:
Create the layout file alarm_list_slice.xml.
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:background_element="$color:colorAppBackground"
ohos:orientation="vertical">
<request-meta-data
class="com.huawei.metadatabindingdemo.alarm.metadata.ClockRowMetaData"
name="ClockMetaData"
schema="com.example.meta-data.alarm"/>
<!-- title -->
<DependentLayout...>
<!-- list -->
<ListContainer
ohos:id="$+id:list_view"
...
ohos:top_padding="18vp"/>
</DirectionalLayout>
Request binding to the data source in the AlarmListSlice class.
MetaDataRequestInfo request = new MetaDataRequestInfo.Builder()
.setRequestSource("ClockMetaData",
"dataability:///com.huawei.metadatabindingdemo.db.AlarmsDataAbility")
.setSyncRequest("ClockMetaData", false)
.build();
Component mainComponent;
try {
binding = AlarmlistsliceMetaDataBinding.requestBinding(this, request, this);
mainComponent = binding.getLayoutComponent();
} catch (DataSourceConnectionException e) {
mainComponent = LayoutScatter.getInstance(this)
.parse(ResourceTable.Layout_default_error, null, false);
}
setUIContent((ComponentContainer) mainComponent);
In this example, setSyncRequest is set to false, indicating an asynchronous request. Set dataCallback to this in the requestBinding method, because IMetaDataObserver has been implemented in the AlarmListSlice class.
The two callbacks are implemented as follows:
@Override
public void onDataLoad(List<MetaData> metaDatas, MetaDataRequestInfo.RequestItem requestItem) {
if (metaDatas == null || requestItem == null) { return;}
if (listContainer != null) {
itemProvider.initData(createAlarms(this, metaDatas));
listContainer.setItemProvider(itemProvider);
}
}
@Override
public void onDataChange(List<MetaData> addedMetaData, List<MetaData> updatedMetaData,
List<MetaData> deletedMetaData, MetaDataRequestInfo.RequestItem requestItem) {
if (addedMetaData == null) { return;}
itemProvider.addItems(createAlarms(this, addedMetaData));
}
private List<AlarmRow> createAlarms(AbilitySlice context, List<MetaData> dataList) {
List<AlarmRow> list = new ArrayList<>();
for (MetaData metaData : dataList) {
AlarmRow item = new AlarmRow(context, metaData);
list.add(item);
}
return list;
}
The display effect of items in the list is as follows:
Create the layout file alarm_row.xml for the list items.
<?xml version="1.0" encoding="utf-8"?><DependentLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
xmlns:metaDataBinding="http://schemas.huawei.com/res/metaDatabinding"
...
ohos:orientation="vertical">
<using-meta-data
class="com.huawei.metadatabindingdemo.alarm.metadata.ClockRowMetaData"
name="ClockMetaData"
schema="com.example.meta-data.alarm"/>
<DependentLayout
ohos:id="$+id:double_line_text_area"...>
<Text
ohos:id="$+id:timezone"
...
metaDataBinding:text="*{ClockMetaData.getTimeZone(@{ClockMetaData.hour})}"/>
<Text
ohos:id="$+id:time"
...
metaDataBinding:text="@{ClockMetaData.hour} + ':' + @{ClockMetaData.minutes}"/>
<Text
ohos:id="$+id:message"
...
metaDataBinding:text="*{ClockMetaData.toMessage(@{ClockMetaData.message},@{ClockMetaData.daysofweek})}"/>
</DependentLayout>
<DependentLayout
ohos:id="$+id:double_line_switch_hot_area"...>
<Image
ohos:id="$+id:switch_enable_button"
...
metaDataBinding:image_src="@{ClockMetaData.enabled} == 1 ? ${Media_icon_switch_enabled} : ${Media_icon_switch_disabled}"
metaDataBinding:onClick="#{ClockMetaData.enabled = (@{ClockMetaData.enabled} == 1 ? 0 : 1)}"/>
</DependentLayout>
</DependentLayout>
Multiple expressions for referencing metadata are used. The usage is as follows:
Identifier | Description | Example |
@ | Obtains the value of the metadata attribute and assigns it to the UI attribute for unidirectional binding. | metaDataBinding:text="@{ClockMetaData.message}" |
@= | Implements bidirectional binding between the value of the metadata attribute and the UI attribute. | metaDataBinding:text="@={ClockMetaData.message}" |
* | Binds a custom function. | metaDataBinding:text="*{ClockMetaData.getTimeZone(@{ClockMetaData.hour})}" |
$ | Binds a resource file. | metaDataBinding:image_src="@{ClockMetaData.enabled} == 1 ? ${Media_icon_switch_enabled} : ${Media_icon_switch_disabled}" |
# | Assigns a value to the metadata based on click events. | metaDataBinding:onClick="#{ClockMetaData.enabled = (@{ClockMetaData.enabled} == 1 ? 0 : 1)}" |
Create an AlarmRow class to represent a list item. This class holds a metadata object and provides methods for obtaining UI components, binding UI components, and responding to click events.
public class AlarmRow {
private final AbilitySlice context;
private final ClockRowMetaData clockMeta;
public AlarmRow(AbilitySlice context, MetaData clockMeta) {
this.context = context;
this.clockMeta = (ClockRowMetaData) clockMeta;
}
public Component createComponent() {
AlarmrowMetaDataBinding metaBinding = AlarmrowMetaDataBinding.createBinding(context, clockMeta);
Component comp = metaBinding.getLayoutComponent();
comp.setTag(metaBinding);
return comp;
}
public void bindComponent(Component component) {
AlarmrowMetaDataBinding metaBinding = (AlarmrowMetaDataBinding) component.getTag();
metaBinding.reBinding(component, clockMeta);
}
public void onClick() {
context.present(new AlarmEditSlice(clockMeta), new Intent());
}
}
Create the AlarmListProvider class, with the getComponent method overridden, to bind the list item component to the metadata.
public Component getComponent(int position, Component component, ComponentContainer componentContainer) {
AlarmRow alarm = alarmList.get(alarmIndex);
if (component == null) {
Component newComponent = alarm.createComponent();
return newComponent;
} else {
alarm.bindComponent(component);
return component;
}
}
In the AlarmEditSlice class, bind the passed metadata to the current page and listen for the click events of the Back and Save buttons. When you click Back, the metadata rollback is invoked to roll back the operation. When you click Save, the data is submitted and backed up.
metaBinding = AlarmdetailMetaDataBinding.createBinding(this, clockMeta);
Component comp = metaBinding.getLayoutComponent();
DependentLayout dependentLayout = (DependentLayout) comp.findComponentById(ResourceTable
.Id_title_area_back_icon_hot_area);
dependentLayout.setClickedListener(component -> {
clockMeta.rollback();
this.terminate();
});
comp.findComponentById(ResourceTable.Id_save).setClickedListener(component -> {
clockMeta.commit();
clockMeta.stash();
this.terminate();
});
Add a click event listener for the + button in the upper right corner of the page. After you click the button, a data record is added and saved in the list and data source. The added data record indicates that the alarm clock is enabled and the alarm clock time is 12:12 on Wednesday and Thursday. The code for adding the record is as follows:
public static void insertAnAlarm(MetaDataBinding binding) {
MetaDataRequestInfo.RequestItem requestItem = binding.getRequestInfo().getRequestItem("ClockMetaData");
MetaData metaData = AlarmlistsliceMetaDataBinding.createMetaData(requestItem);
metaData.put(COL_HOUR, DEFAULT_HOUR);
metaData.put(COL_MINUTE, DEFAULT_MINUTES);
metaData.put(COL_DAYS, DEFAULT_DAYS_OF_WEEK);
metaData.put(COL_ENABLE, DEFAULT_ENABLE);
metaData.put("message", "count" + count);
binding.addMetaData(metaData, requestItem);
count++;
}
The display effect is as follows:
The metadata binding framework supports binding of the following components and attributes.
Module | Attribute |
All components | alpha, enabled, focusable, visibility, and onClick |
Button | text, text_color, and text_size |
Checkbox | checked |
Image | image_src |
ProgressBar | progress |
RadioContainer | marked |
Slider | progress |
Switch | checked |
TabList | orientation |
TextField | hint |
Text | text, text_color, and text_size |
TimePicker | hour, minute, and second |
For custom UI components, the metadata binding framework provides the BindingComponent annotation, BindingTag annotation, and Operator API.
In the custom_ui directory, create a square component and bind its color to the data source. Then create a custom RadioButton component and bind its color to the data source.
For details about the code used to draw the square component, see the source code.
The method for setting the color of the square component is as follows:
public void setColor(boolean isGreen) {
this.isGreen = isGreen;
if (isGreen) {
rectPaint.setColor(Color.GREEN);
} else {
rectPaint.setColor(Color.YELLOW);
}
invalidate();
}
The RadioButton component inherits from the RadioButton class, with the constructor overridden.
Implement bidirectional binding between a Switch component and the bool_attr element of the metadata. The Switch component is used to assign a value to the metadata. The JSON Schema file of metadata is custom_schema.json.
The layout file custom_operator.xml is as follows:
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
xmlns:metaDataBinding="http://schemas.huawei.com/res/metaDatabinding"
...
>
<using-meta-data
class="com.huawei.metadatabindingdemo.custom_ui.metadata.CustomMetaData"
name="MetaData"
schema="com.example.meta-data.custom"/>
<Switch
ohos:id="$+id:switch_color"
...
metaDataBinding:checked="@={MetaData.bool_attr}"/>
<com.hauwei.metadatabindingdemo.custom_ui.component.MyRadioButton
ohos:id="$+id:test_my_radiobt"
...
metaDataBinding:text_color_off2="*{MetaData.getColorOff(@{MetaData.bool_attr})}"
metaDataBinding:text_color_on2="*{MetaData.getColorOn(@{MetaData.bool_attr})}"/>
<com.huawei.metadatabindingdemo.custom_ui.component.MySquare
ohos:id="$+id:test_my_comp"
...
metaDataBinding:enabled2="@{MetaData.bool_attr}"/>
</DirectionalLayout>
Create a metadata object in CustomOperatorSlice and bind the metadata object to the UI component.
metaData = new CustomMetaData();
metaData.setPrimaryKey("id");
metaData.setName("MetaData");
metaData.put("bool_attr", false);
MetaDataBinding binding;
binding = CustomoperatorMetaDataBinding.createBinding(this, metaData);
Component mainComponent;
mainComponent = binding.getLayoutComponent();
setUIContent((ComponentContainer) mainComponent);
Adapt to the square component:
@BindingComponent(component ="com.huawei.metadatabindingdemo.custom_ui.component.MySquare")
public class Custom1Operator {
@BindingTag(attr = "enabled2", type = "java.lang.Boolean")
public static Operator<Boolean> SetColorOperator = new Operator<Boolean>() {
@Override
public void operate(Component component, Boolean value) {
((MySquare) component).setColor(value);
}
};
}
Adapt to the custom RadioButton component.
@BindingComponent(component ="com.huawei.metadatabindingdemo.custom_ui.component.MyRadioButton")
public class Custom2Operator {
@BindingTag(attr = "text_color_on2", type = "ohos.agp.utils.Color")
public static Operator<Color> SetOnColor = new Operator<Color>() {
@Override
public void operate(Component component, Color value) {
((MyRadioButton)component).setTextColorOn(value);
}
};
@BindingTag(attr = "text_color_off2", type = "ohos.agp.utils.Color")
public static Operator<Color> SetOffColor = new Operator<Color>() {
@Override
public void operate(Component component, Color value) {
((MyRadioButton)component).setTextColorOff(value);
}
};
}
Now the metadata binding framework can recognize the two custom components.
The metadata binding framework uses CustomDao.ICustomMetaDataHandler and SimpleDao.ISimpleMetaDataHandler to support custom data sources. Since ICustomMetaDataHandler can be used to fetch multiple pieces of metadata, we use it to implement a list container. The layout file has been elaborated in the previous sections and it is not described here. In this example, we use the database of the alarm clock list as the data source and add a new table to the database. For details, see the source code in the custom_data_source directory. The following shows only the implementation of CustomDao.ICustomMetaDataHandler.
public class MyDataHandler implements CustomDao.ICustomMetaDataHandler {
private DataAbilityHelper mDbHelper = null;
private MetaDataRequestInfo.RequestItem mReqItem = null;
@Override
public boolean onConnect(CustomDao dao) {
Context mCtx = MetaDataFramework.appContext;
this.mDbHelper = DataAbilityHelper.creator(mCtx);
this.mReqItem = dao.getRequestItem();
return true;
}
@Override
public List<MetaData> onQuery(CustomDao dao, boolean isSync, String[] properties, PacMap predicates) {
ArrayList<MetaData> list = new ArrayList<>();
try {ResultSet resultSet = mDbHelper.query(NotesDataAbility.NOTE_URI, null, null);
if (resultSet != null) {
boolean hasData = resultSet.goToFirstRow();
if (!hasData) {
return list;
}
do {
list.add(getQueryResults(resultSet)); // ResultSet to MetaData
}while (resultSet.goToNextRow());
}
} catch (DataAbilityRemoteException e) {
MyLog.error("ohosTest: SimpleHandler onQuery error");
}
return list;
}
@Override
public void onAdd(MetaData metaData) {}
@Override
public void onDelete(MetaData metaData) {}
@Override
public void onChange(MetaData metaData, String key, Object value) {}
@Override
public void onFlush(MetaData metaData) {}
@Override
public void onDisconnect(CustomDao dao) {}
}
CustomDao.ICustomMetaDataHandler provides the functions of connecting, disconnecting, adding, deleting, modifying, and querying metadata. You only need to implement these methods to perform operations on the data source. In this way, the metadata binding framework binds the UI component to the data source.
Well done. You have completed this codelab and learned how to:
You can download the complete code of this project from MetaDataBindingDemo.