Keep Track of Workouts While Running in the Background
Jackson
Posted on June 29, 2022
It can be so frustrating to lose track of a workout because the fitness app has stopped running in the background, when you turn off the screen or have another app in the front to listen to music or watch a video during the workout. Talk about all of your sweat and effort going to waste!
Fitness apps work by recognizing and displaying the user's workout status in real time, using the sensor on the phone or wearable device. They can obtain and display complete workout records to users only if they can keep running in the background. Since most users will turn off the screen, or use other apps during a workout, it has been a must-have feature for fitness apps to keep alive in the background. However, to save the battery power, most phones will restrict or even forcibly close apps once they are running in the background, causing the workout data to be incomplete. When building your own fitness app, it's important to keep this limitation in mind.
There are two tried and tested ways to keep fitness apps running in the background:
1) Instruct the user to manually configure the settings on their phones or wearable devices, for example, to disable battery optimization, or to allow the specific app to run in the background. However, this process can be cumbersome, and not easy to follow.
2) Integrate development tools into your app, for example, Health Kit, which provides APIs that allow your app to keep running in the background during workouts, without losing track of any workout data.
The following details the process for integrating this kit.
Integration Procedure
1) Before you get started, apply for Health Kit on HUAWEI Developers, select the required data scopes, and integrate the Health SDK.
2) Obtain users' authorization, and apply for the scopes to read and write workout records.
3) Enable a foreground service to prevent your app from being frozen by the system, and call ActivityRecordsController in the foreground service to create a workout record that can run in the background.
4) Call beginActivityRecord of ActivityRecordsController to start the workout record. By default, an app will be allowed to run in the background for 10 minutes.
// Note that this refers to an Activity object.
ActivityRecordsController activityRecordsController = HuaweiHiHealth.getActivityRecordsController(this);
// 1. Build the start time of a new workout record.
long startTime = Calendar.getInstance().getTimeInMillis();
// 2. Build the ActivityRecord object and set the start time of the workout record.
ActivityRecord activityRecord = new ActivityRecord.Builder()
.setId("MyBeginActivityRecordId")
.setName("BeginActivityRecord")
.setDesc("This is ActivityRecord begin test!")
.setActivityTypeId(HiHealthActivities.RUNNING)
.setStartTime(startTime, TimeUnit.MILLISECONDS)
.build();
// 3. Construct the screen to be displayed when the workout record is running in the background. Note that you need to replace MyActivity with the Activity class of the screen.
ComponentName componentName = new ComponentName(this, MyActivity.class);
// 4. Construct a listener for the status change of the workout record.
OnActivityRecordListener activityRecordListener = new OnActivityRecordListener() {
@Override
public void onStatusChange(int statusCode) {
Log.i("ActivityRecords", "onStatusChange statusCode:" + statusCode);
}
};
// 5. Call beginActivityRecord to start the workout record.
Task<Void> task1 = activityRecordsController.beginActivityRecord(activityRecord, componentName, activityRecordListener);
// 6. ActivityRecord is successfully started.
task1.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
Log.i("ActivityRecords", "MyActivityRecord begin success");
}
// 7. ActivityRecord fails to be started.
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
String errorCode = e.getMessage();
String errorMsg = HiHealthStatusCodes.getStatusCodeMessage(Integer.parseInt(errorCode));
Log.i("ActivityRecords", errorCode + ": " + errorMsg);
}
});
5) If the workout lasts for more than 10 minutes, call continueActivityRecord of ActivityRecordsController each time before a 10-minute ends to apply for the workout to continue for another 10 minutes.
// Note that this refers to an Activity object.
ActivityRecordsController activityRecordsController = HuaweiHiHealth.getActivityRecordsController(this);
// Call continueActivityRecord and pass the workout record ID for the record to continue in the background.
Task<Void> endTask = activityRecordsController.continueActivityRecord("MyBeginActivityRecordId");
endTask.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
Log.i("ActivityRecords", "continue backgroundActivityRecord was successful!");
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.i("ActivityRecords", "continue backgroundActivityRecord error");
}
});
6) When the user finishes the workout, call endActivityRecord of ActivityRecordsController to stop the record and stop keeping it alive in the background.
// Note that this refers to an Activity object.
final ActivityRecordsController activityRecordsController = HuaweiHiHealth.getActivityRecordsController(this);
// Call endActivityRecord to stop the workout record. The input parameter is null or the ID string of ActivityRecord.
// Stop a workout record of the current app by specifying the ID string as the input parameter.
// Stop all workout records of the current app by specifying null as the input parameter.
Task<List<ActivityRecord>> endTask = activityRecordsController.endActivityRecord("MyBeginActivityRecordId");
endTask.addOnSuccessListener(new OnSuccessListener<List<ActivityRecord>>() {
@Override
public void onSuccess(List<ActivityRecord> activityRecords) {
Log.i("ActivityRecords","MyActivityRecord End success");
// Return the list of workout records that have stopped.
if (activityRecords.size() > 0) {
for (ActivityRecord activityRecord : activityRecords) {
DateFormat dateFormat = DateFormat.getDateInstance();
DateFormat timeFormat = DateFormat.getTimeInstance();
Log.i("ActivityRecords", "Returned for ActivityRecord: " + activityRecord.getName() + "\n\tActivityRecord Identifier is "
+ activityRecord.getId() + "\n\tActivityRecord created by app is " + activityRecord.getPackageName()
+ "\n\tDescription: " + activityRecord.getDesc() + "\n\tStart: "
+ dateFormat.format(activityRecord.getStartTime(TimeUnit.MILLISECONDS)) + " "
+ timeFormat.format(activityRecord.getStartTime(TimeUnit.MILLISECONDS)) + "\n\tEnd: "
+ dateFormat.format(activityRecord.getEndTime(TimeUnit.MILLISECONDS)) + " "
+ timeFormat.format(activityRecord.getEndTime(TimeUnit.MILLISECONDS)) + "\n\tActivity:"
+ activityRecord.getActivityType());
}
} else {
// null will be returned if the workout record hasn't stopped.
Log.i("ActivityRecords","MyActivityRecord End response is null");
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
String errorCode = e.getMessage();
String errorMsg = HiHealthStatusCodes.getStatusCodeMessage(Integer.parseInt(errorCode));
Log.i("ActivityRecords",errorCode + ": " + errorMsg);
}
});
Note that calling the API for keeping your app running in the background is a sensitive operation and requires manual approval. Make sure that your app meets the data security and compliance requirements before applying for releasing it.
Conclusion
Health Kit allows you to build apps that continue tracking workouts in the background, even when the screen has been turned off, or another app has been opened to run in the front. It's a must-have for fitness app developers. Integrate the kit to get started today!
References
HUAWEI Developers
Development Procedure for Keeping Your App Running in the Background
Posted on June 29, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.