# Work with historical data

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](https://developers.google.com/fit/android/using-sessions).

> [!NOTE]
> **Note:** For best practices when you manage user data, see [Google Fit Developer and User Data Policy](https://developers.google.com/fit/policy).

## 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`](https://developers.google.com/android/reference/com/google/android/gms/fitness/request/DataReadRequest)
instance.

> [!NOTE]
> **Note:** Apps that run on Android 10 (API level 29) or higher must [get an Android permission](https://developer.android.com/about/versions/10/privacy/changes#physical-activity-recognition) when they record certain physical activity data types.

### Kotlin

```kotlin
// Read the data that's been collected throughout the past week.
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 allows
        // bucketing by <a href="/fit/android/using-sessions">sessions</a>.
        .bucketByTime(1, TimeUnit.DAYS)
        .setTimeRange(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS)
        .build()
```

### Java

```java
// Read the data that's been collected throughout the past week.
ZonedDateTime endTime = LocalDateTime.now().atZone(ZoneId.systemDefault());
ZonedDateTime startTime = endTime.minusWeeks(1);
Log.i(TAG, "Range Start: $startTime");
Log.i(TAG, "Range End: $endTime");

DataReadRequest readRequest = new 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, 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

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

### Java

```java
DataReadRequest readRequest = new 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()`](https://developers.google.com/android/reference/com/google/android/gms/fitness/HistoryClient#public-taskdatareadresponse-readdata-datareadrequest-request)
method to asynchronously read historical data.

The following example demonstrates how to obtain the `DataPoint` instances from
a [`DataSet`](https://developers.google.com/android/reference/com/google/android/gms/fitness/data/DataSet):

### Kotlin

```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 (dataSet in response.buckets.flatMap { it.dataSets }) {
            dumpDataSet(dataSet)
        }
    }
    .addOnFailureListener { e ->
        Log.w(TAG,"There was an error reading data from Google Fit", e)
    }

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()
```

### Java

```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 (Bucket bucket : response.getBuckets()) {
                for (DataSet dataSet : bucket.getDataSets()) {
                    dumpDataSet(dataSet);
                }
            }
        })
        .addOnFailureListener(e ->
            Log.w(TAG, "There was an error reading data from Google Fit", e));

}

private void dumpDataSet(DataSet dataSet) {
    Log.i(TAG, "Data returned for Data type: ${dataSet.dataType.name}");
    for (DataPoint dp : 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 (Field field : dp.getDataType().getFields()) {
            Log.i(TAG,"\tField: ${field.name.toString()} Value: ${dp.getValue(field)}");
        }
    }
}


private String getStartTimeString() {
    return Instant.ofEpochSecond(this.getStartTime(TimeUnit.SECONDS))
        .atZone(ZoneId.systemDefault())
        .toLocalDateTime().toString();
}

private String getEndTimeString() {
    return 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()`](https://developers.google.com/android/reference/com/google/android/gms/fitness/HistoryClient#public-taskdataset-readdailytotal-datatype-datatype)
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`](https://developers.google.com/android/reference/com/google/android/gms/fitness/data/DataType#public-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](https://developers.google.com/android/reference/com/google/android/gms/common/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()`](https://developer.android.com/reference/android/support/wearable/activity/WearableActivity.html#onExitAmbient()).
For more information on how to use this data in a watch face, see
[Watch face complications](https://developer.android.com/training/wearables/watch-faces/complications)
and the [Android WatchFace sample application](https://github.com/android/wear-os-samples/tree/main/WatchFace).

## Insert data

To insert historical data, first create a `DataSet`instance:

### Kotlin

```kotlin
// Declare that the data being inserted was collected during the past hour.
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, 950 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()
```

### Java

```java
// Declare that the data being inserted was collected during the past hour.
ZonedDateTime endTime = LocalDateTime.now().atZone(ZoneId.systemDefault());
ZonedDateTime startTime = endTime.minusHours(1);

// Create a data source
DataSource dataSource = new 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.
int stepCountDelta = 950;
DataPoint dataPoint = DataPoint.builder(dataSource)
        .setField(Field.FIELD_STEPS, stepCountDelta)
        .setTimeInterval(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS)
        .build();

DataSet dataSet = DataSet.builder(dataSource)
        .add(dataPoint)
        .build();
```

> [!WARNING]
> **Warning:** Be sure to limit each individual request to 1,000 datapoints. Exceeding this limit could result in an error.

After you create a `DataSet` instance, use the
[`HistoryClient.insertData`](https://developers.google.com/android/reference/com/google/android/gms/fitness/HistoryClient#public-taskvoid-insertdata-dataset-dataset)
method to asynchronously add this historical data.

### Kotlin

```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

```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));
}
```

> [!NOTE]
> **Note:** The `DataPoint` instances that you insert must have the same number of fields with the same formats as those defined by the data type. For example, if you want to insert past location data, each sample of the data must have four `float` components as defined by the `com.google.location.sample` data type. If you use the wrong format, an `IllegalArgumentException` occurs.

### Manage conflicting data points

Each
[`DataPoint`](https://developers.google.com/android/reference/com/google/android/gms/fitness/data/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`](https://developers.google.com/android/reference/com/google/android/gms/fitness/HistoryClient#public-taskvoid-updatedata-dataupdaterequest-request)
method described in [Update data](https://developers.google.com/fit/android/history#update-data).

![A data point can't be inserted if its duration overlaps with any existing data
points](https://developers.google.com/static/fit/images/insertData.png)

**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](https://developers.google.com/fit/android/history#manage-conflicts), use the `HistoryApi.insertData` method.

> [!NOTE]
> **Note:** An app can't update data inserted by other apps.

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

```kotlin
// Declare that the historical data was collected during the past 50 minutes.
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
// For each data point, specify a start time, end time, and the
// data value -- in this case, 1000 new steps.
val stepCountDelta = 1000

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()
```

### Java

```java
// Declare that the historical data was collected during the past 50 minutes.
ZonedDateTime endTime = LocalDateTime.now().atZone(ZoneId.systemDefault());
ZonedDateTime startTime = endTime.minusMinutes(50);

// Create a data source
DataSource dataSource = new 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.
int stepCountDelta = 1000;

DataPoint dataPoint = DataPoint.builder(dataSource)
        .setTimeInterval(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS)
        .setField(Field.FIELD_STEPS, stepCountDelta)
        .build();

DataSet 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:

### Kotlin

```kotlin
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)
    }
```

### Java

```java
DataUpdateRequest request = new DataUpdateRequest.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.

> [!NOTE]
> **Note:** An app can't delete data inserted by other apps.

To delete historical data, use the
[`HistoryClient.deleteData`](https://developers.google.com/android/reference/com/google/android/gms/fitness/HistoryClient#public-taskvoid-deletedata-datadeleterequest-request)
method:

### Kotlin

```kotlin
// Declare that this code deletes step count information that was collected
// throughout the past day.
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)
    }
```

### Java

```java
// Declare that this code deletes step count information that was collected
// throughout the past day.
ZonedDateTime endTime = LocalDateTime.now().atZone(ZoneId.systemDefault());
ZonedDateTime startTime = endTime.minusDays(1);

// Create a delete request object, providing a data type and a time interval
DataDeleteRequest request = new 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 (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](https://developers.google.com/fit/android/using-sessions) or
delete all data. For more information, see the API reference for
[`DataDeleteRequest`](https://developers.google.com/android/reference/com/google/android/gms/fitness/request/DataDeleteRequest).

## Register for data updates

Your app can read raw sensor data in real time by registering with
[`SensorsClient`](https://developers.google.com/fit/android/sensors).

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](https://developers.google.com/android/reference/com/google/android/gms/fitness/HistoryClient#public-taskvoid-registerdataupdatelistener-dataupdatelistenerregistrationrequest-request).
To register for updates, use
[`HistoryClient.registerDataUpdateListener`](https://developers.google.com/android/reference/com/google/android/gms/fitness/HistoryClient#public-taskvoid-registerdataupdatelistener-dataupdatelistenerregistrationrequest-request).

The following code snippet lets an app be notified when the user enters a new
value for their weight:

### Kotlin

```kotlin
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")
    }
```

### Java

```java
Intent intent = new Intent(this, MyDataUpdateService.class);
PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)

DataUpdateListenerRegistrationRequest request = new DataUpdateListenerRegistrationRequest.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

```kotlin
class MyDataUpdateService : IntentService("MyDataUpdateService") {
    override fun onHandleIntent(intent: Intent?) {
        val update = 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 {
            val start = getUpdateStartTime(TimeUnit.MILLISECONDS)
            val end = getUpdateEndTime(TimeUnit.MILLISECONDS)

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

### Java

```java
public class MyDataUpdateService extends IntentService {

    public MyDataUpdateService(String name) {
        super("MyDataUpdateService");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        if (intent != null) {
            DataUpdateNotification update = 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) {
                long start = update.getUpdateStartTime(TimeUnit.MILLISECONDS);
                long end = 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.