The Google Fit APIs, including the Google Fit REST API, will be deprecated in 2026. As of May 1, 2024, developers cannot sign up to use these APIs.
For instructions on which API or platform to migrate to, visit the Health Connect migration guide. For a comparison of Health Connect with the Google Fit APIs and the Fitbit Web APIs, visit the Health Connect comparison guide.
Learn more about Health Connect and how to integrate with the API.
Work with historical data
Stay organized with collections
Save and categorize content based on your preferences.
Page Summary
-
The History API facilitates bulk operations like reading, inserting, updating, and deleting historical health and wellness data in the fitness store.
-
You can use the History API to read data from other apps, import batch data, update existing data, and delete data your app previously stored.
-
To read historical data, create a DataReadRequest and use the HistoryClient.readData() method.
-
Data can be inserted by creating a DataSet and using the HistoryClient.insertData method, with a warning to limit each request to 1,000 datapoints.
-
Data can be updated using the HistoryClient.updateData method, which replaces existing data points that overlap with the new data.
-
Historical data inserted by your app can be deleted using the HistoryClient.deleteData method.
-
Apps can register to receive updates when certain data types, like weight, are inserted into the Google Fit database using HistoryClient.registerDataUpdateListener.
The History API lets your app perform bulk operations on the fitness store: reading, inserting, updating, and deleting historical health and wellness data. Use the History API to do the following:
- 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 that contains session metadata, use the Sessions API.
Read data
The following sections cover how to read different kinds of aggregate data.
Read detailed and aggregate data
To read historical data, create a
DataReadRequest
instance.
Kotlin
// Read the data that's been collected throughout the past week. valendTime=LocalDateTime.now().atZone(ZoneId.systemDefault()) valstartTime=endTime.minusWeeks(1) Log.i(TAG,"Range Start: $startTime") Log.i(TAG,"Range End: $endTime") valreadRequest= 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 allows // bucketing by <a href="/fit/android/using-sessions">sessions</a>. .bucketByTime(1,TimeUnit.DAYS) .setTimeRange(startTime.toEpochSecond(),endTime.toEpochSecond(),TimeUnit.SECONDS) .build()
Java
// Read the data that's been collected throughout the past week. ZonedDateTimeendTime=LocalDateTime.now().atZone(ZoneId.systemDefault()); ZonedDateTimestartTime=endTime.minusWeeks(1); Log.i(TAG,"Range Start: $startTime"); Log.i(TAG,"Range End: $endTime"); DataReadRequestreadRequest=newDataReadRequest.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, while bucketBySession allows // bucketing by sessions. .bucketByTime(1,TimeUnit.DAYS) .setTimeRange(startTime.toEpochSecond(),endTime.toEpochSecond(),TimeUnit.SECONDS) .build();
The previous 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 doesn't have to aggregate the data manually.
Aggregate data for multiple activity types
Your app can use data requests to retrieve many 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, where each activity gets its own bucket
of calorie data.
Kotlin
valreadRequest=DataReadRequest.Builder() .aggregate(DataType.AGGREGATE_CALORIES_EXPENDED) .bucketByActivityType(1,TimeUnit.SECONDS) .setTimeRange(startTime.toEpochSecond(),endTime.toEpochSecond(),TimeUnit.SECONDS) .build()
Java
DataReadRequestreadRequest=newDataReadRequest.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.
The following example demonstrates how to obtain the DataPoint instances from
a DataSet:
Kotlin
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(dataSetinresponse.buckets.flatMap{it.dataSets}){ dumpDataSet(dataSet) } } .addOnFailureListener{e-> Log.w(TAG,"There was an error reading data from Google Fit",e) } fundumpDataSet(dataSet:DataSet){ Log.i(TAG,"Data returned for Data type: ${dataSet.dataType.name}") for(dpindataSet.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(fieldindp.dataType.fields){ Log.i(TAG,"\tField: ${field.name.toString()} Value: ${dp.getValue(field)}") } } } funDataPoint.getStartTimeString()=Instant.ofEpochSecond(this.getStartTime(TimeUnit.SECONDS)) .atZone(ZoneId.systemDefault()) .toLocalDateTime().toString() funDataPoint.getEndTimeString()=Instant.ofEpochSecond(this.getEndTime(TimeUnit.SECONDS)) .atZone(ZoneId.systemDefault()) .toLocalDateTime().toString()
Java
Fitness.getHistoryClient(this,GoogleSignIn.getAccountForExtension(this,fitnessOptions)) .readData(readRequest) .addOnSuccessListener(response->{ // The aggregate query puts datasets into buckets, so convert to a // single list of datasets for(Bucketbucket:response.getBuckets()){ for(DataSetdataSet:bucket.getDataSets()){ dumpDataSet(dataSet); } } }) .addOnFailureListener(e-> Log.w(TAG,"There was an error reading data from Google Fit",e)); } privatevoiddumpDataSet(DataSetdataSet){ Log.i(TAG,"Data returned for Data type: ${dataSet.dataType.name}"); for(DataPointdp:dataSet.getDataPoints()){ 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(Fieldfield:dp.getDataType().getFields()){ Log.i(TAG,"\tField: ${field.name.toString()} Value: ${dp.getValue(field)}"); } } } privateStringgetStartTimeString(){ returnInstant.ofEpochSecond(this.getStartTime(TimeUnit.SECONDS)) .atZone(ZoneId.systemDefault()) .toLocalDateTime().toString(); } privateStringgetEndTimeString(){ returnInstant.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 can pass in an instantaneous data type that has an aggregate daily
total. For more information on the supported data types, see
DataType.getAggregateType.
Google Fit doesn't 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're unable
to show the permissions panel, for example on Wear OS watch faces.
Users prefer to see consistent step counts across the Google Fit app,
other apps, and Wear OS watch faces, because 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
update the count in
onExitAmbient().
For more information on how to use this data in a watch face, see
Watch face complications
and the Android WatchFace sample application.
Insert data
To insert historical data, first create a DataSetinstance:
Kotlin
// Declare that the data being inserted was collected during the past hour. valendTime=LocalDateTime.now().atZone(ZoneId.systemDefault()) valstartTime=endTime.minusHours(1) // Create a data source valdataSource=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, 950 new steps. valstepCountDelta=950 valdataPoint= DataPoint.builder(dataSource) .setField(Field.FIELD_STEPS,stepCountDelta) .setTimeInterval(startTime.toEpochSecond(),endTime.toEpochSecond(),TimeUnit.SECONDS) .build() valdataSet=DataSet.builder(dataSource) .add(dataPoint) .build()
Java
// Declare that the data being inserted was collected during the past hour. ZonedDateTimeendTime=LocalDateTime.now().atZone(ZoneId.systemDefault()); ZonedDateTimestartTime=endTime.minusHours(1); // Create a data source DataSourcedataSource=newDataSource.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, 950 new steps. intstepCountDelta=950; DataPointdataPoint=DataPoint.builder(dataSource) .setField(Field.FIELD_STEPS,stepCountDelta) .setTimeInterval(startTime.toEpochSecond(),endTime.toEpochSecond(),TimeUnit.SECONDS) .build(); DataSetdataSet=DataSet.builder(dataSource) .add(dataPoint) .build();
After you create a DataSet instance, use the
HistoryClient.insertData
method to asynchronously add this historical data.
Kotlin
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) }
Java
Fitness.getHistoryClient(this,GoogleSignIn.getAccountForExtension(this,fitnessOptions)) .insertData(dataSet) .addOnSuccessListener(unused-> 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 might overlap with existing data points, use the
HistoryClient.updateData
method described in Update data.
A data point can't be inserted if its duration overlaps with any existing data points
Figure 1. How the insertData() method handles new data points that
conflict with an existing DataPoint.
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 don't conflict with existing data
points, 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:
Kotlin
// Declare that the historical data was collected during the past 50 minutes. valendTime=LocalDateTime.now().atZone(ZoneId.systemDefault()) valstartTime=endTime.minusMinutes(50) // Create a data source valdataSource=DataSource.Builder() .setAppPackageName(this) .setDataType(DataType.TYPE_STEP_COUNT_DELTA) .setStreamName("$TAG - step count") .setType(DataSource.TYPE_RAW) .build() // Create a data set // For each data point, specify a start time, end time, and the // data value -- in this case, 1000 new steps. valstepCountDelta=1000 valdataPoint=DataPoint.builder(dataSource) .setTimeInterval(startTime.toEpochSecond(),endTime.toEpochSecond(),TimeUnit.SECONDS) .setField(Field.FIELD_STEPS,stepCountDelta) .build() valdataSet=DataSet.builder(dataSource) .add(dataPoint) .build()
Java
// Declare that the historical data was collected during the past 50 minutes. ZonedDateTimeendTime=LocalDateTime.now().atZone(ZoneId.systemDefault()); ZonedDateTimestartTime=endTime.minusMinutes(50); // Create a data source DataSourcedataSource=newDataSource.Builder() .setAppPackageName(this) .setDataType(DataType.TYPE_STEP_COUNT_DELTA) .setStreamName("$TAG - step count") .setType(DataSource.TYPE_RAW) .build(); // Create a data set // For each data point, specify a start time, end time, and the // data value -- in this case, 1000 new steps. intstepCountDelta=1000; DataPointdataPoint=DataPoint.builder(dataSource) .setTimeInterval(startTime.toEpochSecond(),endTime.toEpochSecond(),TimeUnit.SECONDS) .setField(Field.FIELD_STEPS,stepCountDelta) .build(); DataSetdataSet=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:
Kotlin
valrequest=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) }
Java
DataUpdateRequestrequest=newDataUpdateRequest.Builder() .setDataSet(dataSet) .setTimeInterval(startTime.toEpochSecond(),endTime.toEpochSecond(),TimeUnit.SECONDS) .build(); Fitness.getHistoryClient(this,GoogleSignIn.getAccountForExtension(this,fitnessOptions)) .updateData(request) .addOnSuccessListener(unused-> 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:
Kotlin
// Declare that this code deletes step count information that was collected // throughout the past day. valendTime=LocalDateTime.now().atZone(ZoneId.systemDefault()) valstartTime=endTime.minusDays(1) // Create a delete request object, providing a data type and a time interval valrequest=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) }
Java
// Declare that this code deletes step count information that was collected // throughout the past day. ZonedDateTimeendTime=LocalDateTime.now().atZone(ZoneId.systemDefault()); ZonedDateTimestartTime=endTime.minusDays(1); // Create a delete request object, providing a data type and a time interval DataDeleteRequestrequest=newDataDeleteRequest.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(unused-> Log.i(TAG,"Data deleted successfully!")) .addOnFailureListener(e-> Log.w(TAG,"There was an error with the deletion request",e));
Apps can delete data from specific sessions or
delete all data. For more information, see the API reference for
DataDeleteRequest.
Register for data updates
Your app can read raw sensor data in real time by registering with
SensorsClient.
For other types of data that are less frequent and are manually counted, your
app can register to receive updates when these measurements are inserted into
the Google Fit database. Examples of these data types include height,
weight, and workouts like weight lifting; for more details, see the full list
of supported data types.
To register for updates, use
HistoryClient.registerDataUpdateListener.
The following code snippet lets an app be notified when the user enters a new value for their weight:
Kotlin
valintent=Intent(this,MyDataUpdateService::class.java) valpendingIntent=PendingIntent.getService(this,0,intent,PendingIntent.FLAG_UPDATE_CURRENT) valrequest=DataUpdateListenerRegistrationRequest.Builder() .setDataType(DataType.TYPE_WEIGHT) .setPendingIntent(pendingIntent) .build() Fitness.getHistoryClient(this,GoogleSignIn.getAccountForExtension(this,fitnessOptions)) .registerDataUpdateListener(request) .addOnSuccessListener{ Log.i(TAG,"DataUpdateListener registered") }
Java
Intentintent=newIntent(this,MyDataUpdateService.class); PendingIntentpendingIntent=PendingIntent.getService(this,0,intent,PendingIntent.FLAG_UPDATE_CURRENT) DataUpdateListenerRegistrationRequestrequest=newDataUpdateListenerRegistrationRequest.Builder() .setDataType(DataType.TYPE_WEIGHT) .setPendingIntent(pendingIntent) .build(); Fitness.getHistoryClient(this,GoogleSignIn.getAccountForExtension(this,fitnessOptions)) .registerDataUpdateListener(request) .addOnSuccessListener(unused-> Log.i(TAG,"DataUpdateListener registered"));
An IntentService can be used to receive notifications of updates:
Kotlin
classMyDataUpdateService:IntentService("MyDataUpdateService"){ overridefunonHandleIntent(intent:Intent?){ valupdate=DataUpdateNotification.getDataUpdateNotification(intent) // Show the time interval over which the data points were collected. // To extract specific data values, in this case the user's weight, // use DataReadRequest. update?.apply{ valstart=getUpdateStartTime(TimeUnit.MILLISECONDS) valend=getUpdateEndTime(TimeUnit.MILLISECONDS) Log.i(TAG,"Data Update start: $start end: $end DataType: ${dataType.name}") } } }
Java
publicclass MyDataUpdateServiceextendsIntentService{ publicMyDataUpdateService(Stringname){ super("MyDataUpdateService"); } @Override protectedvoidonHandleIntent(@NullableIntentintent){ if(intent!=null){ DataUpdateNotificationupdate=DataUpdateNotification.getDataUpdateNotification(intent); // Show the time interval over which the data points // were collected. // To extract specific data values, in this case the user's weight, // use DataReadRequest. if(update!=null){ longstart=update.getUpdateStartTime(TimeUnit.MILLISECONDS); longend=update.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.