We're making some changes to the Google Fit APIs. Learn about how these changes might affect your app. Read our new policy.

Work with historical data

The History API enables your app to perform bulk operations on the fitness store: reading, inserting, updating, and deleting historical health and wellness data. Use the History API to:

  • Read health and wellness data that was inserted or recorded using other apps.
  • Import batch data into Google Fit.
  • Update data in Google Fit.
  • Delete historical data that your app previously stored.

To insert data with session metadata, you can use the Sessions API.

Read data

Read detailed and aggregate data

To read historical data:

  1. Create a DataReadRequest instance.
// Setting a start and end date using a range of 1 week before this moment.
val endTime = LocalDateTime.now().atZone(ZoneId.systemDefault())
val startTime = endTime.minusWeeks(1)
Log.i(TAG, "Range Start: $startTime")
Log.i(TAG, "Range End: $endTime")

val readRequest =
    DataReadRequest.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(DataType.AGGREGATE_STEP_COUNT_DELTA)
        // Analogous to a "Group By" in SQL, defines how data should be aggregated.
        // bucketByTime allows for a time span, whereas bucketBySession would allow
        // bucketing by "sessions".
        .bucketByTime(1, TimeUnit.DAYS)
        .setTimeRange(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS)
        .build()

The data request can specify multiple data types to return, effectively combining multiple data queries into one call. This example only specifies the TYPE_STEP_COUNT_DELTA data type. The data request can also indicate whether to return time-series data points or aggregated data points. This example uses aggregated data points where each DataPoint represents the number of steps walked in a day. For this particular use case, aggregated data points have two advantages:

  • Your app and the fitness store exchange smaller amounts of data.
  • Your app does not have to aggregate the data manually.

Your app can use data requests to retrieve lots of different types of data. The following example shows how to create a DataReadRequest to get calories burned for each activity performed within the specified time range. The resulting data matches the calories per activity as reported in the Google Fit app, with each activity getting its own bucket of calorie data.

val readRequest = DataReadRequest.Builder()
    .aggregate(DataType.AGGREGATE_CALORIES_EXPENDED)
    .bucketByActivityType(1, TimeUnit.SECONDS)
    .setTimeRange(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS)
    .build()

After you create a DataReadRequest instance, use the HistoryClient.readData() method to asynchronously read historical data.

Fitness.getHistoryClient(this, GoogleSignIn.getAccountForExtension(this, fitnessOptions))
    .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 from Google Fit", e)
    }

The following example demonstrates how to obtain the DataPoint instances from a DataSet:

fun dumpDataSet(dataSet: DataSet) {
    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.getStartTimeString()}")
        Log.i(TAG,"\tEnd: ${dp.getEndTimeString()}")
        for (field in dp.dataType.fields) {
            Log.i(TAG,"\tField: ${field.name.toString()} Value: ${dp.getValue(field)}")
        }
    }
}

fun DataPoint.getStartTimeString() = Instant.ofEpochSecond(this.getStartTime(TimeUnit.SECONDS))
    .atZone(ZoneId.systemDefault())
    .toLocalDateTime().toString()

fun DataPoint.getEndTimeString() = Instant.ofEpochSecond(this.getEndTime(TimeUnit.SECONDS))
    .atZone(ZoneId.systemDefault())
    .toLocalDateTime().toString()

Read daily total data

Google Fit also provides simple access to the daily total of a specified data type. Use the HistoryClient.readDailyTotal() method to retrieve the data type that you specify as of midnight of the current day in the device's current timezone. For example, pass in the TYPE_STEP_COUNT_DELTA data type to this method to retrieve the daily total steps. You may pass in an instantaneous data type that has an aggregate daily total. For more information on the supported data types, see DataType.getAggregatesForInput().

Google Fit does not require authorization to subscribe to TYPE_STEP_COUNT_DELTA updates from the HistoryClient.readDailyTotal() method when this method is called using the default account and no scopes are specified. This can be useful if you require step data for use in areas where you are unable to show the permissions panel (for example, Wear OS watch faces).

Users prefer to see consistent step counts across the Google Fit app, other apps, and Wear OS watch faces, as this provides them with a consistent and reliable experience. To keep step counts consistent, subscribe to steps in the Google Fit platform from your app or watch face, and then call this method every 30 seconds in interactive mode, and every 60 seconds in ambient mode. For more information on how to use this data in a watch face, see Showing Information in Watch Faces and the Android Watch Face sample application.

Insert data

To insert historical data, first create a DataSet instance:

// Set a start and end time for our data, using a start time of 1 hour before this moment.
val endTime = LocalDateTime.now().atZone(ZoneId.systemDefault())
val startTime = endTime.minusHours(1)

// Create a data source
val dataSource = DataSource.Builder()
    .setAppPackageName(this)
    .setDataType(DataType.TYPE_STEP_COUNT_DELTA)
    .setStreamName("$TAG - step count")
    .setType(DataSource.TYPE_RAW)
    .build()

// For each data point, specify a start time, end time, and the data value
// -- in this case, the number of new steps.
val stepCountDelta = 950
val dataPoint =
    DataPoint.builder(dataSource)
        .setField(Field.FIELD_STEPS, stepCountDelta)
        .setTimeInterval(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS)
        .build()

val dataSet = DataSet.builder(dataSource)
    .add(dataPoint)
    .build()

After you create a DataSet instance, use the HistoryClient.insertData method to asynchronously add this historical data.

Fitness.getHistoryClient(this, GoogleSignIn.getAccountForExtension(this, fitnessOptions))
    .insertData(dataSet)
    .addOnSuccessListener {
        Log.i(TAG, "DataSet added successfully!")
    }
    .addOnFailureListener { e ->
        Log.w(TAG, "There was an error adding the DataSet", e)
    }

Manage conflicting data points

Each DataPoint in your app's DataSet must have a startTime and an endTime that defines a unique interval within that DataSet, with no overlap between DataPoint instances.

If your app attempts to insert a new DataPoint that conflicts with an existing DataPoint instance, the new DataPoint is discarded. To insert a new DataPoint that may overlap existing data points, use the HistoryClient.updateData method described in Update data.

Update data

Google Fit lets your app update historical health and wellness data it previously inserted. To add historical data for a new DataSet, or to add new DataPoint instances that do not conflict with existing data points as described in Manage conflicting data points, your app should use the HistoryApi.insertData method.

To update historical data, use the HistoryClient.updateData method. This method deletes any existing DataPoint instances that overlap with DataPoint instances added using this method.

To update historical health and wellness data, first create a DataSet instance:

// Set a start and end time for the data that fits within the time range
// of the original insertion.
val endTime = LocalDateTime.now().atZone(ZoneId.systemDefault())
val startTime = endTime.minusMinutes(50)

// Create a data source
val dataSource  = DataSource.Builder()
    .setAppPackageName(this)
    .setDataType(DataType.TYPE_STEP_COUNT_DELTA)
    .setStreamName("$TAG - step count")
    .setType(DataSource.TYPE_RAW)
    .build()

// Create a data set
val stepCountDelta = 1000
// For each data point, specify a start time, end time, and the data value -- in this case,
// the number of new steps.
val dataPoint = DataPoint.builder(dataSource)
    .setTimeInterval(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS)
    .setField(Field.FIELD_STEPS, stepCountDelta)
    .build()

val dataSet = DataSet.builder(dataSource)
    .add(dataPoint)
    .build()

Then, use DataUpdateRequest.Builder() to create a new data update request, and use the HistoryClient.updateData method to make the request.

val request = DataUpdateRequest.Builder()
    .setDataSet(dataSet)
    .setTimeInterval(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS)
    .build()

Fitness.getHistoryClient(this, GoogleSignIn.getAccountForExtension(this, fitnessOptions))
    .updateData(request)
    .addOnSuccessListener {
        Log.i(TAG, "DataSet updated successfully!")
    }
    .addOnFailureListener { e ->
        Log.w(TAG, "There was an error updating the DataSet", e)
    }

Delete data

Google Fit lets your app delete historical health and wellness data it previously inserted.

To delete historical data, use the HistoryClient.deleteData method:

// Set a start and end time for our data, using a start time of 1 day before this moment.
val endTime = LocalDateTime.now().atZone(ZoneId.systemDefault())
val startTime = endTime.minusDays(1)

// Create a delete request object, providing a data type and a time interval
val request = DataDeleteRequest.Builder()
    .setTimeInterval(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS)
    .addDataType(DataType.TYPE_STEP_COUNT_DELTA)
    .build()

// Invoke the History API with the HistoryClient object and delete request, and then
// specify a callback that will check the result.
Fitness.getHistoryClient(this, GoogleSignIn.getAccountForExtension(this, fitnessOptions))
    .deleteData(request)
    .addOnSuccessListener {
        Log.i(TAG, "Data deleted successfully!")
    }
    .addOnFailureListener { e ->
        Log.w(TAG, "There was an error with the deletion request", e)
    }

Apps can provide specific sessions or delete all data. For more information, see the API reference for DataDeleteRequest.

Register for data updates

For data that comes from sensors, registering with SensorsClient lets your app read raw sensor data in real time.

For other types of data that are less frequent and are manually counted, like height, weight and workouts like weight lifting (see the full list here), your app can register to receive updates when these measurements are inserted into the Google Fit database using HistoryClient.registerDataUpdateListener.

val intent = Intent(this, MyDataUpdateService::class.java)
val pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)

val request = DataUpdateListenerRegistrationRequest.Builder()
    .setDataType(DataType.TYPE_WEIGHT)
    .setPendingIntent(pendingIntent)
    .build()

Fitness.getHistoryClient(this, GoogleSignIn.getAccountForExtension(this, fitnessOptions))
    .registerDataUpdateListener(request)
    .addOnSuccessListener {
        Log.i(TAG, "DataUpdateListener registered")
    }

An IntentService can be used to receive notifications of updates:

class MyDataUpdateService : IntentService("MyDataUpdateService") {
    override fun onHandleIntent(intent: Intent?) {
        val update = DataUpdateNotification.getDataUpdateNotification(intent)
        update?.apply {
            val start = getUpdateStartTime(TimeUnit.MILLISECONDS)
            val end = getUpdateEndTime(TimeUnit.MILLISECONDS)

            Log.i(TAG, "Data Update start: $start end: $end DataType: ${dataType.name}")
        }
    }
}

The IntentService must be declared in your AndroidManifest.xml file.