The Recording API on mobile allows your app to record steps, similar to a pedometer retrieving step count data, from a mobile device in a battery-efficient way. This API is accountless, meaning it does not require a Google Account to use the service and data is stored on-device.
This guide shows you how to use the Recording API on mobile in your health & fitness experiences.
See the Recording API on mobile sample on GitHub for an example.
Notable details
There are several notable features unique to the Recording API on mobile:
- Once the recording subscription starts or is renewed, data since the latest subscription - for up to 10 days - is accessible.
- Data is only available when there is an active subscription. If a subscription
is removed by calling
unsubscribe
, collected step data won't be accessible.
Data types
The Recording API on mobile can record the following data types:
Get Started
To get started, add the following dependency in your build.gradle
file:
Kotlin DSL
plugin {
id("com.android.application")
}
...
dependencies {
implementation("com.google.android.gms:play-services-fitness:21.2.0")
}
Groovy DSL
apply plugin: 'com.android.application'
...
dependencies {
implementation 'com.google.android.gms:play-services-fitness:21.2.0'
}
Request permissions
To record data using the Recording API on mobile, your app will need to request the following permission:
android.permission.ACTIVITY_RECOGNITION
Perform a Play Services version check
To use the Recording API on mobile, the user must have Google Play services
updated to LOCAL_RECORDING_CLIENT_MIN_VERSION_CODE
. You can check for this
using the isGooglePlayServicesAvailable
method:
val hasMinPlayServices = isGooglePlayServicesAvailable(context, LocalRecordingClient.LOCAL_RECORDING_CLIENT_MIN_VERSION_CODE)
if(hasMinPlayServices != ConnectionResult.SUCCESS) {
// Prompt user to update their device's Google Play services app and return
}
// Continue with Recording API functions
Otherwise, if the user's Google Play services version is too low, the system
throws a ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED
exception.
Subscribe to Fitness Data
To request background collection of steps data, use the
subscribe
method, as shown in the following code snippet:
val localRecordingClient = FitnessLocal.getLocalRecordingClient(this)
// Subscribe to steps data
localRecordingClient.subscribe(LocalDataType.TYPE_STEP_COUNT_DELTA)
.addOnSuccessListener {
Log.i(TAG, "Successfully subscribed!")
}
.addOnFailureListener { e ->
Log.w(TAG, "There was a problem subscribing.", e)
}
Read and Process Fitness Data
Once subscribed, request the data using the readData
method. Then, you can
obtain LocalDataPoints from the resulting LocalDataSet
by
making a LocalDataReadRequest
, as shown in the following code
snippet:
val endTime = LocalDateTime.now().atZone(ZoneId.systemDefault())
val startTime = endTime.minusWeeks(1)
val readRequest =
LocalDataReadRequest.Builder()
// The data request can specify multiple data types to return,
// effectively combining multiple data queries into one call.
// This example demonstrates aggregating only one data type.
.aggregate(LocalDataType.TYPE_STEP_COUNT_DELTA)
// Analogous to a "Group By" in SQL, defines how data should be
// aggregated. bucketByTime allows bucketing by time span.
.bucketByTime(1, TimeUnit.DAYS)
.setTimeRange(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS)
.build()
localRecordingClient.readData(readRequest).addOnSuccessListener { response ->
// The aggregate query puts datasets into buckets, so flatten into a
// single list of datasets.
for (dataSet in response.buckets.flatMap { it.dataSets }) {
dumpDataSet(dataSet)
}
}
.addOnFailureListener { e ->
Log.w(TAG,"There was an error reading data", e)
}
fun dumpDataSet(dataSet: LocalDataSet) {
Log.i(TAG, "Data returned for Data type: ${dataSet.dataType.name}")
for (dp in dataSet.dataPoints) {
Log.i(TAG,"Data point:")
Log.i(TAG,"\tType: ${dp.dataType.name}")
Log.i(TAG,"\tStart: ${dp.getStartTime(TimeUnit.HOURS)}")
Log.i(TAG,"\tEnd: ${dp.getEndTime(TimeUnit.HOURS)}")
for (field in dp.dataType.fields) {
Log.i(TAG,"\tLocalField: ${field.name.toString()} LocalValue: ${dp.getValue(field)}")
}
}
}
The LocalRecordingClient
continuously updates its collection of data. You can
use readData
to pull the latest numbers at any time.
Note that the LocalRecordingClient
stores up to 10 days of data. To reduce the
risk of losing data, you can use WorkManager to periodically collect the data in
the background.
Unsubscribe from fitness data
In order to free up resources, you should make sure to unsubscribe from the
collection of sensor data when your app is no longer in need of it. To
unsubscribe, use the unsubscribe
method:
val localRecordingClient = FitnessLocal.getLocalRecordingClient(this)
// Unsubscribe from steps data
localRecordingClient.unsubscribe(LocalDataType.TYPE_STEP_COUNT_DELTA)
.addOnSuccessListener {
Log.i(TAG, "Successfully unsubscribed!")
}
.addOnFailureListener { e ->
Log.w(TAG, "There was a problem unsubscribing.", e)
}