Beginner: Page Ability features in Huawei Harmony OS - Huawei Developers

Introduction
In this article, we can create an application showing below features:
1. Page Ability and Ability Slice
2. Page Ability life cycle and Ability Slice life cycle
3. Switching between Ability slices
4. Switching between abilities.
5. Transfer data between abilities.
Requirements
1. DevEco IDE
2. Wearable watch (Can use simulator also)
Harmony OS Supports various 2 types of abilities
1. Feature Ability
2. Particle Ability
In this article, we will test Feature Ability template called Page template.
UI Design
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Ability Slice:
An Ability Slice represents a single screen and its control logic.
Page Template (Page Abilities):
Page template is used by Feature ability to interact with users, one page template can contain one or more Ability Slices. Like shown below.
When a Page ability appears in the foreground, it presents one of its ability slices by default.
config.json
I have declared 2 abilities with type page.
JSON:
{
"app": {
"bundleName": "com.example.threadingsample",
"vendor": "example",
"version": {
"code": 1,
"name": "1.0"
},
"apiVersion": {
"compatible": 3,
"target": 3
}
},
"deviceConfig": {},
"module": {
"package": "com.example.threadingsample",
"name": ".MyApplication",
"deviceType": [
"wearable"
],
"distro": {
"deliveryWithInstall": true,
"moduleName": "entry",
"moduleType": "entry"
},
"abilities": [
{
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
],
"orientation": "landscape",
"name": "com.example.threadingsample.MainAbility",
"icon": "$media:icon",
"description": "$string:mainability_description",
"label": "ThreadingSample",
"type": "page",
"launchType": "standard"
},
{
"orientation": "landscape",
"name": "com.example.threadingsample.second.SecondAbility",
"icon": "$media:icon",
"description": "$string:mainability_description",
"label": "SecondAbility",
"type": "page",
"launchType": "standard"
}
]
}
}
Page Ability life cycle
For more information: https://developer.harmonyos.com/en/...uides/ability-page-lifecycle-0000000000029840
Ability Slice life cycle:
An ability slice's lifecycle is bound to the Page ability that hosts it. You must override the onStart() callback of ability slices and use setUIContent() to set the UI content to display in this callback.
Switching between slices
Add the below code in MainAbilitySlice.java
Java:
package com.example.threadingsample.slice;
import com.example.threadingsample.ResourceTable;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.Text;
import ohos.app.dispatcher.TaskDispatcher;
import ohos.app.dispatcher.task.TaskPriority;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
public class MainAbilitySlice extends AbilitySlice {
static final HiLogLabel LABEL_LOG = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MY_TAG");
Text text;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
HiLog.info(LABEL_LOG, "MainAbilitySlice->"+Thread.currentThread().getName());
text = (Text) findComponentById(ResourceTable.Id_text);
Button launchNewSlice = (Button) findComponentById(ResourceTable.Id_button_launch_new_slice);
launchNewSlice.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
present(new ChildAbilitySlice(), new Intent());
}
});
Button launchNewAbility = (Button) findComponentById(ResourceTable.Id_button_launch_new_ability);
launchNewAbility.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
HiLog.info(LABEL_LOG, "MainAbilitySlice launch new [email protected]@->"+Thread.currentThread().getName());
Intent intent = new Intent();
// Use the OperationBuilder class of Intent to construct an Operation object and set the deviceId (left empty if a local ability is required), bundleName, and abilityName attributes for the object.
Operation operation = new Intent.OperationBuilder()
.withDeviceId("")
.withBundleName("com.example.threadingsample")
.withAbilityName("com.example.threadingsample.second.SecondAbility")
.build();
intent.setParam("TEST_KEY", "apple");
// Set the created Operation object to the Intent as its operation attribute.
intent.setOperation(operation);
startAbility(intent);
}
});
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}
Page Ability life cycle
For more information: https://developer.harmonyos.com/en/...uides/ability-page-lifecycle-0000000000029840
Ability Slice life cycle:
An ability slice's lifecycle is bound to the Page ability that hosts it. You must override the onStart() callback of ability slices and use setUIContent() to set the UI content to display in this callback.
Switching between slices
Add the below code in MainAbilitySlice.java
Java:
package com.example.threadingsample.slice;
import com.example.threadingsample.ResourceTable;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.Text;
import ohos.app.dispatcher.TaskDispatcher;
import ohos.app.dispatcher.task.TaskPriority;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
public class MainAbilitySlice extends AbilitySlice {
static final HiLogLabel LABEL_LOG = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MY_TAG");
Text text;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
HiLog.info(LABEL_LOG, "MainAbilitySlice->"+Thread.currentThread().getName());
text = (Text) findComponentById(ResourceTable.Id_text);
Button launchNewSlice = (Button) findComponentById(ResourceTable.Id_button_launch_new_slice);
launchNewSlice.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
present(new ChildAbilitySlice(), new Intent());
}
});
Button launchNewAbility = (Button) findComponentById(ResourceTable.Id_button_launch_new_ability);
launchNewAbility.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
HiLog.info(LABEL_LOG, "MainAbilitySlice launch new [email protected]@->"+Thread.currentThread().getName());
Intent intent = new Intent();
// Use the OperationBuilder class of Intent to construct an Operation object and set the deviceId (left empty if a local ability is required), bundleName, and abilityName attributes for the object.
Operation operation = new Intent.OperationBuilder()
.withDeviceId("")
.withBundleName("com.example.threadingsample")
.withAbilityName("com.example.threadingsample.second.SecondAbility")
.build();
intent.setParam("TEST_KEY", "apple");
// Set the created Operation object to the Intent as its operation attribute.
intent.setOperation(operation);
startAbility(intent);
}
});
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}
Add below code in ability_main.xml
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:orientation="vertical"
ohos:background_element="#8c7373"
ohos:padding="32">
<Text
ohos:multiple_lines="true"
ohos:id="$+id:text"
ohos:height="match_content"
ohos:width="200"
ohos:layout_alignment="horizontal_center"
ohos:text="Text"
ohos:text_size="10fp"/>
<Button
ohos:id="$+id:button_launch_new_slice"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$graphic:background_button"
ohos:layout_alignment="horizontal_center"
ohos:padding="5"
ohos:text="Launch new Slice"
ohos:text_size="30"
ohos:top_margin="5"/>
<Button
ohos:id="$+id:button_launch_new_ability"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$graphic:background_button"
ohos:layout_alignment="horizontal_center"
ohos:padding="5"
ohos:text="Launch new Ability"
ohos:text_size="30"
ohos:top_margin="5"/>
</DirectionalLayout>
Add the below code in ChildAbilitySlice.java
Java:
package com.example.threadingsample.slice;
import com.example.threadingsample.ResourceTable;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.Text;
import ohos.app.dispatcher.TaskDispatcher;
import ohos.app.dispatcher.task.TaskPriority;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
public class ChildAbilitySlice extends AbilitySlice {
static final HiLogLabel LABEL_LOG = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MY_TAG");
Text text;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_child_slice_two);
HiLog.info(LABEL_LOG, "ChildAbilitySlice->"+Thread.currentThread().getName());
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}
Add the below code in child_slice_two.xml
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:orientation="vertical"
ohos:background_element="#8c7373"
ohos:padding="32">
<Text
ohos:multiple_lines="true"
ohos:id="$+id:text"
ohos:height="match_content"
ohos:width="200"
ohos:layout_alignment="horizontal_center"
ohos:text="Child Slice"
ohos:text_size="10fp"/>
</DirectionalLayout>
Switch from MainAbilitySlice to ChildAbilitySlice using present method.
Java:
Button launchNewSlice = (Button) findComponentById(ResourceTable.Id_button_launch_new_slice);
launchNewSlice.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
present(new ChildAbilitySlice(), new Intent());
}
});
Switching from MainAbility to SecondAbility.
Java:
Button launchNewAbility = (Button) findComponentById(ResourceTable.Id_button_launch_new_ability);
launchNewAbility.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
HiLog.info(LABEL_LOG, "MainAbilitySlice launch new [email protected]@->"+Thread.currentThread().getName());
Intent intent = new Intent();
// Use the OperationBuilder class of Intent to construct an Operation object and set the deviceId (left empty if a local ability is required), bundleName, and abilityName attributes for the object.
Operation operation = new Intent.OperationBuilder()
.withDeviceId("")
.withBundleName("com.example.threadingsample")
.withAbilityName("com.example.threadingsample.second.SecondAbility")
.build();
intent.setParam("TEST_KEY", "apple");
// Set the created Operation object to the Intent as its operation attribute.
intent.setOperation(operation);
startAbility(intent);
}
});
Add the below code to SecondAbility.java
Java:
package com.example.threadingsample.second;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
public class SecondAbility extends Ability {
private static final int MY_PERMISSIONS_REQUEST_LOCATION = 1001;
static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MY_TAG");
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setMainRoute(SecondAbilitySlice.class.getName());
}
}
Add the below code in SecondAbilitySlice.java
Java:
package com.example.threadingsample.second;
import com.example.threadingsample.ResourceTable;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.components.Text;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
public class SecondAbilitySlice extends AbilitySlice {
static final HiLogLabel LABEL_LOG = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MY_TAG");
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_two);
HiLog.info(LABEL_LOG, "inside SecondAbilitySlice!!");
if(getAbility() != null) {
if (getAbility().getIntent() != null) {
if (getAbility().getIntent().hasParameter("TEST_KEY")) {
String valueFromFirstAbility = getAbility().getIntent().getStringParam("TEST_KEY");
HiLog.info(LABEL_LOG, "inside [email protected]@-->"+valueFromFirstAbility);
Text text = (Text) findComponentById(ResourceTable.Id_text);
text.setText(valueFromFirstAbility);
} else {
HiLog.info(LABEL_LOG, "TEST_KEY parameter is not present");
}
} else {
HiLog.info(LABEL_LOG, "intent is null");
}
}else{
HiLog.info(LABEL_LOG, "ability is null");
}
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}
Add the below code in ability_two.xml
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:orientation="vertical"
ohos:background_element="#8c7373"
ohos:padding="32">
<Text
ohos:multiple_lines="true"
ohos:id="$+id:text"
ohos:height="match_content"
ohos:width="200"
ohos:layout_alignment="horizontal_center"
ohos:text="Ability2"
ohos:text_size="10fp"/>
</DirectionalLayout>
Transfer data from one ability to another
Java:
Intent intent = new Intent();
intent.setParam("TEST_KEY", "apple");
Retrieve data on other ability
Java:
if(getAbility() != null) {
if (getAbility().getIntent() != null) {
if (getAbility().getIntent().hasParameter("TEST_KEY")) {
String valueFromFirstAbility = getAbility().getIntent().getStringParam("TEST_KEY");
HiLog.info(LABEL_LOG, "inside [email protected]@-->"+valueFromFirstAbility);
Text text = (Text) findComponentById(ResourceTable.Id_text);
text.setText(valueFromFirstAbility);
} else {
HiLog.info(LABEL_LOG, "TEST_KEY parameter is not present");
}
} else {
HiLog.info(LABEL_LOG, "intent is null");
}
}else{
HiLog.info(LABEL_LOG, "ability is null");
}
Tips and Tricks
All Abilities must be registered into config.json
Conclusion
In this article, we have UI components, there life cycle and navigation between them, transfer data between two pages.
Reference
Harmony Official document
DevEco Studio User guide
JS API Reference
Read In Forum

Does this Page ability is like Fragment in Android ?

Does
ask011 said:
Introduction
In this article, we can create an application showing below features:
1. Page Ability and Ability Slice
2. Page Ability life cycle and Ability Slice life cycle
3. Switching between Ability slices
4. Switching between abilities.
5. Transfer data between abilities.
Requirements
1. DevEco IDE
2. Wearable watch (Can use simulator also)
Harmony OS Supports various 2 types of abilities
1. Feature Ability
2. Particle Ability
In this article, we will test Feature Ability template called Page template.
UI Design
Ability Slice:
An Ability Slice represents a single screen and its control logic.
Page Template (Page Abilities):
Page template is used by Feature ability to interact with users, one page template can contain one or more Ability Slices. Like shown below.
When a Page ability appears in the foreground, it presents one of its ability slices by default.
config.json
I have declared 2 abilities with type page.
JSON:
{
"app": {
"bundleName": "com.example.threadingsample",
"vendor": "example",
"version": {
"code": 1,
"name": "1.0"
},
"apiVersion": {
"compatible": 3,
"target": 3
}
},
"deviceConfig": {},
"module": {
"package": "com.example.threadingsample",
"name": ".MyApplication",
"deviceType": [
"wearable"
],
"distro": {
"deliveryWithInstall": true,
"moduleName": "entry",
"moduleType": "entry"
},
"abilities": [
{
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
],
"orientation": "landscape",
"name": "com.example.threadingsample.MainAbility",
"icon": "$media:icon",
"description": "$string:mainability_description",
"label": "ThreadingSample",
"type": "page",
"launchType": "standard"
},
{
"orientation": "landscape",
"name": "com.example.threadingsample.second.SecondAbility",
"icon": "$media:icon",
"description": "$string:mainability_description",
"label": "SecondAbility",
"type": "page",
"launchType": "standard"
}
]
}
}
Page Ability life cycle
For more information: https://developer.harmonyos.com/en/...uides/ability-page-lifecycle-0000000000029840
Ability Slice life cycle:
An ability slice's lifecycle is bound to the Page ability that hosts it. You must override the onStart() callback of ability slices and use setUIContent() to set the UI content to display in this callback.
Switching between slices
Add the below code in MainAbilitySlice.java
Java:
package com.example.threadingsample.slice;
import com.example.threadingsample.ResourceTable;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.Text;
import ohos.app.dispatcher.TaskDispatcher;
import ohos.app.dispatcher.task.TaskPriority;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
public class MainAbilitySlice extends AbilitySlice {
static final HiLogLabel LABEL_LOG = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MY_TAG");
Text text;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
HiLog.info(LABEL_LOG, "MainAbilitySlice->"+Thread.currentThread().getName());
text = (Text) findComponentById(ResourceTable.Id_text);
Button launchNewSlice = (Button) findComponentById(ResourceTable.Id_button_launch_new_slice);
launchNewSlice.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
present(new ChildAbilitySlice(), new Intent());
}
});
Button launchNewAbility = (Button) findComponentById(ResourceTable.Id_button_launch_new_ability);
launchNewAbility.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
HiLog.info(LABEL_LOG, "MainAbilitySlice launch new [email protected]@->"+Thread.currentThread().getName());
Intent intent = new Intent();
// Use the OperationBuilder class of Intent to construct an Operation object and set the deviceId (left empty if a local ability is required), bundleName, and abilityName attributes for the object.
Operation operation = new Intent.OperationBuilder()
.withDeviceId("")
.withBundleName("com.example.threadingsample")
.withAbilityName("com.example.threadingsample.second.SecondAbility")
.build();
intent.setParam("TEST_KEY", "apple");
// Set the created Operation object to the Intent as its operation attribute.
intent.setOperation(operation);
startAbility(intent);
}
});
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}
Page Ability life cycle
For more information: https://developer.harmonyos.com/en/...uides/ability-page-lifecycle-0000000000029840
Ability Slice life cycle:
An ability slice's lifecycle is bound to the Page ability that hosts it. You must override the onStart() callback of ability slices and use setUIContent() to set the UI content to display in this callback.
Switching between slices
Add the below code in MainAbilitySlice.java
Java:
package com.example.threadingsample.slice;
import com.example.threadingsample.ResourceTable;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.Text;
import ohos.app.dispatcher.TaskDispatcher;
import ohos.app.dispatcher.task.TaskPriority;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
public class MainAbilitySlice extends AbilitySlice {
static final HiLogLabel LABEL_LOG = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MY_TAG");
Text text;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
HiLog.info(LABEL_LOG, "MainAbilitySlice->"+Thread.currentThread().getName());
text = (Text) findComponentById(ResourceTable.Id_text);
Button launchNewSlice = (Button) findComponentById(ResourceTable.Id_button_launch_new_slice);
launchNewSlice.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
present(new ChildAbilitySlice(), new Intent());
}
});
Button launchNewAbility = (Button) findComponentById(ResourceTable.Id_button_launch_new_ability);
launchNewAbility.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
HiLog.info(LABEL_LOG, "MainAbilitySlice launch new [email protected]@->"+Thread.currentThread().getName());
Intent intent = new Intent();
// Use the OperationBuilder class of Intent to construct an Operation object and set the deviceId (left empty if a local ability is required), bundleName, and abilityName attributes for the object.
Operation operation = new Intent.OperationBuilder()
.withDeviceId("")
.withBundleName("com.example.threadingsample")
.withAbilityName("com.example.threadingsample.second.SecondAbility")
.build();
intent.setParam("TEST_KEY", "apple");
// Set the created Operation object to the Intent as its operation attribute.
intent.setOperation(operation);
startAbility(intent);
}
});
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}
Add below code in ability_main.xml
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:orientation="vertical"
ohos:background_element="#8c7373"
ohos:padding="32">
<Text
ohos:multiple_lines="true"
ohos:id="$+id:text"
ohos:height="match_content"
ohos:width="200"
ohos:layout_alignment="horizontal_center"
ohos:text="Text"
ohos:text_size="10fp"/>
<Button
ohos:id="$+id:button_launch_new_slice"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$graphic:background_button"
ohos:layout_alignment="horizontal_center"
ohos:padding="5"
ohos:text="Launch new Slice"
ohos:text_size="30"
ohos:top_margin="5"/>
<Button
ohos:id="$+id:button_launch_new_ability"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$graphic:background_button"
ohos:layout_alignment="horizontal_center"
ohos:padding="5"
ohos:text="Launch new Ability"
ohos:text_size="30"
ohos:top_margin="5"/>
</DirectionalLayout>
Add the below code in ChildAbilitySlice.java
Java:
package com.example.threadingsample.slice;
import com.example.threadingsample.ResourceTable;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.Text;
import ohos.app.dispatcher.TaskDispatcher;
import ohos.app.dispatcher.task.TaskPriority;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
public class ChildAbilitySlice extends AbilitySlice {
static final HiLogLabel LABEL_LOG = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MY_TAG");
Text text;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_child_slice_two);
HiLog.info(LABEL_LOG, "ChildAbilitySlice->"+Thread.currentThread().getName());
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}
Add the below code in child_slice_two.xml
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:orientation="vertical"
ohos:background_element="#8c7373"
ohos:padding="32">
<Text
ohos:multiple_lines="true"
ohos:id="$+id:text"
ohos:height="match_content"
ohos:width="200"
ohos:layout_alignment="horizontal_center"
ohos:text="Child Slice"
ohos:text_size="10fp"/>
</DirectionalLayout>
Switch from MainAbilitySlice to ChildAbilitySlice using present method.
Java:
Button launchNewSlice = (Button) findComponentById(ResourceTable.Id_button_launch_new_slice);
launchNewSlice.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
present(new ChildAbilitySlice(), new Intent());
}
});
Switching from MainAbility to SecondAbility.
Java:
Button launchNewAbility = (Button) findComponentById(ResourceTable.Id_button_launch_new_ability);
launchNewAbility.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
HiLog.info(LABEL_LOG, "MainAbilitySlice launch new [email protected]@->"+Thread.currentThread().getName());
Intent intent = new Intent();
// Use the OperationBuilder class of Intent to construct an Operation object and set the deviceId (left empty if a local ability is required), bundleName, and abilityName attributes for the object.
Operation operation = new Intent.OperationBuilder()
.withDeviceId("")
.withBundleName("com.example.threadingsample")
.withAbilityName("com.example.threadingsample.second.SecondAbility")
.build();
intent.setParam("TEST_KEY", "apple");
// Set the created Operation object to the Intent as its operation attribute.
intent.setOperation(operation);
startAbility(intent);
}
});
Add the below code to SecondAbility.java
Java:
package com.example.threadingsample.second;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
public class SecondAbility extends Ability {
private static final int MY_PERMISSIONS_REQUEST_LOCATION = 1001;
static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MY_TAG");
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setMainRoute(SecondAbilitySlice.class.getName());
}
}
Add the below code in SecondAbilitySlice.java
Java:
package com.example.threadingsample.second;
import com.example.threadingsample.ResourceTable;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.components.Text;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
public class SecondAbilitySlice extends AbilitySlice {
static final HiLogLabel LABEL_LOG = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MY_TAG");
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_two);
HiLog.info(LABEL_LOG, "inside SecondAbilitySlice!!");
if(getAbility() != null) {
if (getAbility().getIntent() != null) {
if (getAbility().getIntent().hasParameter("TEST_KEY")) {
String valueFromFirstAbility = getAbility().getIntent().getStringParam("TEST_KEY");
HiLog.info(LABEL_LOG, "inside [email protected]@-->"+valueFromFirstAbility);
Text text = (Text) findComponentById(ResourceTable.Id_text);
text.setText(valueFromFirstAbility);
} else {
HiLog.info(LABEL_LOG, "TEST_KEY parameter is not present");
}
} else {
HiLog.info(LABEL_LOG, "intent is null");
}
}else{
HiLog.info(LABEL_LOG, "ability is null");
}
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}
Add the below code in ability_two.xml
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:orientation="vertical"
ohos:background_element="#8c7373"
ohos:padding="32">
<Text
ohos:multiple_lines="true"
ohos:id="$+id:text"
ohos:height="match_content"
ohos:width="200"
ohos:layout_alignment="horizontal_center"
ohos:text="Ability2"
ohos:text_size="10fp"/>
</DirectionalLayout>
Transfer data from one ability to another
Java:
Intent intent = new Intent();
intent.setParam("TEST_KEY", "apple");
Retrieve data on other ability
Java:
if(getAbility() != null) {
if (getAbility().getIntent() != null) {
if (getAbility().getIntent().hasParameter("TEST_KEY")) {
String valueFromFirstAbility = getAbility().getIntent().getStringParam("TEST_KEY");
HiLog.info(LABEL_LOG, "inside [email protected]@-->"+valueFromFirstAbility);
Text text = (Text) findComponentById(ResourceTable.Id_text);
text.setText(valueFromFirstAbility);
} else {
HiLog.info(LABEL_LOG, "TEST_KEY parameter is not present");
}
} else {
HiLog.info(LABEL_LOG, "intent is null");
}
}else{
HiLog.info(LABEL_LOG, "ability is null");
}
Tips and Tricks
All Abilities must be registered into config.json
Conclusion
In this article, we have UI components, there life cycle and navigation between them, transfer data between two pages.
Reference
Harmony Official document
DevEco Studio User guide
JS API Reference
Read In Forum
Click to expand...
Click to collapse
Is there any life cycle for Page ability

Related

Comparison Between Huawei ML Kit Text Recognition & Firebase ML Kit Text Recognition

Comparison Between Huawei ML Kit Text Recognition & Firebase ML Kit Text Recognition
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
In this article, we will compare Huawei ML Kit Text Recognition and Firebase ML Kit Text Recognition usages and also we will create sample Android applications to understand how they work. Lets get start it.
Huawei ML Kit Text Recognition
About The Service
HUAWEI ML Kit allows your apps to easily leverage Huawei’s long-term proven expertise in machine learning to support diverse artificial intelligence (AI) applications throughout a wide range of industries. Thanks to Huawei’s technology accumulation, ML Kit provides diversified leading machine learning capabilities that are easy to use, helping you develop various AI apps.
Text Recognition
The text recognition service can extracts text from images of receipts, business cards, and documents. This service is widely used in office, education, transit, and other apps. For example, you can use this service in a translation app to extract text in a photo and translate the text, improving user experience.
This service can run on the cloud or device, but the supported languages differ in the two scenarios. On-device APIs can recognize text in Simplified Chinese, Japanese, Korean, and Latin-based languages (refer to Latin Script Supported by On-device Text Recognition). When running on the cloud, the service can recognize text in languages such as Simplified Chinese, English, Spanish, Portuguese, Italian, German, French, Russian, Japanese, Korean, Polish, Finnish, Norwegian, Swedish, Danish, Turkish, Thai, Arabic, Hindi, and Indonesian.
Configure your project on AppGallery Connect
Registering a Huawei ID
You need to register a Huawei ID to use the plugin. If you don’t have one, follow the instructions here.
Preparations for Integrating HUAWEI HMS Core
First of all, you need to integrate Huawei Mobile Services with your application. I will not get into details about how to integrate your application but you can use this tutorial as step by step guide.
1. Integrating the Text Recognition SDK
You need to integrate the base SDK and then one or more required language model packages in your app-level build.gradle.
Code:
//AGC Core
implementation 'com.huawei.agconnect:agconnect-core:1.4.0.300'
//ML OCR Base SDK
implementation 'com.huawei.hms:ml-computer-vision-ocr:2.0.1.300'
//Latin-based Language Model Package
implementation 'com.huawei.hms:ml-computer-vision-ocr-latin-model:2.0.1.300'
2. Automatically Updating the Machine Learning Model
To use the on-device text recognition service, add the following statements to the AndroidManifest.xml file.
Code:
<manifest
...
<meta-data
android:name="com.huawei.hms.ml.DEPENDENCY"
android:value= "ocr"/>
...
</manifest>
3. There will be an ImageView, a TextView and two Button in our RelativeLayout
Code:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="@+id/captured_image_view"
android:layout_width="match_parent"
android:layout_height="400dp" />
<TextView
android:id="@+id/detected_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/captured_image_view"
android:textSize="20sp"
android:maxLines="10"
android:layout_margin="10dp"
/>
<Button
android:id="@+id/capture_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/detect_text_from_image"
android:text="@string/button_text"
android:textAllCaps="false"
android:background="@color/colorAccent"
android:textColor="@android:color/white"
android:textSize="28sp"
android:layout_marginBottom="5dp"
/>
<Button
android:id="@+id/detect_text_from_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="@string/button_detect"
android:textAllCaps="false"
android:background="@color/colorPrimary"
android:textColor="@android:color/white"
android:textSize="28sp"
/>
</RelativeLayout>
4. Text Recognition from Images on the Device
Take a photo with a camera app
The Android way of delegating actions to other applications is to invoke an Intent that describes what you want done. This process involves three pieces: The Intent itself, a call to start the external Activity, and some code to handle the image data when focus returns to your activity.We will see the result into onActivityResult.
Code:
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.huawei.hmf.tasks.OnFailureListener;
import com.huawei.hmf.tasks.OnSuccessListener;
import com.huawei.hmf.tasks.Task;
import com.huawei.hms.mlsdk.MLAnalyzerFactory;
import com.huawei.hms.mlsdk.common.MLFrame;
import com.huawei.hms.mlsdk.text.MLLocalTextSetting;
import com.huawei.hms.mlsdk.text.MLText;
import com.huawei.hms.mlsdk.text.MLTextAnalyzer;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
static final int REQUEST_IMAGE_CAPTURE = 1;
private MLTextAnalyzer mTextAnalyzer;
private ImageView capturedImageView;
private TextView detectedTextView;
private Button buttonAddImage, detectTextBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
capturedImageView = findViewById(R.id.captured_image_view);
detectedTextView = findViewById(R.id.detected_text_view);
buttonAddImage = findViewById(R.id.capture_image);
detectTextBtn=findViewById(R.id.detect_text_from_image);
buttonAddImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dispatchTakePictureIntent();
detectedTextView.setText("");
}
});
detectTextBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
createMLTextAnalyzer();
}
});
}
private void dispatchTakePictureIntent() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(intent, REQUEST_IMAGE_CAPTURE);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK && data != null) {
Bundle extras = data.getExtras();
Bitmap selectedBitmap = (Bitmap) extras.get("data");
capturedImageView.setImageBitmap(selectedBitmap);
asyncAnalyzeText(selectedBitmap);
}
}
5. Create the text analyzer MLTextAnalyzer to recognize text in images. You can set MLLocalTextSetting to specify languages that can be recognized. If you do not set the languages, only Latin-based languages can be recognized by default.
Code:
private void createMLTextAnalyzer() {
MLLocalTextSetting setting = new MLLocalTextSetting.Factory()
.setOCRMode(MLLocalTextSetting.OCR_DETECT_MODE)
.setLanguage("en")
.create();
mTextAnalyzer = MLAnalyzerFactory.getInstance().getLocalTextAnalyzer(setting);
}
6. Pass the MLFrame object to the asyncAnalyseFrame method for text recognition.
Code:
private void asyncAnalyzeText(Bitmap bitmap) {
if (mTextAnalyzer == null) {
createMLTextAnalyzer();
}
MLFrame frame = MLFrame.fromBitmap(bitmap);
Task<MLText> task = mTextAnalyzer.asyncAnalyseFrame(frame);
task.addOnSuccessListener(new OnSuccessListener<MLText>() {
@Override
public void onSuccess(MLText text) {
detectedTextView.setText(text.getStringValue());
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
detectedTextView.setText(e.getMessage());
}
});
}
7. After the recognition is complete, stop the analyzer to release recognition resources.
Code:
@Override
protected void onDestroy() {
super.onDestroy();
try {
if (mTextAnalyzer != null)
mTextAnalyzer.stop();
} catch (IOException e) {
e.printStackTrace();
}
}
8. You can see all the code in Main Activity below.
Code:
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.huawei.hmf.tasks.OnFailureListener;
import com.huawei.hmf.tasks.OnSuccessListener;
import com.huawei.hmf.tasks.Task;
import com.huawei.hms.mlsdk.MLAnalyzerFactory;
import com.huawei.hms.mlsdk.common.MLFrame;
import com.huawei.hms.mlsdk.text.MLLocalTextSetting;
import com.huawei.hms.mlsdk.text.MLText;
import com.huawei.hms.mlsdk.text.MLTextAnalyzer;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
static final int REQUEST_IMAGE_CAPTURE = 1;
private MLTextAnalyzer mTextAnalyzer;
private ImageView capturedImageView;
private TextView detectedTextView;
private Button buttonAddImage, detectTextBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
capturedImageView = findViewById(R.id.captured_image_view);
detectedTextView = findViewById(R.id.detected_text_view);
buttonAddImage = findViewById(R.id.capture_image);
detectTextBtn=findViewById(R.id.detect_text_from_image);
buttonAddImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dispatchTakePictureIntent();
detectedTextView.setText("");
}
});
detectTextBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
createMLTextAnalyzer();
}
});
}
private void dispatchTakePictureIntent() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(intent, REQUEST_IMAGE_CAPTURE);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK && data != null) {
Bundle extras = data.getExtras();
Bitmap selectedBitmap = (Bitmap) extras.get("data");
capturedImageView.setImageBitmap(selectedBitmap);
asyncAnalyzeText(selectedBitmap);
}
}
private void createMLTextAnalyzer() {
MLLocalTextSetting setting = new MLLocalTextSetting.Factory()
.setOCRMode(MLLocalTextSetting.OCR_DETECT_MODE)
.setLanguage("en")
.create();
mTextAnalyzer = MLAnalyzerFactory.getInstance().getLocalTextAnalyzer(setting);
}
private void asyncAnalyzeText(Bitmap bitmap) {
if (mTextAnalyzer == null) {
createMLTextAnalyzer();
}
MLFrame frame = MLFrame.fromBitmap(bitmap);
Task<MLText> task = mTextAnalyzer.asyncAnalyseFrame(frame);
task.addOnSuccessListener(new OnSuccessListener<MLText>() {
@Override
public void onSuccess(MLText text) {
detectedTextView.setText(text.getStringValue());
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
detectedTextView.setText(e.getMessage());
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
try {
if (mTextAnalyzer != null)
mTextAnalyzer.stop();
} catch (IOException e) {
e.printStackTrace();
}
}
}
9. Here’s the result.
Firebase ML Kit Text Recognition
You can use ML Kit to recognize text in images. ML Kit has both a general-purpose API suitable for recognizing text in images, such as the text of a street sign, and an API optimized for recognizing the text of documents. The general-purpose API has both on-device and cloud-based models.
Before you begin
1. If you haven’t already, add Firebase to your Android project.
2. In your project-level build.gradle file, make sure to include Google's Maven repository in both your buildscript and allprojects sections.
3. Add the dependencies for the ML Kit Android libraries to your module (app-level) Gradle file (usually app/build.gradle):
Code:
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
dependencies {
// ...
implementation 'com.google.firebase:firebase-core:15.0.2'
implementation 'com.google.firebase:firebase-ml-vision:15.0.0'
}
4. add the following declaration to your app’s AndroidManifest.xml file
Code:
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.camera"
android:required="true" />
<application ...>
...
<meta-data
android:name="com.google.firebase.ml.vision.DEPENDENCIES"
android:value="ocr" />
<!-- To use multiple models: android:value="ocr,model2,model3" -->
</application>
5. There will be an ImageView, a TextView and two Button in our RelativeLayout
Code:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="@+id/captured_image_view"
android:layout_width="match_parent"
android:layout_height="400dp" />
<TextView
android:id="@+id/detected_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/captured_image_view"
android:textSize="20sp"
android:maxLines="10"
android:layout_margin="10dp"
/>
<Button
android:id="@+id/capture_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/detect_text_from_image"
android:text="@string/button_text"
android:textAllCaps="false"
android:background="@color/colorAccent"
android:textColor="@android:color/white"
android:textSize="28sp"
android:layout_marginBottom="5dp"
/>
<Button
android:id="@+id/detect_text_from_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="@string/button_detect"
android:textAllCaps="false"
android:background="@color/colorPrimary"
android:textColor="@android:color/white"
android:textSize="28sp"
/>
</RelativeLayout>
6. Take a photo with a camera app
Here’s a function that invokes an intent to capture a photo.
Code:
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.ml.vision.FirebaseVision;
import com.google.firebase.ml.vision.common.FirebaseVisionImage;
import com.google.firebase.ml.vision.text.FirebaseVisionText;
import com.google.firebase.ml.vision.text.FirebaseVisionTextDetector;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private Button captureImageBtn, detectTextBtn;
private ImageView capturedImageView;
private TextView detectedTextView;
static final int REQUEST_IMAGE_CAPTURE = 1;
Bitmap imageBitmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
captureImageBtn = findViewById(R.id.capture_image);
detectTextBtn = findViewById(R.id.detect_text_from_image);
capturedImageView = findViewById(R.id.captured_image_view);
detectedTextView = findViewById(R.id.detected_text_view);
captureImageBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
dispatchTakePictureIntent();
detectedTextView.setText("");
}
});
detectTextBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
detectTextFromImage();
}
});
}
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}
}
7. The Android Camera application encodes the photo in the return Intent delivered to onActivityResult() as a small Bitmap in the extras, under the key "data". The following code retrieves this image and displays it in an ImageView.
Code:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
Bundle extras = data.getExtras();
imageBitmap = (Bitmap) extras.get("data");
capturedImageView.setImageBitmap(imageBitmap);
}
}
8. To create a FirebaseVisionImage object from a Bitmap object.
Code:
private void detectTextFromImage() {
FirebaseVisionImage firebaseVisionImage = FirebaseVisionImage.fromBitmap(imageBitmap);
FirebaseVisionTextDetector firebaseVisionTextDetector = FirebaseVision.getInstance().getVisionTextDetector();
firebaseVisionTextDetector.detectInImage(firebaseVisionImage).addOnSuccessListener(new OnSuccessListener<FirebaseVisionText>() {
@Override
public void onSuccess(FirebaseVisionText firebaseVisionText) {
displayTextFromImage(firebaseVisionText);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Toast.makeText(MainActivity.this, "Error: " + e.getMessage(), Toast.LENGTH_SHORT).show();
Log.d("Error", e.getMessage());
}
});
}
9. To display text from the image
Code:
private void displayTextFromImage(FirebaseVisionText firebaseVisionText) {
List<FirebaseVisionText.Block>blockList=firebaseVisionText.getBlocks();
if(blockList.size()==0){
Toast.makeText(this, "No Text Found in Image", Toast.LENGTH_SHORT).show();
}else{
for(FirebaseVisionText.Block block: firebaseVisionText.getBlocks()){
String text=block.getText();
detectedTextView.setText(text);
}
}
}
10. You can see all the code in Main Activity below.
Code:
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.ml.vision.FirebaseVision;
import com.google.firebase.ml.vision.common.FirebaseVisionImage;
import com.google.firebase.ml.vision.text.FirebaseVisionText;
import com.google.firebase.ml.vision.text.FirebaseVisionTextDetector;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private Button captureImageBtn, detectTextBtn;
private ImageView capturedImageView;
private TextView detectedTextView;
static final int REQUEST_IMAGE_CAPTURE = 1;
Bitmap imageBitmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
captureImageBtn = findViewById(R.id.capture_image);
detectTextBtn = findViewById(R.id.detect_text_from_image);
capturedImageView = findViewById(R.id.captured_image_view);
detectedTextView = findViewById(R.id.detected_text_view);
captureImageBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
dispatchTakePictureIntent();
detectedTextView.setText("");
}
});
detectTextBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
detectTextFromImage();
}
});
}
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
Bundle extras = data.getExtras();
imageBitmap = (Bitmap) extras.get("data");
capturedImageView.setImageBitmap(imageBitmap);
}
}
private void detectTextFromImage() {
FirebaseVisionImage firebaseVisionImage = FirebaseVisionImage.fromBitmap(imageBitmap);
FirebaseVisionTextDetector firebaseVisionTextDetector = FirebaseVision.getInstance().getVisionTextDetector();
firebaseVisionTextDetector.detectInImage(firebaseVisionImage).addOnSuccessListener(new OnSuccessListener<FirebaseVisionText>() {
@Override
public void onSuccess(FirebaseVisionText firebaseVisionText) {
displayTextFromImage(firebaseVisionText);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Toast.makeText(MainActivity.this, "Error: " + e.getMessage(), Toast.LENGTH_SHORT).show();
Log.d("Error", e.getMessage());
}
});
}
private void displayTextFromImage(FirebaseVisionText firebaseVisionText) {
List<FirebaseVisionText.Block>blockList=firebaseVisionText.getBlocks();
if(blockList.size()==0){
Toast.makeText(this, "No Text Found in Image", Toast.LENGTH_SHORT).show();
}else{
for(FirebaseVisionText.Block block: firebaseVisionText.getBlocks()){
String text=block.getText();
detectedTextView.setText(text);
}
}
}
}
11. Here’s the result.
For more information about HUAWEI ML Kit, visit:
https://developer.huawei.com/consumer/en/hms/huawei-mlkit
Other Resources:
https://developer.android.com/training/camera/photobasics#java
https://firebase.google.com/docs/ml-kit/android/recognize-text
https://firebase.google.com/support/release-notes/android
Related Links
Original post: https://medium.com/huawei-developers/comparison-between-huawei-ml-kit-text-recognition-and-firebase-ml-kit-text-recognition-98217e3cfa84
Nice article
Is huawei ML kit is better than firebase ML kit?
riteshchanchal said:
Is huawei ML kit is better than firebase ML kit?
Click to expand...
Click to collapse
I'm not sure which one is better here I just used text recognition. But in this example the Huawei Ml kit worked better. Also, payment is required to use the latest version of the Firebase ML kit but Huawei ML kit is free.

Beginners : Explaining Database Storage in Huawei Harmony using SQLite

Introduction
In this article, we can create an app showing below storage features:
1. Create database and create table
2. Insert data
3. Update data
4. Delete data
5. Fetch data
Requirements
1. Dev Eco IDE
2. Wearable watch (Can use simulator also)
Harmony OS Supports various ways of storage
1. Storage like (Shared preference, key value pairs).
2. File Storage
3. SQLite Db
In this article, we will test SQLite Db
UI Design
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
ability_main.xml
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:orientation="vertical"
ohos:background_element="#8c7373"
ohos:padding="32">
<Text
ohos:multiple_lines="true"
ohos:id="$+id:text"
ohos:height="match_content"
ohos:width="200"
ohos:layout_alignment="horizontal_center"
ohos:text="Text"
ohos:text_size="10fp"/>
<Button
ohos:id="$+id:button"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$graphic:background_button"
ohos:layout_alignment="horizontal_center"
ohos:text="$string:save"
ohos:text_size="30"
ohos:top_margin="5"/>
<Button
ohos:id="$+id:button_get"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$graphic:background_button"
ohos:layout_alignment="horizontal_center"
ohos:padding="5"
ohos:text="$string:read"
ohos:text_size="30"
ohos:top_margin="5"/>
<Button
ohos:id="$+id:button_update"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$graphic:background_button"
ohos:layout_alignment="horizontal_center"
ohos:padding="5"
ohos:text="$string:update"
ohos:text_size="30"
ohos:top_margin="5"/>
<Button
ohos:id="$+id:button_delete"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$graphic:background_button"
ohos:layout_alignment="horizontal_center"
ohos:padding="5"
ohos:text="$string:delete"
ohos:text_size="30"
ohos:top_margin="5"/>
</DirectionalLayout>
MainAbilitySlice.java
Java:
package com.example.testwearableemptyfeaturejava.slice;
import com.example.testwearableemptyfeaturejava.ResourceTable;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.Text;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.window.dialog.ToastDialog;
import ohos.app.Context;
import ohos.data.DatabaseHelper;
import ohos.data.rdb.*;
import ohos.data.resultset.ResultSet;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
public class MainAbilitySlice extends AbilitySlice {
static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MY_TAG");
RdbStore mStore;
Text mText;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
initDb(getApplicationContext());
mText = (Text) findComponentById(ResourceTable.Id_text);
Button button = (Button) findComponentById(ResourceTable.Id_button);
if (button != null) {
button.setClickedListener(new Component.ClickedListener() {
@Override
// Register a listener for observing click events of the button.
public void onClick(Component component) {
HiLog.warn(LABEL, "inside %{public}s", "MainAbilitySliceButtonClick");
// Add the operation to perform when the button is clicked.
insertData();
}
});
}
Button buttonGet = (Button) findComponentById(ResourceTable.Id_button_get);
if(buttonGet != null){
buttonGet.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
HiLog.warn(LABEL, "inside %{public}s", "get data");
readData();
}
});
}
Button buttonDelete = (Button) findComponentById(ResourceTable.Id_button_delete);
if(buttonDelete != null){
buttonDelete.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
HiLog.warn(LABEL, "inside %{public}s", "deleteData");
deleteData();
}
});
}
Button buttonUpdate = (Button) findComponentById(ResourceTable.Id_button_update);
if(buttonUpdate != null){
buttonUpdate.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
HiLog.warn(LABEL, "inside %{public}s", "updateData");
updateData();
}
});
}
}
private void initDb(Context context){
StoreConfig config = StoreConfig.newDefaultConfig("RdbStoreTest.db");
final RdbOpenCallback callback = new RdbOpenCallback() {
@Override
public void onCreate(RdbStore store) {
store.executeSql("CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER, salary REAL, blobType BLOB)");
}
@Override
public void onUpgrade(RdbStore store, int oldVersion, int newVersion) {
}
};
DatabaseHelper helper = new DatabaseHelper(context);
mStore = helper.getRdbStore(config, 1, callback, null);
}
private void insertData(){
ValuesBucket values = new ValuesBucket();
//values.putInteger("id", 2);
values.putString("name", "kamal");
values.putInteger("age", 18);
values.putDouble("salary", 100.5);
values.putByteArray("blobType", new byte[] {1, 2, 3});
long id = mStore.insert("test", values);
HiLog.warn(LABEL, "insert completed %{public}s", "id is"+id);
showToastMessage("data inserted successfully");
}
private void readData(){
try {
String[] columns = new String[] {"id", "name", "age", "salary"};
RdbPredicates rdbPredicates = new RdbPredicates("test").orderByAsc("salary");
ResultSet resultSet = mStore.query(rdbPredicates, columns);
if(resultSet == null || resultSet.getRowCount() <=0){
showToastMessage("no data in table");
return;
}
String data = "";
while(resultSet.goToNextRow()){
String name = resultSet.getString(resultSet.getColumnIndexForName("name"));
String age = resultSet.getString(resultSet.getColumnIndexForName("age"));
String salary = resultSet.getString(resultSet.getColumnIndexForName("salary"));
HiLog.warn(LABEL, "inside %{public}s", "read data"+name);
data = data + "[" + name + "][" + age + "][" + salary + "]\n";
}
mText.setText(data);
HiLog.warn(LABEL, "read completedqq %{public}s", "");
showToastMessage("data read successfully");
}catch (Exception e){
e.printStackTrace();
}
}
private void updateData(){
try {
ValuesBucket values = new ValuesBucket();
values.putString("name", "updated kamal");
values.putInteger("age", 28);
values.putDouble("salary", 200.5);
values.putByteArray("blobType", new byte[] {1, 2, 3});
AbsRdbPredicates rdbPredicates = new RdbPredicates("test").equalTo("age", 18);
int index = mStore.update(values, rdbPredicates);
HiLog.warn(LABEL, "update completed %{public}s", ""+index);
showToastMessage("data updated successfully");
}catch (Exception e){
e.printStackTrace();
}
}
private void deleteData(){
try {
String[] columns = new String[] {"id", "name", "age", "salary"};
RdbPredicates rdbPredicates = new RdbPredicates("test").equalTo("age", 18);
int index = mStore.delete(rdbPredicates);
HiLog.warn(LABEL, "delete completed %{public}s", ""+index);
showToastMessage("data deleted successfully");
}catch (Exception e){
e.printStackTrace();
}
}
private void showToastMessage(String string){
new ToastDialog(getApplicationContext()).setText(string).setAlignment(1).setSize(300,50).show();
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}
MainAbility.java
Java:
package com.example.testwearableemptyfeaturejava;
import com.example.testwearableemptyfeaturejava.slice.MainAbilitySlice;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
public class MainAbility extends Ability {
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setMainRoute(MainAbilitySlice.class.getName());
}
}
Code Explanation
Create database under “MainAbility.java” or any separate class.
Java:
private void initDb(Context context){
StoreConfig config = StoreConfig.newDefaultConfig("RdbStoreTest.db");
final RdbOpenCallback callback = new RdbOpenCallback() {
@Override
public void onCreate(RdbStore store) {
store.executeSql("CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER, salary REAL, blobType BLOB)");
}
@Override
public void onUpgrade(RdbStore store, int oldVersion, int newVersion) {
}
};
DatabaseHelper helper = new DatabaseHelper(context);
mStore = helper.getRdbStore(config, 1, callback, null);
}
If database is not there, it will be created. onCreate method will create table test.
Insert data under “MainAbility.java” or any new class.
Java:
private void insertData(){
ValuesBucket values = new ValuesBucket();
values.putString("name", "kamal");
values.putInteger("age", 18);
values.putDouble("salary", 100.5);
values.putByteArray("blobType", new byte[] {1, 2, 3});
long id = mStore.insert("test", values);
HiLog.warn(LABEL, "insert completed %{public}s", "id is"+id);
}
Data is retrieved and UI is updated.
Update row under “MainAbility.java” or any class.
Java:
private void updateData(){
try {
ValuesBucket values = new ValuesBucket();
values.putString("name", "updated kamal");
values.putInteger("age", 28);
values.putDouble("salary", 200.5);
values.putByteArray("blobType", new byte[] {1, 2, 3});
AbsRdbPredicates rdbPredicates = new RdbPredicates("test").equalTo("age", 18);
int index = mStore.update(values, rdbPredicates);
HiLog.warn(LABEL, "update completed %{public}s", ""+index);
showToastMessage("data updated successfully");
}catch (Exception e){
e.printStackTrace();
}
}
Delete data under “MainAbility.java” or any class.
Java:
private void deleteData(){
try {
String[] columns = new String[] {"id", "name", "age", "salary"};
RdbPredicates rdbPredicates = new RdbPredicates("test").equalTo("age", 18);
int index = mStore.delete(rdbPredicates);
HiLog.warn(LABEL, "delete completed %{public}s", ""+index);
showToastMessage("data deleted successfully");
}catch (Exception e){
e.printStackTrace();
}
}
Tips and Tricks
1. All the file operations are Asynchronous.
2. Relational mapping is possible.
3. RDB can use a maximum of four connection pools to manage read and write operations.
4. To ensure data accuracy, the RDB supports only one write operation at a time.
5. RdbPredicates: You do not need to write complex SQL statements. Instead, you can combine SQL statements simply by calling methods in this class, such as equalTo, notEqualTo, groupBy, orderByAsc, and beginsWith.
6. RawRdbPredicates: You can set whereClause and whereArgs, but cannot call methods such as equalTo.
Conclusion
we have learned to save, update, delete and retrieve the data using SQLite database in Harmony OS along with the UI components.
Reference
Harmony Official document
DevEco Studio User guide
JS API Reference
Read In Forum
Does it support room database ?
Can i implement this with Rxandroid?

Beginner: Service Ability features in Huawei Harmony OS

Introduction
In this article, we can create an application showing below features:
1. Service Ability
2. Create service ability
3. Connect page ability with service ability
4. Update result on UI received from service ability
Requirements
1. DevEco IDE
2. Wearable watch (Can use simulator also)
Harmony OS Supports two types of abilities
1. Feature Ability
2. Particle Ability
In this article, we will test Particle Ability template called Service template.
UI Design
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Service Template (Service Abilities):
The Service template is used for Particle Ability that provide background tasks.
A Service ability has only one instance on a device and multiple abilities share this instance.
Service Abilities runs on Main thread, you must create another thread for that operation in the Service ability.
Life cycle methods can be find here:
Document
developer.harmonyos.com
Main uses, it can be used in playing music or downloading files or any other background tasks which doesn’t need UI.
Create Service Ability:
It has two steps:
1. Register ability in config.json
2. Create service class extending Ability
Add the below code in Config.json
JSON:
{
"app": {
"bundleName": "com.example.testserviceability",
"vendor": "example",
"version": {
"code": 1,
"name": "1.0"
},
"apiVersion": {
"compatible": 3,
"target": 3
}
},
"deviceConfig": {},
"module": {
"package": "com.example.testserviceability",
"name": ".MyApplication",
"deviceType": [
"wearable"
],
"distro": {
"deliveryWithInstall": true,
"moduleName": "entry",
"moduleType": "entry"
},
"abilities": [
{
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
],
"orientation": "landscape",
"name": "com.example.testserviceability.MainAbility",
"icon": "$media:icon",
"description": "$string:mainability_description",
"label": "TestServiceAbility",
"type": "page",
"launchType": "standard"
},
{
"name": ".ServiceAbility",
"type": "service",
"visible": true
}
]
}
}
Add the below code in ServiceAbility.java
Java:
package com.example.testserviceability;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.LocalRemoteObject;
import ohos.aafwk.content.Intent;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.rpc.IRemoteObject;
public class ServiceAbility extends Ability {
static final HiLogLabel LABEL_LOG = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MY_TAG");
@Override
public void onStart(Intent intent) {
super.onStart(intent);
HiLog.info(LABEL_LOG, "inside onStart!!");
}
@Override
public void onCommand(Intent intent, boolean restart, int startId) {
super.onCommand(intent, restart, startId);
HiLog.info(LABEL_LOG, "inside onCommand!!");
}
@Override
public IRemoteObject onConnect(Intent intent) {
super.onConnect(intent);
HiLog.info(LABEL_LOG, "inside onConnect!!");
//return super.onConnect(intent);
return new MyRemoteObject();
}
String sayHello(String name){
return "Hello "+name;
}
@Override
public void onDisconnect(Intent intent) {
super.onDisconnect(intent);
HiLog.info(LABEL_LOG, "inside onDisconnect!!");
}
@Override
public void onStop() {
super.onStop();
HiLog.info(LABEL_LOG, "inside onStop!!");
}
public class MyRemoteObject extends LocalRemoteObject {
public MyRemoteObject() {
super();
}
public String callHello(String name){
return sayHello(name);
}
}
}
Connect Page Ability with Service Ability:
This can be done using LocalRemoteObject, like shown below.
Add the below code in MainAbilitySlice.java
Java:
package com.example.testserviceability.slice;
import com.example.testserviceability.ResourceTable;
import com.example.testserviceability.ServiceAbility;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.ability.IAbilityConnection;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.Text;
import ohos.bundle.ElementName;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.rpc.IRemoteObject;
public class MainAbilitySlice extends AbilitySlice {
static final HiLogLabel LABEL_LOG = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MY_TAG");
ServiceAbility.MyRemoteObject myRemoteObject;
Text text;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
text = (Text) findComponentById(ResourceTable.Id_text);
Button btnStartService = (Button) findComponentById(ResourceTable.Id_button_start_service);
btnStartService.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
startBackGroundService();
}
});
Button btnCallServiceFunction = (Button) findComponentById(ResourceTable.Id_button_call_service_function);
btnCallServiceFunction.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
if(myRemoteObject != null){
String valueFromService = myRemoteObject.callHello("pavan");
HiLog.info(LABEL_LOG, "valueFromService-->"+valueFromService);
text.setText(valueFromService);
}else{
HiLog.info(LABEL_LOG, "myRemoteObject is null!!");
}
}
});
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
private void startBackGroundService(){
HiLog.info(LABEL_LOG, "inside startBackGroundService!!");
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
.withDeviceId("")
.withBundleName("com.example.testserviceability")
.withAbilityName("com.example.testserviceability.ServiceAbility")
.build();
intent.setOperation(operation);
connectAbility(intent, connection);
}
// Create an IAbilityConnection instance.
private IAbilityConnection connection = new IAbilityConnection() {
// Override the callback invoked when the Service ability is connected.
@Override
public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int resultCode) {
// The client must implement the IRemoteObject interface in the same way as the Service ability does. You will receive an IRemoteObject object from the server and can then parse information from it.
myRemoteObject= (ServiceAbility.MyRemoteObject) iRemoteObject;
HiLog.info(LABEL_LOG, "service connection made successful!!");
}
// Override the callback invoked when the Service ability is disconnected.
@Override
public void onAbilityDisconnectDone(ElementName elementName, int resultCode) {
}
};
}
Add the below code in ability_main.xml
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:orientation="vertical"
ohos:background_element="#8c7373"
ohos:padding="32">
<Text
ohos:multiple_lines="true"
ohos:id="$+id:text"
ohos:height="match_content"
ohos:width="200"
ohos:layout_alignment="horizontal_center"
ohos:text="Text"
ohos:text_size="10fp"/>
<Button
ohos:id="$+id:button_start_service"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$graphic:background_button"
ohos:layout_alignment="horizontal_center"
ohos:padding="5"
ohos:text="Start service"
ohos:text_size="30"
ohos:top_margin="5"/>
<Button
ohos:id="$+id:button_call_service_function"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$graphic:background_button"
ohos:layout_alignment="horizontal_center"
ohos:padding="5"
ohos:text="Call service function"
ohos:text_size="30"
ohos:top_margin="5"/>
</DirectionalLayout>
Connect Page ability with Service ability
Java:
private void startBackGroundService(){
HiLog.info(LABEL_LOG, "inside startBackGroundService!!");
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
.withDeviceId("")
.withBundleName("com.example.testserviceability")
.withAbilityName("com.example.testserviceability.ServiceAbility")
.build();
intent.setOperation(operation);
connectAbility(intent, connection);
}
Call function of service from page ability
Code:
Button btnCallServiceFunction = (Button) findComponentById(ResourceTable.Id_button_call_service_function);
btnCallServiceFunction.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
if(myRemoteObject != null){
String valueFromService = myRemoteObject.callHello("pavan");
HiLog.info(LABEL_LOG, "valueFromService-->"+valueFromService);
text.setText(valueFromService);
}else{
HiLog.info(LABEL_LOG, "myRemoteObject is null!!");
}
}
});
Tips and Tricks
1. All Abilities must be registered into Config.json.
2. Service Ability runs on main thread, you must create other thread to handle work.
Conclusion
In this article, we have UI components communicating with background running service. Calling a function of background service and getting result back on UI.
Reference
1. Harmony Official document
2. DevEco Studio User guide
3. JS API Reference
Checkout in forum
Well done, can you please upload the sources files, because i am new developing in DevEco, and need to know the distribution of the files in the project, thanks in advance
Can we show progress inside Service Ability?
Does it support dialog?
In which thread its will work

MVVM Architecture On HarmonyOS Using Retrofit And RxJava

{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
In this tutorial, we will be discussing and implementing the HarmonyOS MVVM Architectural pattern in our Harmony app.
This project is available on github, link can be found at the end of the article
Table of contents
What is MVVM
Harmony MVVM example project structure
Adding dependencies
Model
Layout
Retrofit interface
ViewModel
Tip and Tricks
Conclusion
Recommended resources
What is MVVM
MVVM stands for Model, View, ViewModel:
Model: This holds the data of the application. It cannot directly talk to the View. Generally, it’s recommended to expose the data to the ViewModel through ActiveDatas (Observables ).
View: It represents the UI of the application devoid of any Application Logic. It observes the ViewModel.
ViewModel: It acts as a link between the Model and the View. It’s responsible for transforming the data from the Model. It provides data streams to the View. It also uses hooks or callbacks to update the View. It’ll ask for the data from the Model.
MVVM can be achieved in two ways:
Using Data binding
RxJava
In this tutorial we will implement MVVM in Harmony using RXjava, as Data binding is still under development and not ready to use in Harmony.
Harmony MVVM example project structure​
We will create packages by features. It will make your code more modular and manageable.
Adding the Dependencies
Add the following dependencies in your module level build.gradle file:
Code:
dependencies {
//[...]
//RxJava
implementation "io.reactivex.rxjava2:rxjava:2.2.17"
//retrofit
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation "com.squareup.retrofit2:converter-moshi:2.6.0"
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
//RxJava adapter for retrofit
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.7.1'
}
Model
The Model would hold the user’s email and password. The following User.java class does it:
Code:
package com.megaache.mvvmdemo.model;
public class User {
private String email;
private String password;
public User(String email, String password) {
this.email = email;
this.password = password;
}
public void setEmail(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
public void setPassword(String password) {
this.password = password;
}
public String getPassword() {
return password;
}
@Override
public String toString() {
return "User{" +
"email='" + email + '\'' +
", password='" + password + '\'' +
'}';
}
}
Layout
NOTE: For this tutorial, I have decided to create the layout for smart watch devices, however it will work fine on all devices, you just need to re-arrange the components and modify the alignment.
The layout will consist of login button, two text fields and two error texts, each will be shown or hidden depending on the value of the text box above it, after clicking the login button. the final UI will like the screenshot below:
Before we create layout lets add some colors:
First create file color.json under resources/base/element and add the following json content:
Code:
{
"color": [
{
"name": "primary",
"value": "#283148"
},
{
"name": "primaryDark",
"value": "#283148"
},
{
"name": "accent",
"value": "#06EBBF"
},
{
"name": "red",
"value": "#FF406E"
}
]
}
Then, lets design background elements for the Text Fields and the Button:
Create file background_text_field.xml and background_text_button.xml under resources/base/graphic as shown in below screenshot :
Then add the following code:
Background_text_field.xml:
Code:
<?xml version="1.0" encoding="UTF-8" ?>
<shape
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:shape="rectangle">
<corners
ohos:radius="20"/>
<solid
ohos:color="#ffffff"/>
<stroke
ohos:width="2"
ohos:color="$color:accent"/>
</shape>
Background_button.xml:
Code:
<?xml version="1.0" encoding="UTF-8" ?>
<shape
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:shape="rectangle">
<corners
ohos:radius="20"/>
<solid
ohos:color="$color:accent"/>
</shape>
Now lets create the background element for the main layout, let’s called background_ability_login.xml:
Code:
<?xml version="1.0" encoding="UTF-8" ?>
<shape xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:shape="rectangle">
<solid
ohos:color="$color:primaryDark"/>
</shape>
Finally, let’s create the layout file ability_login.xml:
Code:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:id="$+id:scrollview"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:background_element="$graphic:background_ability_login"
ohos:layout_alignment="horizontal_center"
ohos:rebound_effect="true"
>
<DirectionalLayout
ohos:height="match_content"
ohos:width="match_parent"
ohos:orientation="vertical"
ohos:padding="20vp"
>
<DirectionalLayout
ohos:height="match_content"
ohos:width="match_parent"
ohos:layout_alignment="center"
ohos:orientation="vertical"
>
<TextField
ohos:id="$+id:tf_email"
ohos:height="match_content"
ohos:width="match_parent"
ohos:background_element="$graphic:background_text_field"
ohos:hint="email"
ohos:left_padding="10vp"
ohos:min_height="40vp"
ohos:multiple_lines="false"
ohos:text_alignment="vertical_center"
ohos:text_color="black"
ohos:text_input_type="pattern_number"
ohos:text_size="15fp"/>
<Text
ohos:id="$+id:t_email_invalid"
ohos:height="match_content"
ohos:width="match_content"
ohos:layout_alignment="center"
ohos:text="invalid email"
ohos:text_color="$color:red"
ohos:text_size="15fp"
/>
</DirectionalLayout>
<DirectionalLayout
ohos:height="match_content"
ohos:width="match_parent"
ohos:layout_alignment="center"
ohos:orientation="vertical"
ohos:top_margin="10vp">
<TextField
ohos:id="$+id:tf_password"
ohos:height="match_content"
ohos:width="match_parent"
ohos:background_element="$graphic:background_text_field"
ohos:hint="password"
ohos:left_padding="10vp"
ohos:min_height="40vp"
ohos:multiple_lines="false"
ohos:text_alignment="vertical_center"
ohos:text_color="black"
ohos:text_input_type="pattern_password"
ohos:text_size="15fp"
/>
<Text
ohos:id="$+id:t_password_invalid"
ohos:height="match_content"
ohos:width="match_content"
ohos:layout_alignment="center"
ohos:padding="0vp"
ohos:text="invalid password"
ohos:text_color="$color:red"
ohos:text_size="15fp"
/>
</DirectionalLayout>
<Button
ohos:id="$+id:btn_login"
ohos:height="match_content"
ohos:width="match_parent"
ohos:background_element="$graphic:background_button"
ohos:bottom_margin="30vp"
ohos:min_height="40vp"
ohos:text="login"
ohos:text_color="#fff"
ohos:text_size="18fp"
ohos:top_margin="10vp"/>
</DirectionalLayout>
</ScrollView>
Retrofit interface
Before we move to the ViewModel, we have to setup our Retrofit service and repository class.
To keep the project clean, I will create class config.java which will hold our API URLs:
Code:
package com.megaache.mvvmdemo;
public class Config {
//todo: update base url variable with valid url
public static final String BASE_URL = "https://example.com";
public static final String API_VERSION = "/api/v1";
public static final String LOGIN_URL="auth/login";
}
Note: The url's are just for demonstration. For the demo to work, you must replace the urls.
First create interface APIServices.java:
For this tutorial, we assume the method of login EndPoint is Post, you may changes depending on your API, the method login will return an Observable, that will be observed in the ViewModel using RxJava.
Code:
package com.megaache.mvvmdemo.network;
import com.megaache.mvvmdemo.Config;
import com.megaache.mvvmdemo.network.request.LoginRequest;
import com.megaache.mvvmdemo.network.response.LoginResponse;
import io.reactivex.Observable;
import retrofit2.http.Body;
import retrofit2.http.Headers;
import retrofit2.http.POST;
public interface APIServices {
@POST(Config.LOGIN_URL)
@Headers("Content-Type: application/json;charset=UTF-8")
public Observable<LoginResponse> login(@Body LoginRequest loginRequest);
}
Note: the class LoginRequest which you will see later in this tutorial, must be equal to the request that the server expects in The names of variables and their types, otherwise the server will fail to process the request.
Then, add method createRetrofitClient() to MyApplication.java, which will create and return retrofit instance, the instance will use Moshi converter to handle the conversion of JSON to our java class, and RxJava2 adapter to return observables that can work with RxJava instead of the default Call class which requires callbacks:
Code:
package com.megaache.mvvmdemo;
import com.megaache.mvvmdemo.network.APIServices;
import ohos.aafwk.ability.AbilityPackage;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.moshi.MoshiConverterFactory;
import java.util.concurrent.TimeUnit;
public class MyApplication extends AbilityPackage {
@Override
public void onInitialize() {
super.onInitialize();
}
public static APIServices createRetrofitClient() {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(60L, TimeUnit.SECONDS)
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Config.BASE_URL + Config.API_VERSION)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(MoshiConverterFactory.create()).client(client)
.build();
return retrofit.create(APIServices.class);
}
}
NOTE: For cleaner code, you can create file RetrofitClient.java and move the method createRetrofitClient() to it.
Now, let’s work on the Login feature, we going to first create request and response classes, then move to the ViewModel and the view:
We need LoginRequest and LoginResponse which both will extends BaseRequest and BaseRespnonse, code is shown below:
Create BaseRequest.java:
In real life project, your API may expect some parameter to be sent with every request. For example: accessToken, language, deviceId, pushToken etc. which will depende on your API. for this tutorial I added one field called deviceType with static value.
Code:
package com.megaache.mvvmdemo.network.request;
public class BaseRequest {
private String deviceType;
public BaseRequest() {
deviceType = "harmony-watch";
}
public String getDeviceType() {
return deviceType;
}
public void setDeviceType(String deviceType) {
this.deviceType = deviceType;
}
}
Create class LoginRequest.java, which will extend BaseRequest and have two fields Email and Password, which will provided by the end user:
Code:
package com.megaache.mvvmdemo.network.request;
public class LoginRequest {
private String email;
private String password;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Then for the response, Create BaseResponse.java first:
Code:
package com.megaache.mvvmdemo.network.response;
import com.megaache.mvvmdemo.MyApplication;
import java.io.Serializable;
public class BaseResponse implements Serializable {
}
Then LoginResponse.java extending BaseResponse:
Code:
package com.megaache.mvvmdemo.network.response;
import com.megaache.mvvmdemo.model.User;
import com.squareup.moshi.Json;
public class LoginResponse extends BaseResponse {
@Json(name = "user")
private User user;
@Json(name = "accessToken")
private String accessToken;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
}
Note: this class must be equal to the response you get from server, otherwise Retrofit Gson converter will fail to convert the response to LoginResponse class, both the type of variables and their names must equal the those in the JSON response.
ViewModel
In ViewModel, we will wrap the data which was loaded with Retrofit inside class LoggedIn in LoginViewState, and observe states Observable defined in BaseViewModel in our Ability (or AbilitySlice). Whenever the value in states changes, the ability will be notified without checking whether the ability is alive or not.
The code for LoginViewState.java extending empty class BaseViewState.java, and ErrorData.java (used in LoginViewState.java) is given below:
ErrorData.java:
Code:
package com.megaache.mvvmdemo.model;
import java.io.Serializable;
public class ErrorData implements Serializable {
private String message;
private int statusCode;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getStatusCode() {
return statusCode;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
}
LoginViewState.java:
Code:
package com.megaache.mvvmdemo.ui.login;
import com.megaache.mvvmdemo.base.BaseViewState;
import com.megaache.mvvmdemo.model.ErrorData;
import com.megaache.mvvmdemo.network.response.LoginResponse;
public class LoginViewState extends BaseViewState {
public static class Loading extends LoginViewState {
}
public static class Error extends LoginViewState {
private ErrorData message;
public Error(ErrorData message) {
this.message = message;
}
public void setMessage(ErrorData message) {
this.message = message;
}
public ErrorData getMessage() {
return message;
}
}
public static class LoggedIn extends LoginViewState {
private LoginResponse userDataResponse;
public LoggedIn(LoginResponse userDataResponse) {
this.userDataResponse = userDataResponse;
}
public LoginResponse getUserDataResponse() {
return userDataResponse;
}
public void setUserDataResponse(LoginResponse userDataResponse) {
this.userDataResponse = userDataResponse;
}
}
}
The code for the LoginViewModel.java is given below:
When the user clicks the login button, the method sendLoginRequest() will setup our retrofit Observable, the request will not be sent until the we call the method subscribe which will be done on the View. notice we are subscribing on the Schedulers.Io() Scheduler, which is will execute the requests in a background thread to avoid freezing the UI, and because of that we have to create our custom Observer that will invoke the callback code in UI thread after we receive data, more on this later:
Code:
package com.megaache.mvvmdemo.ui.login;
import com.megaache.mvvmdemo.base.BaseViewModel;
import com.megaache.mvvmdemo.MyApplication;
import com.megaache.mvvmdemo.model.ErrorData;
import com.megaache.mvvmdemo.network.request.LoginRequest;
import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import ohos.aafwk.abilityjet.activedata.ActiveData;
public class LoginViewModel extends BaseViewModel<LoginViewState> {
private static final int MIN_PASSWORD_LENGTH = 6;
public ActiveData<Boolean> emailValid = new ActiveData<>();
public ActiveData<Boolean> passwordValid = new ActiveData<>();
public ActiveData<Boolean> loginState = new ActiveData<>();
public LoginViewModel() {
super();
}
public void login(String email, String password) {
boolean isEmailValid = isEmailValid(email);
emailValid.setData(isEmailValid);
if (!isEmailValid)
return;
boolean isPasswordValid = isPasswordValid(email);
passwordValid.setData(isPasswordValid);
if (!isPasswordValid)
return;
LoginRequest loginRequest = new LoginRequest();
loginRequest.setEmail(email);
loginRequest.setPassword(password);
super.subscribe(sendLoginRequest(loginRequest));
}
private Observable<LoginViewState> sendLoginRequest(LoginRequest loginRequest) {
return MyApplication.createRetrofitClient()
.login(loginRequest)
.doOnError(Throwable::printStackTrace)
.map(LoginViewState.LoggedIn::new)
.cast(LoginViewState.class)
.onErrorReturn(throwable -> {
ErrorData errorData = new ErrorData();
if (throwable.getMessage() != null)
errorData.setMessage(throwable.getMessage());
else
errorData.setMessage(" No internet! ");
return new LoginViewState.Error(errorData);
})
.subscribeOn(Schedulers.io())
.startWith(new LoginViewState.Loading());
}
private boolean isEmailValid(String email) {
return email != null && !email.isEmpty() && email.contains("@");
}
private boolean isPasswordValid(String password) {
return password != null && password.length() > MIN_PASSWORD_LENGTH;
}
}
Settings up the ability (View)
As you know, ability is our view, we have instantiated ViewModel and observer states and ActiveDatas in the method ObserverData(), as mentioned before, retrofit will send the request on background thread, therefore the code in the Observer will run on the same thread (Schedulars.io()), which will cause exceptions if that code attemp to update the UI, to prevent that, we will create a custom UIObserver class which extends Observer, that will run our code in the UI task dispatcher of the ability (UI Thread), code for UiObserver.java as show below:
Code:
package com.megaache.mvvmdemo.utils;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.abilityjet.activedata.DataObserver;
import ohos.app.dispatcher.TaskDispatcher;
public abstract class UiObserver<T> extends DataObserver<T> {
private TaskDispatcher uiTaskDispatcher;
public UiObserver(Ability baseAbilitySlice) {
setLifecycle(baseAbilitySlice.getLifecycle());
uiTaskDispatcher = baseAbilitySlice.getUITaskDispatcher();
}
@Override
public void onChanged(T t) {
uiTaskDispatcher.asyncDispatch(() -> onValueChanged(t));
}
public abstract void onValueChanged(T t);
}
Code for LoginAbility.java is shown below:
Code:
package com.megaache.mvvmdemo.ui.login;
import com.megaache.mvvmdemo.ResourceTable;
import com.megaache.mvvmdemo.utils.UiObserver;
import com.megaache.mvvmdemo.model.ErrorData;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.Text;
import ohos.agp.components.TextField;
import ohos.agp.window.dialog.ToastDialog;
public class LoginAbility extends Ability {
private LoginViewModel loginViewModel;
private TextField emailTF;
private Text emailInvalidT;
private TextField passwordTF;
private Text passwordInvalidT;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
loginViewModel = new LoginViewModel();
initUI();
observeData();
}
private void initUI() {
super.setUIContent(ResourceTable.Layout_ability_login);
Button loginButton = (Button) findComponentById(ResourceTable.Id_btn_login);
loginButton.setClickedListener(c -> attemptLogin());
emailTF = (TextField) findComponentById(ResourceTable.Id_tf_email);
emailInvalidT = (Text) findComponentById(ResourceTable.Id_t_email_invalid);
passwordTF = (TextField) findComponentById(ResourceTable.Id_tf_password);
passwordInvalidT = (Text) findComponentById(ResourceTable.Id_t_password_invalid);
}
private void observeData() {
loginViewModel.emailValid.addObserver(new UiObserver<Boolean>(this) {
@Override
public void onValueChanged(Boolean aBoolean) {
emailInvalidT.setVisibility(aBoolean ? Component.VISIBLE : Component.HIDE);
}
}, false);
loginViewModel.passwordValid.addObserver(new UiObserver<Boolean>(this) {
@Override
public void onValueChanged(Boolean aBoolean) {
passwordInvalidT.setVisibility(aBoolean ? Component.VISIBLE : Component.HIDE);
}
}, false);
loginViewModel.getStates().addObserver(new UiObserver<LoginViewState>(this) {
@Override
public void onValueChanged(LoginViewState loginState) {
if (loginState instanceof LoginViewState.Loading) {
toggleLoadingDialog(true);
} else if (loginState instanceof LoginViewState.Error) {
toggleLoadingDialog(false);
manageError(((LoginViewState.Error) loginState).getMessage());
} else if (loginState instanceof LoginViewState.LoggedIn) {
toggleLoadingDialog(false);
showToast("logging successful!");
}
}
}, false);
}
private void attemptLogin() {
loginViewModel.login(emailTF.getText(), passwordTF.getText());
}
private void toggleLoadingDialog(boolean show) {
//todo: show/hide loading dialog
}
private void manageError(ErrorData errorData) {
showToast(errorData.getMessage());
}
private void showToast(String message) {
new ToastDialog(this)
.setText(message)
.show();
}
@Override
protected void onStop() {
super.onStop();
loginViewModel.unbind();
}
}
Tips And Tricks
If you want your app to work offline, its best to introduce a Repository classes that will handle quering information from server if internet is available or from the cach if not
For cleaner code, try re-using the ViewModel as much as possible, by creating a base class and moving the shared code their
You should not keep a reference to a View (component) or context in the ViewModel, unless you have no option
The ViewModel should not talk directly to the View, instead the View obseve the ViewModel and update itself depending on ViewModel data
A correct implementation of ViewModel should allow you to change the UI with minimal or zero changes to the ViewModel.
Conclusion
MVVM combines the advantages of separation of concerns provided by MVP archichetecture, while leveraging the advantages of RxJava or Data binding. The result is a pattern where the model drives as many of the operations as possible, minimizing the logic in the view.
Finally, talk is cheap, and I strongly advise you to try and learn these things in the code so that you do not need to rely on people like me to tell you what to do.
Clone the project from github, replate the API Urls in class config.java and run it on HarmonyOs Device, you should see a toast that says "Logging succesful" if the credentials are correct, otherwise it should show a toast with the error that says "no internet" or and error returned from the server.
This project is available on GitHub: Click here
Recommended sources:
HarmonyOS (essential topics): Essential Topics
Retrofit: Click here
Rxjava: Click here
Original Source
Comment below if you have any questions or suggestions.
Thank you!
In Android we have Manifest file right there we will declare all the activity names like that do we have any file here?

Expert: Directory App MVVM Jetpack (Video Call with Webrtc & HMS Analytics and Crash Kit) in Android using Kotlin- Part-6

Overview
In this article, I will create a Directory android application using Webrtc Video Calling App in which I will integrate HMS Core kits such as HMS Account, AuthService, Identity Kit, Firebase Auth, Firebase Realtime DB and CloudDB .
App will make use of android MVVM clean architecture using Jetpack components such as DataBinding, AndroidViewModel, Observer, LiveData and much more.
In this article we are going to implement DataBinding using Observable pattern.
Huawei Analytics Kit Introduction
Huawei Analytics Kit is a one-stop user behavior analysis platform for products such as mobile apps, web apps, quick apps, quick games, and mini-programs. It offers scenario-specific data collection, management, analysis, and usage, helping enterprises achieve effective user acquisition, product optimization, precise operations, and business growth..
Flexible synchronization modes:
Cloud DB supports cache and local data synchronization modes. In cache mode, data on the device is a subset of data on the cloud. If persistent cache is allowed, query results will be automatically cached on the device. In local mode, data is stored locally and is not synchronized to the cloud.
Powerful query capability:
Cloud DB supports various predicate query methods. Multiple chain filtering conditions can be used to filter and sort returned results, and limit the number of objects in the returned result set. In cache mode, data can be queried from the Cloud DB zone on the cloud or that on the local device. In local mode, data is directly queried from the Cloud DB zone on the local device.
Real-time update:
In cache mode of Cloud DB, you can listen on data as needed and use the data synchronization function of Cloud DB to update changed data between the device and cloud in real time.
Offline operations:
In cache mode of Cloud DB, if persistent cache is allowed, the application query is automatically changed from Cloud DB to the local host after the device gets offline. All data modified locally will be automatically synchronized to Cloud DB after the device gets online.
Scalability:
Cloud DB provides powerful HUAWEI CLOUD infrastructure functions, including automatic multi-region data replication, consistency assurance, atomic batch operations, and transaction support.
Security level:
Cloud DB supports device-cloud data full encryption management, triple authentication by app, user, and service, and role-based permission management to ensure data security.
WebRTC Service Introduction
WebRTC is a free and open-source project providing web browsers and mobile applications with real-time communication via application programming interfaces.
Prerequisite
Huawei Phone EMUI 3.0 or later.
Non-Huawei phones Android 4.4 or later (API level 19 or higher).
HMS Core APK 4.0.0.300 or later
Android Studio
AppGallery Account
App Gallery Integration process
1. Sign In and Create or Choose a project on AppGallery Connect portal.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
2. Navigate to Project settings and download the configuration file.
3. Navigate to General Information, and then provide Data Storage location.
App Development
Add Required Dependencies:
Launch Android studio and create a new project. Once the project is ready.
Add following dependency for HMS Kits
Code:
//HMS Kits
implementation 'com.huawei.hms:hianalytics:5.0.3.300'
implementation 'com.huawei.agconnect:agconnect-crash:1.4.1.300'
implementation 'com.huawei.agconnect:agconnect-core:1.5.0.300'
implementation 'com.huawei.hms:hwid:5.3.0.302'
implementation 'com.huawei.hms:identity:5.3.0.300'
implementation 'com.huawei.agconnect:agconnect-cloud-database:1.5.0.300'Copy codeCopy code//Google Firebase
implementation platform('com.google.firebase:firebase-bom:28.4.1')
implementation 'com.google.firebase:firebase-analytics'
implementation 'com.google.firebase:firebase-auth'
implementation 'com.google.firebase:firebase-database'
implementation 'com.google.android.gms:play-services-auth:19.2.0'
implementation 'com.airbnb.android:lottie:4.1.0'
implementation 'com.mikhaellopez:circularimageview:4.3.0'
implementation 'com.kaopiz:kprogresshud:1.2.0'
implementation 'com.google.android.gms:play-services-ads:20.4.0' implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
Navigate to the Gradle scripts folder and open build.gradle (project: app)
Code:
repositories {
google()
jcenter()
maven {url 'https://developer.huawei.com/repo/'}
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.1"
classpath 'com.huawei.agconnect:agcp:1.4.2.300'
Configure AndroidManifest.xml.
Code:
<meta-data
android:name="install_channel"
android:value="AppGallery" />
<meta-data
android:name="com.huawei.hms.client.appid"
android:value="104460711" />
</application>
Code Implementation
Created following package model, event, viewmodel.
ViewModel: The ViewModel makes it easy to update data changes on the UI.Create a package named viewmodel in your main folder.Then create a new file and name it LoginViewModel.kt along with their FactoryViewModelProviders.
MainActivity.kt:
Code:
package com.hms.directoryclass MainActivity : AppCompatActivity(), ActivityNavigation { private lateinit var viewModel: LoginViewModel
private lateinit var dataBinding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main) val viewModel: LoginViewModel by lazy {
val activity = requireNotNull(this) {}
ViewModelProviders.of(this, LoginViewModelFactory(activity.application))
.get(LoginViewModel::class.java)
} dataBinding.loginViewModel = viewModel
dataBinding.lifecycleOwner = this
viewModel.startActivityForResultEvent.setEventReceiver(this, this)
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
viewModel.onResultFromActivity(requestCode, data)
super.onActivityResult(requestCode, resultCode, data)
}}
LoginViewModel.kt:
Code:
package com.hms.directory.viewmodel
@SuppressLint("StaticFieldLeak")
class LoginViewModel(application: Application) : AndroidViewModel(application), Observable { private val context = getApplication<Application>().applicationContext
private var mAuthManager: AccountAuthService? = null
private var mAuthParam: AccountAuthParams? = null val startActivityForResultEvent = LiveMessageEvent<ActivityNavigation>() fun login() {
val intent = Intent(context, OrderActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent) /* mAuthParam = AccountAuthParamsHelper(AccountAuthParams.DEFAULT_AUTH_REQUEST_PARAM)
.setIdToken()
.setAccessToken()
.createParams()
mAuthManager = AccountAuthManager.getService(Activity(), mAuthParam)
startActivityForResultEvent.sendEvent {
startActivityForResult(
mAuthManager?.signInIntent,
HMS_SIGN_IN
)
}*/
} fun onResultFromActivity(requestCode: Int, data: Intent?) {
when (requestCode) {
HMS_SIGN_IN -> {
val authAccountTask = AccountAuthManager.parseAuthResultFromIntent(data)
onCompleteLogin(authAccountTask)
}
}
} private fun onCompleteLogin(doneTask: Task<AuthAccount>) {
if (doneTask.isSuccessful) {
val authAccount = doneTask.result
Log.d("LoginViewModel", "SigIn Success")
context.startActivity(Intent(context, ContactListActivity::class.java)) } else {
Log.d("LoginViewModel", "SigIn Error")
}
} override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
} override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
}}
ContactActivity.kt:
Code:
public class ContactListActivity extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contact_list); // Load contacts from file
Contacts.loadData(this); // Set up recycler view and fill it with all the contacts
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.contact_list);
recyclerView.setAdapter(new ContactListAdapter(this, Contacts.LIST));
AGConnectCrash.getInstance().enableCrashCollection(false);
//Crash application
AGConnectCrash.getInstance().testIt(this)
}
LoginFireBaseActivity.java
Code:
package com.hms.directory.app.call;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.FirebaseApp;
import com.google.firebase.auth.AuthCredential;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.GoogleAuthProvider;
import com.google.firebase.database.FirebaseDatabase;
import com.hms.corrierapp.R;
import com.hms.directory.app.call.models.User;import org.jetbrains.annotations.NotNull;public class LoginActivity extends AppCompatActivity { GoogleSignInClient mGoogleSignInClient;
int RC_SIGN_IN = 11;
FirebaseAuth mAuth;
FirebaseDatabase database; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_goole); mAuth = FirebaseAuth.getInstance();
if (mAuth.getCurrentUser() != null) {
goToNextActivity();
} database = FirebaseDatabase.getInstance(); GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken("1016048264402-439a9aamtpiajbgqeqg24qkum2bb7fmh.apps.googleusercontent.com")
.requestEmail()
.build(); mGoogleSignInClient = GoogleSignIn.getClient(this, gso); findViewById(R.id.loginBtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = mGoogleSignInClient.getSignInIntent(); startActivityForResult(intent, RC_SIGN_IN);
//startActivity(new Intent(LoginActivity.this, MainActivity.class));
}
});
} void goToNextActivity() {
startActivity(new Intent(LoginActivity.this, MainActivity.class));
finish();
} @Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable @org.jetbrains.annotations.Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data); if (requestCode == RC_SIGN_IN) {
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
GoogleSignInAccount account = task.getResult();
authWithGoogle(account.getIdToken());
}
} void authWithGoogle(String idToken) {
AuthCredential credential = GoogleAuthProvider.getCredential(idToken, null);
mAuth.signInWithCredential(credential)
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull @NotNull Task<AuthResult> task) {
if (task.isSuccessful()) {
FirebaseUser user = mAuth.getCurrentUser();
User firebaseUser = new User(user.getUid(), user.getDisplayName(), user.getPhotoUrl().toString(), "Unknown", 500);
database.getReference()
.child("profiles")
.child(user.getUid())
.setValue(firebaseUser).addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull @NotNull Task<Void> task) {
if (task.isSuccessful()) {
startActivity(new Intent(LoginActivity.this, MainActivity.class));
finishAffinity();
} else {
Toast.makeText(LoginActivity.this, task.getException().getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
}
});
//Log.e("profile", user.getPhotoUrl().toString());
} else {
Log.e("err", task.getException().getLocalizedMessage());
}
}
});
}
}
CallConnectingActivity.java
Code:
public class ConnectingActivity extends AppCompatActivity { ActivityConnectingBinding binding;
FirebaseAuth auth;
FirebaseDatabase database;
boolean isOkay = false; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityConnectingBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); auth = FirebaseAuth.getInstance();
database = FirebaseDatabase.getInstance(); String profile = getIntent().getStringExtra("profile");
Glide.with(this)
.load(profile)
.into(binding.profile); String username = auth.getUid(); database.getReference().child("users")
.orderByChild("status")
.equalTo(0).limitToFirst(1)
.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull @NotNull DataSnapshot snapshot) {
if (snapshot.getChildrenCount() > 0) {
isOkay = true;
// Room Available
for (DataSnapshot childSnap : snapshot.getChildren()) {
database.getReference()
.child("users")
.child(childSnap.getKey())
.child("incoming")
.setValue(username);
database.getReference()
.child("users")
.child(childSnap.getKey())
.child("status")
.setValue(1);
Intent intent = new Intent(ConnectingActivity.this, CallActivity.class);
String incoming = childSnap.child("incoming").getValue(String.class);
String createdBy = childSnap.child("createdBy").getValue(String.class);
boolean isAvailable = childSnap.child("isAvailable").getValue(Boolean.class);
intent.putExtra("username", username);
intent.putExtra("incoming", incoming);
intent.putExtra("createdBy", createdBy);
intent.putExtra("isAvailable", isAvailable);
startActivity(intent);
finish();
}
} else {
// Not Available HashMap<String, Object> room = new HashMap<>();
room.put("incoming", username);
room.put("createdBy", username);
room.put("isAvailable", true);
room.put("status", 0); database.getReference()
.child("users")
.child(username)
.setValue(room).addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void unused) {
database.getReference()
.child("users")
.child(username).addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull @NotNull DataSnapshot snapshot) {
if (snapshot.child("status").exists()) {
if (snapshot.child("status").getValue(Integer.class) == 1) { if (isOkay)
return; isOkay = true;
Intent intent = new Intent(ConnectingActivity.this, CallActivity.class);
String incoming = snapshot.child("incoming").getValue(String.class);
String createdBy = snapshot.child("createdBy").getValue(String.class);
boolean isAvailable = snapshot.child("isAvailable").getValue(Boolean.class);
intent.putExtra("username", username);
intent.putExtra("incoming", incoming);
intent.putExtra("createdBy", createdBy);
intent.putExtra("isAvailable", isAvailable);
startActivity(intent);
finish();
}
}
} @Override
public void onCancelled(@NonNull @NotNull DatabaseError error) { }
});
}
}); }
} @Override
public void onCancelled(@NonNull @NotNull DatabaseError error) { }
});
}
}
CloudDB:
Code:
import android.content.Context;
import android.util.Log;import com.huawei.agconnect.AGCRoutePolicy;
import com.huawei.agconnect.AGConnectInstance;
import com.huawei.agconnect.AGConnectOptionsBuilder;
import com.huawei.agconnect.auth.AGConnectAuth;
import com.huawei.agconnect.cloud.database.AGConnectCloudDB;
import com.huawei.agconnect.cloud.database.CloudDBZone;
import com.huawei.agconnect.cloud.database.CloudDBZoneConfig;
import com.huawei.agconnect.cloud.database.CloudDBZoneQuery;
import com.huawei.agconnect.cloud.database.exceptions.AGConnectCloudDBException;
import com.huawei.hmf.tasks.OnFailureListener;
import com.huawei.hmf.tasks.OnSuccessListener;
import com.huawei.hmf.tasks.Task;import static android.content.ContentValues.TAG;public class CloudDB { private Context context;
private static CloudDB instance; private AGConnectCloudDB mCloudDB;
private CloudDBZoneConfig mConfig;
private CloudDBZone mCloudDBZone; private CloudDB(Context context) {
this.context=context;
} public static CloudDB getInstance(Context context) {
if (instance==null)instance=new CloudDB(context);
return instance;
} public CloudDB initAGConnectCloudDB() {
AGConnectCloudDB.initialize(context);
return this;
} public CloudDB createCloudDb(){ AGConnectInstance instance = AGConnectInstance.buildInstance(new AGConnectOptionsBuilder().setRoutePolicy(AGCRoutePolicy.CHINA).build(mContext));
mCloudDB = AGConnectCloudDB.getInstance(instance, AGConnectAuth.getInstance(instance));
mCloudDB.createObjectType(ObjectTypeInfoHelper.getObjectTypeInfo()); return this;
} public void configCloudDb(){
mConfig = new CloudDBZoneConfig("QuickStartDemo",
CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);
mConfig.setPersistenceEnabled(true);
Task<CloudDBZone> openDBZoneTask = mCloudDB.openCloudDBZone2(mConfig, true);
openDBZoneTask.addOnSuccessListener(new OnSuccessListener<CloudDBZone>() {
@Override
public void onSuccess(CloudDBZone cloudDBZone) {
Log.i(TAG, "open cloudDBZone success");
mCloudDBZone = cloudDBZone;
// Add subscription after opening cloudDBZone success
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.w(TAG, "open cloudDBZone failed for " + e.getMessage());
}
});
} public void upsertBookInfos(BookInfo bookInfo) {
if (mCloudDBZone == null) {
Log.w(TAG, "CloudDBZone is null, try re-open it");
return;
}
Task<Integer> upsertTask = mCloudDBZone.executeUpsert(bookInfo);
upsertTask.addOnSuccessListener(new OnSuccessListener<Integer>() {
@Override
public void onSuccess(Integer cloudDBZoneResult) {
Log.i(TAG, "Upsert " + cloudDBZoneResult + " records");
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
mUiCallBack.updateUiOnError("Insert book info failed");
}
});
} public void viewCloudDbData(){
private OnSnapshotListener<BookInfo> mSnapshotListener = new OnSnapshotListener<BookInfo>() {
@Override
public void onSnapshot(CloudDBZoneSnapshot<BookInfo> cloudDBZoneSnapshot, AGConnectCloudDBException e) {
if (e != null) {
Log.w(TAG, "onSnapshot: " + e.getMessage());
return;
}
CloudDBZoneObjectList<BookInfo> snapshotObjects = cloudDBZoneSnapshot.getSnapshotObjects();
List<BookInfo> bookInfos = new ArrayList<>();
try {
if (snapshotObjects != null) {
while (snapshotObjects.hasNext()) {
BookInfo bookInfo = snapshotObjects.next();
bookInfos.add(bookInfo);
updateBookIndex(bookInfo);
}
}
mUiCallBack.onSubscribe(bookInfos);
} catch (AGConnectCloudDBException snapshotException) {
Log.w(TAG, "onSnapshot:(getObject) " + snapshotException.getMessage());
} finally {
cloudDBZoneSnapshot.release();
}
}
};
} public void addSubscription() {
if (mCloudDBZone == null) {
Log.w(TAG, "CloudDBZone is null, try re-open it");
return;
} try {
CloudDBZoneQuery<BookInfo> snapshotQuery = CloudDBZoneQuery.where(BookInfo.class)
.equalTo(BookEditFields.SHADOW_FLAG, true);
mRegister = mCloudDBZone.subscribeSnapshot(snapshotQuery,
CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY, mSnapshotListener);
} catch (AGConnectCloudDBException e) {
Log.w(TAG, "subscribeSnapshot: " + e.getMessage());
}
}
}
Xml layout DataBinding
To include data binding in the UI, enclose all content with <layout></layout>.
The ViewModel is introduced to the layout in the <data></data> section, as shown. Ensure that the type value points to the specific folder that has the required ViewModel.
App Build Result
Tips and Tricks
Identity Kit displays the HUAWEI ID registration or sign-in page first. The user can use the functions provided by Identity Kit only after signing in using a registered HUAWEI ID.
Conclusion
In this article, we have learned how to integrate Huawei Cloud DB using Webrtc Video Call in Android application. After completely read this article user can easily implement Huawei Crash Kit in the Directory App android application using Kotlin.
Thanks for reading this article. Be sure to like and comment to this article, if you found it helpful. It means a lot to me.
References
HMS Docs:
https://developer.huawei.com/consum.../HMSCore-Guides/introduction-0000001050048870
https://developer.huawei.com/consum...des/agc-clouddb-introduction-0000001054212760

Categories

Resources