Recording API on mobile

The Recording API on mobile allows your app to record steps 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.

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, distance, or calories 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)
  }