📜 ⬆️ ⬇️

GoogleFit API - start and see the result

Hi, Habrahabr! Modern gadgets and wearable electronics allow you to not only go on the Internet from your heart's content, fumble and like content, but also monitor health, take into account sporting achievements, and just lead a healthy lifestyle.



Today we will talk about the main features of the GoogleFit API on the Android platform and try to put the information into practice: learn how to read data from sensors available in the system, save them to the cloud and read the history of records. We will also create a project that implements these tasks, and consider the general prospects for using the GoogleFit API in real-world development.
')
Thanks to ConstantineMars for help in preparing the article.

What is what


GoogleFit is a fairly small and well-documented platform. The information necessary for working with it can be viewed on our Google Developers portal, there is an entire section devoted to interaction with Fit. For those who don’t want to dive into API descriptions, but it’s interesting to know about the main features of the platform in order, the video Lisa Wray, the official Google Developer Advocate, is an excellent start.

You can start exploring the Fit platform with this tutorial:


GoogleFit allows you to receive fitness data from various sources (sensors installed on phones, smart watches, fitness bracelets), save them to the cloud storage and read them as a history of “fitness measurements” or a set of sessions / workouts.

To access the data, you can use both Android native APIs and REST APIs for writing a web client.

The most important role in the GoogleFit ecosystem is played by wearable gadgets, on which great bets are made. In addition to the “classic” smart watches, the system supports data from specialized Nike + and Jawbone Up fitness bracelets or Bluetooth sensors. As we have said, data is stored in the cloud and allows you to view statistics, freely combining information from different sources.



Fit API is part of Google Play Services . As many of you already know, it’s not so important to have the latest Android OS version on your device, like updated Play Services. Due to the removal of similar API in the part updated by Google, and not by manufacturers of smartphones, users of your applications around the world can use completely different generations of systems. In particular, the Fit API is available to anyone who has Android version 2.3 or higher (Gingerbread, API level 9) on the smartphone.

To avoid any unnecessary questions, let's designate the key concepts of the Fit API:




The Google Fitness API itself consists of the following modules:


GoogleFitResearch demo


To demonstrate the capabilities of GoogleFit, we have created a special project that will allow you to work with the API without bothering to write some basis on which everything will work. The source code of GoogleFit Research demo can be picked up at BitBucket .

Let's start with the simplest: try to get the data from the sensors live, using the Sensors API for this.

First of all, it is necessary to decide which sensors will take the initial data. The Sensors API has a special method for this, which allows you to get a list of available information sources, and we can choose from this list one, several, or at least all sensors.

As an example, we will try to read the pulse rate, the number of steps, and the change in user coordinates. It should be noted that, although we are turning to the pulse meter, we still will not receive data from it: the pulse meter is available in smart watches and fitness trackers, but not in the smartphone itself, we agree that at the time of writing the code there are neither watches nor sensors we do not have a pulse - as there is no data from them either. So we will be able to evaluate how the system responds to the “negatvny test”, i.e. a case where instead of the expected data we get zeroes at best, and at worst a system error message.

Up to all night to get started


All you need to work with an example is your Google account. We don’t need to create a database or write our own server - the GoogleFit API has already taken care of everything.

As an official example, you can use the sources from Google Developers, available on GitHub .

Project preparation


  1. First you will need to log in to your Google account (if for some improbable reasons you still don’t have one, you can correct this misunderstanding at the following link: https://accounts.google.com/SignUp );
  2. Logged in? Go to the Google Developer Console and create a new project. The main thing is to remember to include the Fitness API for it;




  1. Now you need to add the SHA1-key from the project to the console. To do this, use the keytool utility. How to do this is perfectly described in the Google Fit tutorial. We are updating Play Services to the latest version: they are needed for the API to work, first of all, for access to the cloud data storage.




  1. Add a dependency on Play Services to the build.gradle project:


dependencies {
compile 'com.google.android.gms: play-services: 6.5. +'
}


Authorization


With the preparation of the project more or less figured out, now proceed directly to the authorization code.

Connect with services will be using GoogleApiClient. The following code creates a client object that requests Fitness.API from services, adds us read access (SCOPE_LOCATION_READ) and write (SCOPE_BODY_READ_WRITE) and sets the Listeners that will handle the data and errors from Fitness.API. After that, this code snippet tries to connect to Google Play Services with the specified settings:
Hidden text
client = new GoogleApiClient.Builder(activity) .addApi(Fitness.API) .addScope(Fitness.SCOPE_LOCATION_READ) .addScope(Fitness.SCOPE_ACTIVITY_READ) .addScope(Fitness.SCOPE_BODY_READ_WRITE) .addConnectionCallbacks( new GoogleApiClient.ConnectionCallbacks() { @Override public void onConnected(Bundle bundle) { display.show("Connected"); connection.onConnected(); } @Override public void onConnectionSuspended(int i) { display.show("Connection suspended"); if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_NETWORK_LOST) { display.show("Connection lost. Cause: Network Lost."); } else if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) { display.show("Connection lost. Reason: Service Disconnected"); } } } ) .addOnConnectionFailedListener( new GoogleApiClient.OnConnectionFailedListener() { // Called whenever the API client fails to connect. @Override public void onConnectionFailed(ConnectionResult result) { display.log("Connection failed. Cause: " + result.toString()); if (!result.hasResolution()) { GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), activity, 0).show(); return; } if (!authInProgress) { try { display.show("Attempting to resolve failed connection"); authInProgress = true; result.startResolutionForResult(activity, REQUEST_OAUTH); } catch (IntentSender.SendIntentException e) { display.show("Exception while starting resolution activity: " + e.getMessage()); } } } } ) .build(); lient.connect(); 



GoogleApiClient.ConnectionCallbacks - provides processing of successful (onConnected) or unsuccessful (onConnectionSuspended) connection.
GoogleApiClient.OnConnectionFailedListener - handles connection errors and the most important situation - authorization error when you first access the GoogleFit API, thus giving the user a web form of OAuth authorization (result.startResolutionForResult):

Authorization is performed using a standard web form:



The result of the correction of the authorization error that was started by calling startResolutionForResult is processed in onActivityResult:
Hidden text
 @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_OAUTH) { display.log("onActivityResult: REQUEST_OAUTH"); authInProgress = false; if (resultCode == Activity.RESULT_OK) { // Make sure the app is not already connected or attempting to connect if (!client.isConnecting() && !client.isConnected()) { display.log("onActivityResult: client.connect()"); client.connect(); } } } } 

We use the authInProgress variable to prevent the authorization procedure from re-running and the request ID REQUEST_OAUTH. If successful, connect the client by calling mClient.connect (). This is the challenge that we have already tried to implement onCreate, and to which we received an error during the very first authorization.

Sensors API


Sensors API provides live data from sensors at a specified time interval or event.

To demonstrate the operation of individual APIs in our example, we added wrappers that leave only generalized code to be called from MainActivity. For example, for the SensorsAPI in the onConnected () client callback, we call:
Hidden text
 display.show("client connected"); // we can call specific api only after GoogleApiClient connection succeeded initSensors(); display.show("list datasources"); sensors.listDatasources(); 

Inside, work directly with the Sensors API:
Hidden text
 Fitness.SensorsApi.findDataSources(client, new DataSourcesRequest.Builder() .setDataTypes( DataType.TYPE_LOCATION_SAMPLE, DataType.TYPE_STEP_COUNT_DELTA, DataType.TYPE_DISTANCE_DELTA, DataType.TYPE_HEART_RATE_BPM ) .setDataSourceTypes(DataSource.TYPE_RAW, DataSource.TYPE_DERIVED) .build()) .setResultCallback(new ResultCallback<DataSourcesResult>() { @Override public void onResult(DataSourcesResult dataSourcesResult) { datasources.clear(); for (DataSource dataSource : dataSourcesResult.getDataSources()) { Device device = dataSource.getDevice(); String fields = dataSource.getDataType().getFields().toString(); datasources.add(device.getManufacturer() + " " + device.getModel() + " [" + dataSource.getDataType().getName() + " " + fields + "]"); final DataType dataType = dataSource.getDataType(); if ( dataType.equals(DataType.TYPE_LOCATION_SAMPLE) || dataType.equals(DataType.TYPE_STEP_COUNT_DELTA) || dataType.equals(DataType.TYPE_DISTANCE_DELTA) || dataType.equals(DataType.TYPE_HEART_RATE_BPM)) { Fitness.SensorsApi.add(client, new SensorRequest.Builder() .setDataSource(dataSource) .setDataType(dataSource.getDataType()) .setSamplingRate(5, TimeUnit.SECONDS) .build(), new OnDataPointListener() { @Override public void onDataPoint(DataPoint dataPoint) { String msg = "onDataPoint: "; for (Field field : dataPoint.getDataType().getFields()) { Value value = dataPoint.getValue(field); msg += "onDataPoint: " + field + "=" + value + ", "; } display.show(msg); } }) .setResultCallback(new ResultCallback<Status>() { @Override public void onResult(Status status) { if (status.isSuccess()) { display.show("Listener for " + dataType.getName() + " registered"); } else { display.show("Failed to register listener for " + dataType.getName()); } } }); } } datasourcesListener.onDatasourcesListed(); } }); 

Fitness.SensorsApi.findDataSources queries the list of available data sources (which we display in the Datasources fragment).

DataSourcesRequest should include type filters for which we want to get sources, for example DataType.TYPE_STEP_COUNT_DELTA .

As a result of the query, we get DataSourcesResult, from which you can get the details of each data source (device, brand, data type, data type fields):
Hidden text
  for (DataSource dataSource : dataSourcesResult.getDataSources()) { Device device = dataSource.getDevice(); String fields = dataSource.getDataType().getFields().toString(); datasources.add(device.getManufacturer() + " " + device.getModel() + " [" + dataSource.getDataType().getName() + " " + fields + "]"); 

The list of data sources obtained by us may look like this:



In our example, we have simplified the task and subscribe to updates from each source that fits our criteria. In real life, it makes sense to choose one source, narrowing down the criteria so as not to get redundant data that litters traffic. By subscribing to messages from the data source, we can also set the data reading interval (SamplingRate):
Hidden text
 Fitness.SensorsApi.add(client, new SensorRequest.Builder() .setDataSource(dataSource) .setDataType(dataSource.getDataType()) .setSamplingRate(5, TimeUnit.SECONDS) .build(), new OnDataPointListener() { … } 

DataPoint - sensor readings. Naturally, the sensors are different, and their description is the so-called "fields" (fields), which can be considered from the data type, along with the values:
Hidden text
 new OnDataPointListener() { @Override public void onDataPoint(DataPoint dataPoint) { String msg = "onDataPoint: "; for (Field field : dataPoint.getDataType().getFields()) { Value value = dataPoint.getValue(field); msg += "onDataPoint: " + field + "=" + value + ", "; } display.show(msg); } }) 

For example, the step counter (delta) gives us a new entry for each step (or rather, what the sensor perceives as a step, because in this case it was possible to get along with the usual shaking the phone to generate new entries :-p).



Recording API


Records do not give visual results, but their work can be traced through the History API in the form of data stored in the cloud. Actually, all that can be done using the Recording API is to subscribe to events (so that the system automatically keeps records for us, unsubscribe from them and search for existing subscriptions):
Hidden text
 Fitness.RecordingApi.subscribe(client, DataType.TYPE_STEP_COUNT_DELTA) .setResultCallback(new ResultCallback<Status>() { @Override public void onResult(Status status) { if (status.isSuccess()) { if (status.getStatusCode() == FitnessStatusCodes.SUCCESS_ALREADY_SUBSCRIBED) { display.show("Existing subscription for activity detected."); } else { display.show("Successfully subscribed!"); } } else { display.show("There was a problem subscribing."); } } }); 

Here we subscribe to DataType.TYPE_STEP_COUNT_DELTA. If you wish to collect other types of data, it is enough to repeat the call for a different data type.

Listing of existing subscriptions is done as follows:
Hidden text
 Fitness.RecordingApi.listSubscriptions(client, DataType.TYPE_STEP_COUNT_DELTA).setResultCallback(new ResultCallback<ListSubscriptionsResult>() { @Override public void onResult(ListSubscriptionsResult listSubscriptionsResult) { for (Subscription sc : listSubscriptionsResult.getSubscriptions()) { DataType dt = sc.getDataType(); display.show("found subscription for data type: " + dt.getName()); } } }); 


The logs of the Recordings tab look like this:


History API


The History API provides work with data packages that can be saved and loaded from the cloud. This includes reading data at specific time intervals, saving previously read data (as opposed to the Recording API, this is a data packet, not a live stream), deleting records made from the same application.
Hidden text
 DataReadRequest readRequest = new DataReadRequest.Builder() .aggregate(DataType.TYPE_STEP_COUNT_DELTA, DataType.AGGREGATE_STEP_COUNT_DELTA) .bucketByTime(1, TimeUnit.DAYS) .setTimeRange(start, end, TimeUnit.MILLISECONDS) .build(); 

When forming a query (DataReadRequest), we can specify aggregation operations, for example, combine TYPE_STEP_COUNT_DELTA into AGGREGATE_STEP_COUNT_DELTA, representing the total number of steps for a selected period of time; specify the sampling interval (.bucketByTime), set the time interval for which we need data (.setTimeRange).
Hidden text
 Fitness.HistoryApi.readData(client, readRequest).setResultCallback(new ResultCallback<DataReadResult>() { @Override public void onResult(DataReadResult dataReadResult) { if (dataReadResult.getBuckets().size() > 0) { display.show("DataSet.size(): " + dataReadResult.getBuckets().size()); for (Bucket bucket : dataReadResult.getBuckets()) { List<DataSet> dataSets = bucket.getDataSets(); for (DataSet dataSet : dataSets) { display.show("dataSet.dataType: " + dataSet.getDataType().getName()); for (DataPoint dp : dataSet.getDataPoints()) { describeDataPoint(dp, dateFormat); } } } } else if (dataReadResult.getDataSets().size() > 0) { display.show("dataSet.size(): " + dataReadResult.getDataSets().size()); for (DataSet dataSet : dataReadResult.getDataSets()) { display.show("dataType: " + dataSet.getDataType().getName()); for (DataPoint dp : dataSet.getDataPoints()) { describeDataPoint(dp, dateFormat); } } } } }); 

Depending on the type of request, we can get either buckets dataReadResult.getBuckets () or DataSets dataReadResult.getDataSets ().
In essence, the bucket is just a DataSets collection, and the API gives us a choice: if there are no buckets in the API response, we can directly work with the DataSets collection from the dataResult.
Reading DataPoints can be done, for example, like this:
Hidden text
 public void describeDataPoint(DataPoint dp, DateFormat dateFormat) { String msg = "dataPoint: " + "type: " + dp.getDataType().getName() +"\n" + ", range: [" + dateFormat.format(dp.getStartTime(TimeUnit.MILLISECONDS)) + "-" + dateFormat.format(dp.getEndTime(TimeUnit.MILLISECONDS)) + "]\n" + ", fields: ["; for(Field field : dp.getDataType().getFields()) { msg += field.getName() + "=" + dp.getValue(field) + " "; } msg += "]"; display.show(msg); } 

Our logs will be filled with information from previous sessions recorded via Recording and what the official GoogleFit has collected for us (it also activates the Recording API, using which it counts, for example, the number of steps and the activity time per day).



What's next?


So, we looked at the possibilities of reading data directly from sensors (Sensors API), automated recording of sensors' indicators in GoogleFit (Recording API), and working with history (History API). This is the basic functionality of a fitness tracker, which is enough for a full-fledged application.

Then there are two more interesting APIs provided by GoogleFit - Sessions and Bluetooth. The first allows you to group activities in sessions and segments for more structured work with fitness data . The second allows you to search and connect to Bluetooth sensors that are within range, such as heart rate monitors, sensors in shoes / clothes, etc.

You can also create software sensors and thus work with devices that do not implement the necessary protocols, but provide data (implemented using the FitnessSensorService). These features are not necessary, but they add quite good opportunities to get your own data types (aggregated from data from other sensors or generated programmatically) and can be used if necessary.

Of course, if you start working with the GoogleFit API, you will want to make the application beautiful and enjoyable to use. Two more components may be needed for this: displaying graphs similar to what GoogleFit draws (for which there are many external libraries, for example, Bitbucket , and almost certainly AndroidWear , which, in particular, provides an API for interacting with the read sensor pulse in smartwatch



Good luck and success in sports!

Source: https://habr.com/ru/post/252083/


All Articles