A service widget is a UI component used to present important information or operations of a Feature Ability (FA). It allows quick access to a desired application service, without the need to open the associated application to find the service. This codelab describes how to develop a clock FA service widget application on HarmonyOS. This service widget has two layouts: 2 x 2 and 2 x 4, as shown in the following figure.

The procedure is as follows:

This codelab describes how to create, update, and delete a service widget, how to use the object-relational mapping (ORM) database, and how to start the timer service. The code structure of the sample project is as follows:

A service widget application is a special ability service. You need to declare the following attributes in the config.json file for the system to identify the service widget application and bind it. The following sample code shows the configuration of the forms module in abilities of the config.json file:

"forms": [ { "landscapeLayouts": [ "$layout:form_image_with_info_date_card_2_2", "$layout:form_image_with_info_date_card_2_4" ], "isDefault": true, "scheduledUpdateTime": "10:30", "defaultDimension": "2*2", "name": "DateCard", "description": "This is a service widget", "colorMode": "auto", "type": "Java", "supportDimensions": [ "2*2", "2*4" ], "portraitLayouts": [ "$layout:form_image_with_info_date_card_2_2", "$layout:form_image_with_info_date_card_2_4" ], "updateEnabled": true, "updateDuration": 1, "formVisibleNotify": true } ]

The service widget supports two layouts: 2 x 2 and 2 x 4, as shown in the following figures.

The following uses the 2 x 2 layout as an example. The 2 x 2 service widget displays the date, time, and week from top down. The DependentLayout has four embedded DirectionalLayout components, each of which uses the Text component. The sample code is as follows:

<?xml version="1.0" encoding="utf-8"?> <DependentLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:height="match_parent" ohos:width="match_parent" ohos:background_element="#6A9F99" ohos:remote="true"> <DirectionalLayout ohos:height="match_content" ohos:width="match_parent" ohos:orientation="vertical" > <Text...> </DirectionalLayout> <DirectionalLayout ohos:id="$+id:title" ohos:height="match_content" ohos:width="match_parent" ohos:alignment="horizontal_center" ohos:orientation="horizontal" ohos:top_margin="35fp" > <Text...> <Text...> <Text...> </DirectionalLayout> <DirectionalLayout ohos:id="$+id:time" ohos:height="match_content" ohos:width="match_parent" ohos:alignment="horizontal_center" ohos:below="$id:title" ohos:orientation="horizontal" ohos:top_margin="0.5fp" > <Text...> <Text...> <Text...> <Text...> <Text...> </DirectionalLayout> <DirectionalLayout ohos:height="match_content" ohos:width="match_parent" ohos:alignment="center" ohos:below="$id:time" ohos:margin="20fp" ohos:orientation="horizontal" > <Text...> <Text...> <Text...> <Text...> <Text...> <Text...> <Text...> </DirectionalLayout> </DependentLayout>

Creating a Service Widget Database

In this codelab, an ORM database (FormDatabase) and a table (Form) are created to store information such as the service widget ID and name.
Define the database class FormDatabase.java. Create the Form table and set the version to 1. The sample code is as follows:

@Database( entities = {Form.class}, version = 1) public abstract class FormDatabase extends OrmDatabase {}

Define the entity class Form.java for the table form in the database. This class contains three fields: formId (service widget ID, the primary key), formName (service widget name), and dimension (service widget dimensions). The sample code is as follows:

@Entity(tableName = "form") public class Form extends OrmObject { @PrimaryKey() private Long formId; private String formName; private Integer dimension; public Form(Long formId, String formName, Integer dimension) { this.formId = formId; this.formName = formName; this.dimension = dimension; } // You can add getters and setters for the fields or refer to the complete code. }

Initializing the Service Widget Application

After the service widget application is installed and started, the page corresponding to MainAbility is displayed. When onStart is called, the TimerAbility will be triggered to update the clock service widget. The sample code is as follows:

@Override public void onStart(Intent intent) { super.onStart(intent); connect = helper.getOrmContext("FormDatabase", "FormDatabase.db", FormDatabase.class); // Start TimerAbility. Intent intentService = new Intent(); Operation operation = new Intent.OperationBuilder() .withDeviceId("") .withBundleName("com.huawei.cookbooks") .withAbilityName("com.huawei.cookbooks.TimerAbility") .build(); intentService.setOperation(operation); startAbility(intentService); super.setMainRoute(ClockCardSlice.class.getName()); }

When a service widget client requests a service widget, the service widget provider is started and invokes the onCreateForm callback to complete service widget initialization. The sample code in MainAbility is as follows:

@Override protected ProviderFormInfo onCreateForm(Intent intent) { if (intent == null) { return new ProviderFormInfo(); } // Obtain the service widget ID. formId = INVALID_FORM_ID; if (intent.hasParameter(AbilitySlice.PARAM_FORM_IDENTITY_KEY)) { formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_FORM_ID); } else { return new ProviderFormInfo(); } // Obtain the service widget name. String formName = EMPTY_STRING; if (intent.hasParameter(AbilitySlice.PARAM_FORM_NAME_KEY)) { formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY); } // Obtain the service widget dimensions. int dimension = DEFAULT_DIMENSION_2X2; if (intent.hasParameter(AbilitySlice.PARAM_FORM_DIMENSION_KEY)) { dimension = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, DEFAULT_DIMENSION_2X2); } int layoutId = ResourceTable.Layout_form_image_with_info_date_card_2_2; if (dimension == DEFAULT_DIMENSION_2X4) { layoutId = ResourceTable.Layout_form_image_with_info_date_card_2_4; } formInfo = new ProviderFormInfo(layoutId, this); // Store service widget information. Form form = new Form(formId, formName, dimension); ComponentProvider componentProvider = ComponentProviderUtils.getComponentProvider(form, this); formInfo.mergeActions(componentProvider); if (connect == null) { connect = helper.getOrmContext("FormDatabase", "FormDatabase.db", FormDatabase.class); } try { DatabaseUtils.insertForm(form, connect); } catch (Exception e) { DatabaseUtils.deleteFormData(form.getFormId(), connect); } return formInfo; }

You must override the onDeleteForm method, which is called when a service widget is deleted. The sample code for deleting a service widget based on the service widget ID is as follows:

@Override protected void onDeleteForm(long formId) { super.onDeleteForm(formId); // Delete service widget information from the database. DatabaseUtils.deleteFormData(formId, connect); }

Widget Data Service

Create a service ability to periodically update service widget information. The sample code in TimerAbility.java is as follows:

@Override public void onStart(Intent intent) { connect = helper.getOrmContext("FormDatabase", "FormDatabase.db", FormDatabase.class); startTimer(); super.onStart(intent); } // A timer used to update the service widget every second. private void startTimer() { Timer timer = new Timer(); timer.schedule( new TimerTask() { @Override public void run() { updateForms(); notice(); } }, 0,SEND_PERIOD); } private void updateForms() { // Obtain service widget information from the database. OrmPredicates ormPredicates = new OrmPredicates(Form.class); List<Form> formList = connect.query(ormPredicates); // Update the hour, minute, and second. if (formList.size() > 0) { for (Form form : formList) { // Traverse the service widget list to update service widget information. ComponentProvider componentProvider = ComponentProviderUtils.getComponentProvider(form, this); try { Long updateFormId = form.getFormId(); updateForm(updateFormId, componentProvider); } catch (FormException e) { // Delete the service widgets that do not exist. DatabaseUtils.deleteFormData(form.getFormId(), connect); HiLog.error(LABEL_LOG, "onUpdateForm updateForm error"); } } } }

Foreground Service

Set a foreground service. Use the foreground service and the Optimizer of your phone to prevent ServiceAbility from being destroyed by the system. The sample code is as follows:

private void notice() { // Create a notification. NotificationRequest request = new NotificationRequest(NOTICE_ID); request.setAlertOneTime(true); NotificationRequest.NotificationNormalContent content = new NotificationRequest.NotificationNormalContent(); content.setText(DateUtils.getCurrentDate("yyyy-MM-dd HH:mm:ss")); NotificationRequest.NotificationContent notificationContent = new NotificationRequest.NotificationContent(content); request.setContent(notificationContent); // Bind the notification to the service widget. keepBackgroundRunning(NOTICE_ID, request); }

Updating the Service Widget Components

Encapsulate the ComponentProviderUtils class. Call the updateForm method to pass the formId and componentProvider fields to update the date, time, and days of the week in real time. The sample code and the effect are as follows:

public static ComponentProvider getComponentProvider(Form form, Context context) { int layoutId = ResourceTable.Layout_form_image_with_info_date_card_2_2; if (form.getDimension() == DIM_VERSION) { layoutId = ResourceTable.Layout_form_image_with_info_date_card_2_4; } ComponentProvider componentProvider = new ComponentProvider(layoutId, context); setComponentProviderValue(componentProvider); return componentProvider; } // Assign values to clock components. private static void setComponentProviderValue(ComponentProvider componentProvider) { Calendar now = Calendar.getInstance(); int hour = now.get(Calendar.HOUR_OF_DAY); int min = now.get(Calendar.MINUTE); int second = now.get(Calendar.SECOND); String hourString = int2String(hour); String minString = int2String(min); String secondString = int2String(second); componentProvider.setText(ResourceTable.Id_date, DateUtils.getCurrentDate("yyyy-MM-dd")); componentProvider.setText(ResourceTable.Id_hour, hourString); componentProvider.setText(ResourceTable.Id_min, minString); componentProvider.setText(ResourceTable.Id_sec, secondString); // Obtain the current day of the week. int weekDayId = getWeekDayId(); componentProvider.setTextColor(weekDayId, nowWeekColor); // Change the previous day to the original color. int lastWeekId = getLastWeekDayId(); componentProvider.setTextColor(lastWeekId, primaryWeekColor); }

Well done! You have completed the development of the clock service widget application and learned how to:

  1. Use Java service widget APIs.
  2. Configure the config.json file for the service widget.

Source code on Gitee

Source code on GitHub

Code copied