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:

You can use the following devices to complete operations in this codelab:

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.

Introducing the Metadata Binding Framework to Your 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 } }

Using and Initializing the Metadata Binding Framework

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.

Defining the Metadata Structure

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.

Using Metadata in a Layout File

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}"/>

Implementing Binding Using Code

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:

Creating a Layout File

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>

Obtaining Metadata from a Data Source

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; }

Binding List Items to Metadata

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(); });

Adding a Record

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.

Code copied