History API を使用すると、アプリは健康とウェルネスに関する過去のデータの読み取り、挿入、更新、削除などの一括操作をフィットネス ストアで実行できます。History API を使用すると、次のことができます。
- 他のアプリを使用して挿入または記録された健康とウェルネスに関するデータを読み取る。
- バッチデータを Google Fit にインポートします。
- Google Fit でデータを更新する。
- アプリが以前に保存した過去のデータを削除します。
セッション メタデータを含むデータを挿入するには、Sessions API を使用します。
データを読み取る
以降のセクションでは、さまざまな種類の集計データを読み取る方法について説明します。
詳細データと集計データを読み取る
過去のデータを読み取るには、DataReadRequest
インスタンスを作成します。
// 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()
// 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();
上の例では、集計されたデータポイントを使用しています。各 DataPoint
は 1 日あたりの歩数を表します。この特定のユースケースでは
2 つの利点があります。
- アプリとフィットネス ストアが交換するデータの量は少なくなっています。
- アプリでデータを手動で集計する必要はありません。
複数のアクティビティ タイプのデータを集計する
アプリでは、データ リクエストを使用してさまざまな種類のデータを取得できます。次の例は、指定した期間内に行われた各アクティビティの消費カロリーを取得する DataReadRequest
を作成する方法を示しています。生成されたデータは、Google Fit アプリで報告されるアクティビティあたりのカロリーと一致します。各アクティビティには、独自のカロリーデータのバケットが割り当てられます。
val readRequest = DataReadRequest.Builder()
.aggregate(DataType.AGGREGATE_CALORIES_EXPENDED)
.bucketByActivityType(1, TimeUnit.SECONDS)
.setTimeRange(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS)
.build()
DataReadRequest readRequest = new DataReadRequest.Builder()
.aggregate(DataType.AGGREGATE_CALORIES_EXPENDED)
.bucketByActivityType(1, TimeUnit.SECONDS)
.setTimeRange(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS)
.build();
DataReadRequest
インスタンスを作成したら、HistoryClient.readData()
メソッドを使用して過去のデータを非同期で読み取ります。
次の例は、DataSet
から DataPoint
インスタンスを取得する方法を示しています。
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()
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();
}
1 日の合計データを読み取る
Google Fit では、1 日あたりの合計日数も簡単に
データタイプを指定します。HistoryClient.readDailyTotal()
メソッドを使用して、デバイスの現在のタイムゾーンで当日午前 0 時時点として指定したデータ型を取得します。たとえば、このメソッドに TYPE_STEP_COUNT_DELTA
データ型を渡して、1 日の合計歩数を取得します。1 日の合計値を含む瞬間データ型を渡すことができます。サポートされているデータ型の詳細については、DataType.getAggregateType
をご覧ください。
Google Fit では、このメソッドがデフォルト アカウントを使用して呼び出され、スコープが指定されていない場合、HistoryClient.readDailyTotal()
メソッドからの TYPE_STEP_COUNT_DELTA
更新をサブスクライブするための認可は必要ありません。これは、走行できない地域で使用するために歩数データが必要な場合に役立ちます。
Wear OS のウォッチフェイスなどに権限パネルを表示します。
ユーザーは Google Fit アプリで一貫した歩数を好みます。
その他のアプリ、Wear OS ウォッチフェイスなどです。これにより、
一貫した信頼性の高いエクスペリエンスを提供できます。歩数を一定に保つには、アプリまたはウォッチフェイスから Google Fit プラットフォームの歩数を定期購入して、onExitAmbient()
でカウントを更新します。このデータをウォッチフェイスで使用する方法について詳しくは、以下をご覧ください。
ウォッチフェイスの追加機能
および Android WatchFace サンプルアプリ
データの挿入
過去のデータを挿入するには、まず DataSet
インスタンスを作成します。
// 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()
// 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();
DataSet
インスタンスを作成したら、HistoryClient.insertData
メソッドを使用して、この過去データを非同期で追加します。
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)
}
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));
}
競合するデータポイントを管理する
アプリの DataSet
内の各 DataPoint
には、DataSet
内の一意の区間を定義する startTime
と endTime
が必要です。DataPoint
インスタンスが重複しないようにする必要があります。
アプリが既存のバージョンと競合する新しい DataPoint
を挿入しようとした場合
DataPoint
インスタンスの場合、新しい DataPoint
は破棄されます。新しい
DataPoint
が既存のデータポイントと重複する可能性がある場合は、
HistoryClient.updateData
メソッド(データを更新するを参照)
図 1. insertData()
メソッドが、既存の DataPoint
と競合する新しいデータポイントを処理する方法。
データの更新
Google Fit では、健康とウェルネスに関する過去のデータをアプリで更新できます。
挿入しました。新しい DataSet
の過去データを追加する場合、または既存のデータポイントと競合しない新しい DataPoint
インスタンスを追加する場合は、HistoryApi.insertData
メソッドを使用します。
過去のデータを更新するには、HistoryClient.updateData
メソッドを使用します。このメソッドは、このメソッドを使用して追加された DataPoint
インスタンスと重複する既存の DataPoint
インスタンスを削除します。
過去の健康とウェルネス データを更新するには、まず DataSet
インスタンスを作成します。
// 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()
// 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();
次に、DataUpdateRequest.Builder()
を使用して新しいデータ更新リクエストを作成し、HistoryClient.updateData
メソッドを使用してリクエストを送信します。
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)
}
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));
データの削除
Google Fit を使用すると、アプリは以前に挿入した健康とウェルネスに関する過去のデータを削除できます。
過去のデータを削除するには、HistoryClient.deleteData
メソッドを使用します。
// 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)
}
// 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));
アプリは、特定のセッションからデータを削除したり、すべてのデータを削除したりできます。詳しくは、DataDeleteRequest
の API リファレンスをご覧ください。
データ更新に登録する
アプリは、SensorsClient
に登録することで、未加工のセンサーデータをリアルタイムで読み取ることができます。
その他の種類が少なく、手動でカウントされるデータについては、
アプリは、これらの測定値が挿入されたときに更新を受信できるように登録できる
Google Fit データベースに
移動しましたデータの例としては、身長、
ウェイトリフティングなどのワークアウト詳しくは、こちらのリスト
サポートされています。
更新情報の登録には 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")
}
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"));
IntentService
を使用して、更新の通知を受け取ることができます。
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}")
}
}
}
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}");
}
}
}
IntentService
は AndroidManifest.xml
ファイルで宣言する必要があります。