[Huawei Developers] Efnan Akkus:
[IMG="1"]https://miro.medium.com/max/1050/1*ZfDjY1xgLFB1d6mNlMZ-Fg.jpeg[/IMG]

Hello everyone, in this article, we’ll develop an android application using the Huawei Health kit’s data controller feature. Lets get start it.

About the Service

HUAWEI Health Kit (Health Kit for short) allows ecosystem apps to access fitness and health data of users based on their HUAWEI ID and authorization. For consumers, Health Kit provides a mechanism for fitness and health data storage and sharing based on flexible authorization. For developers and partners, Health Kit provides a data platform and fitness and health open capabilities, so that they can build related apps and services based on a multitude of data types. Health Kit connects the hardware devices and ecosystem apps to provide consumers with health care, workout guidance, and ultimate service experience.

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.

Add required dependency to the app-level build.gradle file.

defaultConfig {
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resConfigs "en", "zh-rCN", "tr"
}

}

dependencies {

implementation 'com.huawei.hms:health:5.0.3.300'

}

Lets add the required permissions to the AndroidManifest.xml file.

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

Applying for Health Kit

You should select the data access permissions that must be applied for the product.

[IMG="1"]https://miro.medium.com/max/1050/1*r9uzh8owKL_ukcUvZyarqw.png[/IMG]

For more detail you should visit: https://developer.huawei.com/consumer/en/doc/apply-kitservice-0000001050071707-V5

Developing Your App

Signing In and Applying for Scopes

The developer’s app calls the related APIs to display HUAWEI ID sign-in screen and authorization screen. The app can only access data upon user authorization. The user can select the data types to be authorized and grant only some data permissions.

public class HealthkitActivity extends AppCompatActivity {

private static final String TAG = "KitConnectActivity";

// Request code for displaying the authorization screen using the startActivityForResult method.
// The value can be defined by developers.
private static final int REQUEST_SIGN_IN_LOGIN = 1002;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_healthkit);
signIn();
}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);

// Handle the sign-in response.
handleSignInResult(requestCode, data);
}

private void signIn() {
Log.i(TAG, "begin sign in");
List<Scope> scopeList = new ArrayList<>();

// Add scopes to apply for. The following only shows an example.
// Developers need to add scopes according to their specific needs.

// View and save steps in HUAWEI Health Kit.
scopeList.add(new Scope(Scopes.HEALTHKIT_STEP_BOTH));

// View and save height and weight in HUAWEI Health Kit.
scopeList.add(new Scope(Scopes.HEALTHKIT_HEIGHTWEIGHT_BOTH));

// View and save the heart rate data in HUAWEI Health Kit.
scopeList.add(new Scope(Scopes.HEALTHKIT_HEARTRATE_BOTH));

// Used for recording real-time steps in HUAWEI Health Kit.
// scopeList.add(new Scope(Scopes.HEALTHKIT_STEP_REALTIME));

// Used for recording real-time heartRate in HUAWEI Health Kit.
//scopeList.add(new Scope(Scopes.HEALTHKIT_HEARTRATE_REALTIME));

// View and save activityRecord in HUAWEI Health Kit.
// scopeList.add(new Scope(Scopes.HEALTHKIT_ACTIVITY_RECORD_BOTH));

// Configure authorization parameters.
HuaweiIdAuthParamsHelper authParamsHelper =
new HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM);
HuaweiIdAuthParams authParams =
authParamsHelper.setIdToken().setAccessToken().setScopeList(scopeList).createParams();

// Initialize the HuaweiIdAuthService object.
final HuaweiIdAuthService authService = HuaweiIdAuthManager.getService(getApplicationContext(), authParams);

Task<AuthHuaweiId> authHuaweiIdTask = authService.silentSignIn();

final Context context = this;

// Add the callback for the call result.
authHuaweiIdTask.addOnSuccessListener(new OnSuccessListener<AuthHuaweiId>() {
@Override
public void onSuccess(AuthHuaweiId huaweiId) {
// The silent sign-in is successful.
Log.i(TAG, "silentSignIn success");
Toast.makeText(context, "silentSignIn success", Toast.LENGTH_LONG).show();
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception exception) {
// The silent sign-in fails.
// This indicates that the authorization has not been granted by the current account.
if (exception instanceof ApiException) {
ApiException apiException = (ApiException) exception;
Log.i(TAG, "sign failed status:" + apiException.getStatusCode());
Toast.makeText(context, "sign failed status:" + apiException.getStatusCode(), Toast.LENGTH_LONG).show();
Log.i(TAG, "begin sign in by intent");
Toast.makeText(context, "begin sign in by intent", Toast.LENGTH_LONG).show();

// Call the sign-in API using the getSignInIntent() method.
Intent signInIntent = authService.getSignInIntent();

startActivityForResult(signInIntent, REQUEST_SIGN_IN_LOGIN);
}
}
});
}

private void handleSignInResult(int requestCode, Intent data) {
// Handle only the authorized responses
if (requestCode != REQUEST_SIGN_IN_LOGIN) {
return;
}

// Obtain the authorization response from the intent.
HuaweiIdAuthResult result = HuaweiIdAuthAPIManager.HuaweiIdAuthAPIService.parseHuaweiIdFromIntent(data);
if (result != null) {
Log.d(TAG, "handleSignInResult status = " + result.getStatus() + ", result = " + result.isSuccess());
Toast.makeText(this, "handleSignInResult status = "+ result.getStatus() + ", result = " + result.isSuccess(), Toast.LENGTH_LONG).show();
if (result.isSuccess()) {
Log.d(TAG, "sign in is success");
Toast.makeText(this, "sign in is success", Toast.LENGTH_LONG).show();

// Obtain the authorization result.
HuaweiIdAuthResult authResult =
HuaweiIdAuthAPIManager.HuaweiIdAuthAPIService.parseHuaweiIdFromIntent(data);
}
}
}

}

For details about the sign-in process, please refer to HUAWEI Account Kit Development Guide.

[IMG="2"]https://miro.medium.com/max/788/1*JZ9J3fYcfUdfWOa6lkLwsQ.png[/IMG]

DataController

After integrating Health Kit, the app is able to call ten methods in DataController to perform operations on the fitness and health data. The methods include:

insert: inserts data.

delete: deletes data.

update: updates data.

read: reads data.

readTodaySummation: queries the statistical data of the current day.

readDailySummation: queries the statistical data of multiple days.

clearAll: clears data of the app from the device and cloud.

Inserting the User’s Fitness and Health Data

Insert the user’s fitness and health data into the Health platform.

public class HealthDataControllerActivity extends AppCompatActivity {
private static final String TAG = "DataController";

// Object of controller for fitness and health data, providing APIs for read/write, batch read/write, and listening
private DataController dataController;

// Internal context object of the activity
private Context context;

// PendingIntent, required when registering or unregistering a listener within the data controller
private PendingIntent pendingIntent;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_health_data_controller);
context = this;
logInfoView = (TextView) findViewById(R.id.data_controller_log_info);
logInfoView.setMovementMethod(ScrollingMovementMethod.getInstance());
initDataController();
syncAllData = findViewById(R.id.syncAllData);

syncAllData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
syncAllData(dataController);
}
});

}

/**
* Initialize a data controller object.
*/
private void initDataController() {
// Obtain and set the read & write permissions for DT_CONTINUOUS_STEPS_DELTA and DT_INSTANTANEOUS_HEIGHT.
// Use the obtained permissions to obtain the data controller object.
HiHealthOptions hiHealthOptions = HiHealthOptions.builder()
.addDataType(DataType.DT_CONTINUOUS_STEPS_DELTA, HiHealthOptions.ACCESS_READ)
.addDataType(DataType.DT_CONTINUOUS_STEPS_DELTA, HiHealthOptions.ACCESS_WRITE)
.addDataType(DataType.DT_INSTANTANEOUS_HEIGHT, HiHealthOptions.ACCESS_READ)
.addDataType(DataType.DT_INSTANTANEOUS_HEIGHT, HiHealthOptions.ACCESS_WRITE)
.build();
AuthHuaweiId signInHuaweiId = HuaweiIdAuthManager.getExtendedAuthResult(hiHealthOptions);
dataController = HuaweiHiHealth.getDataController(context, signInHuaweiId);
}

/**
* Use the data controller to add a sampling dataset.
*
* @param view (indicating a UI object)
* @throws ParseException (indicating a failure to parse the time string)
*/
public void insertData(View view) throws ParseException {
// 1. Build a DataCollector object.
DataCollector dataCollector = new DataCollector.Builder().setPackageName(context)
.setDataType(DataType.DT_CONTINUOUS_STEPS_DELTA)
.setDataStreamName("STEPS_DELTA")
.setDataGenerateType(DataCollector.DATA_TYPE_RAW)
.build();

// 2. Create a sampling dataset set based on the data collector.
final SampleSet sampleSet = SampleSet.create(dataCollector);

// 3. Build the start time, end time, and incremental step count for a DT_CONTINUOUS_STEPS_DELTA sampling point.
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date startDate = dateFormat.parse("2020-09-23 09:00:00");
//Date startDate = dateFormat.parse(start_Date.getText().toString());
Date endDate = dateFormat.parse("2020-09-23 09:05:00");
//Date startDate = dateFormat.parse(end_Date.getText().toString());
/*try {
// Enter the start time and end time. The standard UNIX timestamp is used for storage, without considering the time zone differences.
startDate = dateFormat.parse("2020-03-17 09:00:00");
endDate = dateFormat.parse("2020-03-17 09:05:00");
} catch (ParseException e) {
logger("Time parsing error");
}*/
int stepsDelta = 1000;

// 4. Build a DT_CONTINUOUS_STEPS_DELTA sampling point.
SamplePoint samplePoint = sampleSet.createSamplePoint()
.setTimeInterval(startDate.getTime(), endDate.getTime(), TimeUnit.MILLISECONDS);
samplePoint.getFieldValue(Field.FIELD_STEPS_DELTA).setIntValue(stepsDelta);

// 5. Save a DT_CONTINUOUS_STEPS_DELTA sampling point to the sampling dataset.
// You can repeat steps 3 through 5 to add more sampling points to the sampling dataset.
sampleSet.addSample(samplePoint);

// 6. Call the data controller to insert the sampling dataset into the Health platform.
Task<Void> insertTask = dataController.insert(sampleSet);

// 7. Calling the data controller to insert the sampling dataset is an asynchronous operation.
// Therefore, a listener needs to be registered to monitor whether the data insertion is successful or not.
insertTask.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void result) {
logger("Success insert an SampleSet into HMS core");
showSampleSet(sampleSet);
logger(SPLIT);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
printFailureMessage(e, "insert");
}
});
}

Deleting the User’s Fitness and Health Data

Only historical data that has been inserted by the current app can be deleted from the Health platform.

/**
* Use the data controller to delete the sampling data by specific criteria.
*
* @param view (indicating a UI object)
* @throws ParseException (indicating a failure to parse the time string)
*/
public void deleteData(View view) throws ParseException {
// 1. Build the condition for data deletion: a DataCollector object.
DataCollector dataCollector = new DataCollector.Builder().setPackageName(context)
.setDataType(DataType.DT_CONTINUOUS_STEPS_DELTA)
.setDataStreamName("STEPS_DELTA")
.setDataGenerateType(DataCollector.DATA_TYPE_RAW)
.build();

// 2. Build the time range for the deletion: start time and end time.
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date startDate = dateFormat.parse("2020-08-27 09:00:00");
Date endDate = dateFormat.parse("2020-08-27 09:05:00");

// 3. Build a parameter object as the conditions for the deletion.
DeleteOptions deleteOptions = new DeleteOptions.Builder().addDataCollector(dataCollector)
.setTimeInterval(startDate.getTime(), endDate.getTime(), TimeUnit.MILLISECONDS)
.build();

// 4. Use the specified condition deletion object to call the data controller to delete the sampling dataset.
Task<Void> deleteTask = dataController.delete(deleteOptions);

// 5. Calling the data controller to delete the sampling dataset is an asynchronous operation.
// Therefore, a listener needs to be registered to monitor whether the data deletion is successful or not.
deleteTask.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void result) {
logger("Success delete sample data from HMS core");
logger(SPLIT);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
String errorCode = e.getMessage();
String errorMsg = HiHealthStatusCodes.getStatusCodeMessage(Integer.parseInt(errorCode));
logger(errorCode + ": " + errorMsg);
printFailureMessage(e, "delete");
}
});
}

Updating the User’s Fitness and Health Data

/**
* Use the data controller to modify the sampling data by specific criteria.
*
* @param view (indicating a UI object)
* @throws ParseException (indicating a failure to parse the time string)
*/
public void updateData(View view) throws ParseException {
// 1. Build the condition for data update: a DataCollector object.
DataCollector dataCollector = new DataCollector.Builder().setPackageName(context)
.setDataType(DataType.DT_CONTINUOUS_STEPS_DELTA)
.setDataStreamName("STEPS_DELTA")
.setDataGenerateType(DataCollector.DATA_TYPE_RAW)
.build();

// 2. Build the sampling dataset for the update: create a sampling dataset
// for the update based on the data collector.
SampleSet sampleSet = SampleSet.create(dataCollector);

// 3. Build the start time, end time, and incremental step count for
// a DT_CONTINUOUS_STEPS_DELTA sampling point for the update.
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date startDate = dateFormat.parse("2020-08-27 09:00:00");
Date endDate = dateFormat.parse("2020-08-27 09:05:00");
int stepsDelta = 2000;

// 4. Build a DT_CONTINUOUS_STEPS_DELTA sampling point for the update.
SamplePoint samplePoint = sampleSet.createSamplePoint()
.setTimeInterval(startDate.getTime(), endDate.getTime(), TimeUnit.MILLISECONDS);
samplePoint.getFieldValue(Field.FIELD_STEPS_DELTA).setIntValue(stepsDelta);

// 5. Add an updated DT_CONTINUOUS_STEPS_DELTA sampling point to the sampling dataset for the update.
// You can repeat steps 3 through 5 to add more updated sampling points to the sampling dataset for the update.
sampleSet.addSample(samplePoint);

// 6. Build a parameter object for the update.
// Note: (1) The start time of the modified object updateOptions cannot be greater than the minimum
// value of the start time of all sample data points in the modified data sample set
// (2) The end time of the modified object updateOptions cannot be less than the maximum value of the
// end time of all sample data points in the modified data sample set
UpdateOptions updateOptions =
new UpdateOptions.Builder().setTimeInterval(startDate.getTime(), endDate.getTime(), TimeUnit.MILLISECONDS)
.setSampleSet(sampleSet)
.build();

// 7. Use the specified parameter object for the update to call the
// data controller to modify the sampling dataset.
Task<Void> updateTask = dataController.update(updateOptions);

// 8. Calling the data controller to modify the sampling dataset is an asynchronous operation.
// Therefore, a listener needs to be registered to monitor whether the data update is successful or not.
updateTask.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void result) {
logger("Success update sample data from HMS core");
logger(SPLIT);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
printFailureMessage(e, "update");
String errorCode = e.getMessage();
String errorMsg = HiHealthStatusCodes.getStatusCodeMessage(Integer.parseInt(errorCode));
logger(errorCode + ": " + errorMsg);
}
});
}

Querying the User’s Fitness and Health Data

To read historical data from the Health platform, for example, to read the number of steps taken within a period of time, you can specify the read conditions in ReadOptions. For example, you can specify the data collector, data type, and detailed data. The dataset that matches the query criteria will be returned.

/**
* Use the data controller to query the sampling dataset by specific criteria.
*
* @param view (indicating a UI object)
* @throws ParseException (indicating a failure to parse the time string)
*/
public void readData(View view) throws ParseException {
// 1. Build the condition for data query: a DataCollector object.
DataCollector dataCollector = new DataCollector.Builder().setPackageName(context)
.setDataType(DataType.DT_CONTINUOUS_STEPS_DELTA)
.setDataStreamName("STEPS_DELTA")
.setDataGenerateType(DataCollector.DATA_TYPE_RAW)
.build();

// 2. Build the time range for the query: start time and end time.
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date startDate = dateFormat.parse("2020-08-27 09:00:00");
Date endDate = dateFormat.parse("2020-08-27 09:05:00");
try {
// Enter the start time and end time. The standard UNIX timestamp is used for storage, without considering the time zone differences. Data points within the specified timestamp range will be queried.
startDate = dateFormat.parse("2020-03-17 09:00:00");
endDate = dateFormat.parse("2020-03-17 09:05:00");
} catch (ParseException exception) {
logger("Time parsing error");
}

// 3. Build the condition-based query objec
ReadOptions readOptions = new ReadOptions.Builder().read(dataCollector)
.setTimeRange(startDate.getTime(), endDate.getTime(), TimeUnit.MILLISECONDS)
.build();

// 4. Use the specified condition query object to call the data controller to query the sampling dataset.
Task<ReadReply> readReplyTask = dataController.read(readOptions);

// 5. Calling the data controller to query the sampling dataset is an asynchronous operation.
// Therefore, a listener needs to be registered to monitor whether the data query is successful or not.
readReplyTask.addOnSuccessListener(new OnSuccessListener<ReadReply>() {
@Override
public void onSuccess(ReadReply readReply) {
logger("Success read an SampleSets from HMS core");
for (SampleSet sampleSet : readReply.getSampleSets()) {
showSampleSet(sampleSet);
}
logger(SPLIT);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
printFailureMessage(e, "read");
String errorCode = e.getMessage();
String errorMsg = HiHealthStatusCodes.getStatusCodeMessage(Integer.parseInt(errorCode));
logger(errorCode + ": " + errorMsg);
}
});
}

Querying the Statistical Fitness and Health Data of the User of the Day

/**
* Use the data controller to query the summary data of the current day by data type.
*
* @param view (indicating a UI object)
*/
public void readToday(View view) {
// 1. Use the specified data type (DT_CONTINUOUS_STEPS_DELTA) to call the data controller to query
// the summary data of this data type of the current day.
Task<SampleSet> todaySummationTask = dataController.readTodaySummation(DataType.DT_CONTINUOUS_STEPS_DELTA);

// 2. Calling the data controller to query the summary data of the current day is an
// asynchronous operation. Therefore, a listener needs to be registered to monitor whether
// the data query is successful or not.
// Note: In this example, the inserted data time is fixed at 2020-08-27 09:05:00.
// When commissioning the API, you need to change the inserted data time to the current date
// for data to be queried.
todaySummationTask.addOnSuccessListener(new OnSuccessListener<SampleSet>() {
@Override
public void onSuccess(SampleSet sampleSet) {
logger("Success read today summation from HMS core");
if (sampleSet != null) {
showSampleSet(sampleSet);
}
logger(SPLIT);
}
});
todaySummationTask.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {

printFailureMessage(e, "readTodaySummation");
String errorCode = e.getMessage();
String errorMsg = HiHealthStatusCodes.getStatusCodeMessage(Integer.parseInt(errorCode));
logger(errorCode + ": " + errorMsg);
}
});
}

Querying the Statistical Fitness and Health Data of the User of Multiple Days

/**
* Querying the Summary Fitness and Health Data of the User on the Local Device of the Current Day
*
* @param view (indicating a UI object)
*/
public void currentDay(View view) {
//Call the DataController to query the statistical value of the DT_CONTINUOUS_STEPS_DELTA data type of the current day.
// The query time range starts from 00:00:00 of the day and ends at the system timestamp when the API is called.
// Calling this API will query all data points with the start time or end time being in the specified time range.
// The sum value of the queried data points will be returned.
int endTime = 20200827;
int startTime = 20200818;

Task<SampleSet> daliySummationTask =dataController.readDailySummation(DataType.DT_CONTINUOUS_STEPS_DELTA, startTime, endTime);

//Calling the data controller to query the summary data of the current day is an asynchronous operation.
// Therefore, a listener needs to be registered to monitor whether the data query is successful or not.

daliySummationTask.addOnSuccessListener(new OnSuccessListener<SampleSet>() {
@Override
public void onSuccess(SampleSet sampleSet) {
logger("Success read daily summation from HMS core");
if (sampleSet != null) {
showSampleSet(sampleSet);
}
logger(SPLIT);
}
});
daliySummationTask.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
logger("readTodaySummation" + e.toString());
}
});

}

Clearing the User’s Fitness and Health Data from the Device and Cloud

Call the clearAll method of the DataController to delete data inserted by the current app from the device and cloud

/**
* Clear all user data from the device and cloud.
*
* @param view (indicating a UI object)
*/
public void clearCloudData(View view) {
// 1. Call the clearAll method of the data controller to delete data
// inserted by the current app from the device and cloud.
Task<Void> clearTask = dataController.clearAll();

// 2. Calling the data controller to clear user data from the device and cloud is an asynchronous operation.
// Therefore, a listener needs to be registered to monitor whether the clearance is successful or not.
clearTask.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void result) {
logger("clearAll success");
logger(SPLIT);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
printFailureMessage(e, "clearAll");
}
});
}

We successfully integrated Huawei Health Kit’s Data Controller feature into our project. Here’s the result.

[IMG="3"]https://miro.medium.com/max/509/1*whazMgFYRMEbrqBawiuKrw.jpeg[/IMG]

Resources:

https://developer.huawei.com/consumer/en/doc/datacontroller-develop-0000001050071677-V5

Related Links

Original post: https://medium.com/huawei-developers/health-kit-data-controller-sample-a9d29b3ba651