Google Fit is a platform that allows developers to build applications that are focused on user fitness data. One of the tools Google has provided is Google Fit for Android, which is available as a package in Google Play Services.
Earlier in this series, we have explored how to store fitness data through Google Play Services using the Recording API and how to access and update data through the History API. In this tutorial, we expand on these ideas with the Sessions API, which allows you to organize activity data by time intervals.
This tutorial walks you through a sample project that demonstrates how to work with the Sessions API. The finished project can be downloaded from GitHub.
1. Project Setup
Step 1: Set Up the Developer Console
To use Google Fit, you need to create an OAuth 2.0 client ID and register your application through the Google Developer Console. You can find a detailed explanation of how to set up a project in the Google Developer Console in my tutorial about the Google Fit Sensors API.
Step 2: Set Up the Android Project
Once you have your application configured for authentication in the Google Developer Console, use the package name you registered to create a new Android application with an empty Activity
.
With the Android project created, open build.gradle and include Google Play Services under the dependencies node and sync your app. In this sample, I am also using the Butter Knife library to cut down on boiler plate code for click listeners and finding View
items.
compile 'com.google.android.gms:play-services-fitness:8.4.0' compile 'com.jakewharton:butterknife:7.0.1'
Once you have synced your dependencies, open AndroidManifest.xml and include the ACCESS_FINE_LOCATION
permission. This permission is required to read session data from Google Fit. While I don't go into detail in this tutorial, it is worth noting that, if you are targeting API 23 or higher, you need to request the location permission from the user.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Next, open activity_main.xml and update it so that it includes five Button
items that will be used to demonstrate some of the functionality of the Sessions API. The end result should look similar to the following:
Once the layout is created, open MainActivity.java and connect to Google Play Services.
public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { private final String SESSION_NAME = "session name"; private GoogleApiClient mGoogleApiClient = null; private Session mSession; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); mGoogleApiClient = new GoogleApiClient.Builder(this) .addApi(Fitness.SESSIONS_API) .addApi(Fitness.HISTORY_API) .addScope(new Scope(Scopes.FITNESS_LOCATION_READ_WRITE)) .addScope(new Scope(Scopes.FITNESS_ACTIVITY_READ_WRITE)) .addConnectionCallbacks(this) .enableAutoManage(this, 0, this) .build(); } @Override protected void onDestroy() { super.onDestroy(); ButterKnife.unbind(this); } @Override public void onConnected(Bundle bundle) { } @Override public void onConnectionSuspended(int i) { } @Override public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { } }
You may have noticed that you are including the History API along with the Sessions API. This is necessary because the History API is used for deleting session data, which we'll discuss later in this tutorial. You also need to use both the FITNESS_LOCATION_READ_WRITE
and FITNESS_ACTIVITY_READ_WRITE
scope properties for the Sessions API features that we will be discussing.
Once you have set your app up for connecting to Google Play Services and Google Fit, it's time to start trying out the Sessions API.
2. Using the Sessions API
The Sessions API is another tool from Google Fit for storing fitness data for the user. As such, it is not intended to be a replacement for the Recording or History APIs, but rather is an additional way to improve your apps for a better user experience.
In this section, you learn how to start and stop real time session data collection, how to insert a session into the Google Fit data store, how to read previously stored session data, and how to delete unnecessary or incorrect sessions. Similar to the History API, Sessions API calls must be made off of the main thread. While the tutorial for the History API wrapped operations in AsyncTask
calls, this tutorial will focus on using PendingResult
callbacks to keep track of tasks.
Starting and Stopping a Session
The most common scenario for the Sessions API is recording information after a user has initiated a fitness activity. This is often started concurrently with the Recording API so that sensor data can be associated with the session timeframe. To start recording a session, you need to create a session object with a name, a unique identifier, a description, a start time, and the type of activity that the user is performing.
mSession = new Session.Builder() .setName(SESSION_NAME) .setIdentifier(getString(R.string.app_name) + " " + System.currentTimeMillis()) .setDescription("Yoga Session Description") .setStartTime(Calendar.getInstance().getTimeInMillis(), TimeUnit.MILLISECONDS) .setActivity(FitnessActivities.YOGA) .build();
Once your Session
object is created, you can call startSession
on the Sessions API to start recording your user's activity.
PendingResult<Status> pendingResult = Fitness.SessionsApi.startSession(mGoogleApiClient, mSession); pendingResult.setResultCallback( new ResultCallback<Status>() { @Override public void onResult(Status status) { if (status.isSuccess()) { Log.i("Tuts+", "Successfully started session"); } else { Log.i("Tuts+", "Failed to start session: " + status.getStatusMessage()); } } } );
When your user has finished their activity, you can end the session by calling stopSession
with the unique identifier for the Session
.
PendingResult<SessionStopResult> pendingResult = Fitness.SessionsApi.stopSession(mGoogleApiClient, mSession.getIdentifier()); pendingResult.setResultCallback(new ResultCallback<SessionStopResult>() { @Override public void onResult(SessionStopResult sessionStopResult) { if( sessionStopResult.getStatus().isSuccess() ) { Log.i("Tuts+", "Successfully stopped session"); } else { Log.i("Tuts+", "Failed to stop session: " + sessionStopResult.getStatus().getStatusMessage()); } } });
Something worth noting here is that the Session
object implements the Parcelable
interface, so it can be stored in a Bundle
in case you need to store or pass it off to another component within your application. This can be useful if the user starts an activity and then closes the app, as you may need that session data later to stop recording the session.
Inserting Into Google Fit
Now that you know how to record real time session information, the next step is being able to insert sessions after-the-fact. While you can insert a block of time as a session, the Sessions API also allows you to associate activities with segments of a session. This is useful for situations where the user may not continuously do the same activity over their entire fitness routine.
An example of this may be when your app has detected that the user ran, rested for a short period by walking, and then ran again. In the following example, you insert a Session
where the user ran for 15 minutes, walked for five, and then ran for another 15 minutes by keeping track of when the user started and stopped each activity. You also record their average speed for each of those activities.
Calendar calendar = Calendar.getInstance(); Date now = new Date(); calendar.setTime(now); long endTime = calendar.getTimeInMillis(); calendar.add(Calendar.MINUTE, -15); long walkEndTime = calendar.getTimeInMillis(); calendar.add(Calendar.MINUTE, -5); long walkStartTime = calendar.getTimeInMillis(); calendar.add(Calendar.MINUTE, -15); long startTime = calendar.getTimeInMillis(); float firstRunSpeed = 15; float walkSpeed = 5; float secondRunSpeed = 13;
Once you have the key time and speed values, you need to create two DataSet
groups for storing information on activities and speed. These can be created from a set of DataSource
objects with the appropriate DataType
properties set.
DataSource speedSegmentDataSource = new DataSource.Builder() .setAppPackageName(this.getPackageName()) .setDataType(DataType.TYPE_SPEED) .setName("Tuts+ speed dataset") .setType(DataSource.TYPE_RAW) .build(); DataSource activitySegmentDataSource = new DataSource.Builder() .setAppPackageName(this.getPackageName()) .setDataType(DataType.TYPE_ACTIVITY_SEGMENT) .setName("Tuts+ activity segments dataset") .setType(DataSource.TYPE_RAW) .build(); DataSet speedDataSet = DataSet.create(speedSegmentDataSource); DataSet activityDataSet = DataSet.create(activitySegmentDataSource);
Next, you need to create the DataPoint
objects that will be inserted into the Google Fit data store. Each DataPoint
has a time interval and a set of Field
properties that store related data. Once each DataPoint
is configured, you need to insert it into the proper DataSet
. For brevity, I only show the code snippet for creating DataPoint
objects for the first run segment below, though, the other data point objects are shown in the source code for this tutorial on GitHub.
//Create speed data point for first run segment DataPoint firstRunSpeedDataPoint = speedDataSet.createDataPoint() .setTimeInterval(startTime, walkStartTime, TimeUnit.MILLISECONDS); firstRunSpeedDataPoint.getValue(Field.FIELD_SPEED).setFloat(firstRunSpeed); speedDataSet.add(firstRunSpeedDataPoint); //Create activity data point for second run segment DataPoint secondRunActivityDataPoint = activityDataSet.createDataPoint() .setTimeInterval(walkEndTime, endTime, TimeUnit.MILLISECONDS); secondRunActivityDataPoint.getValue(Field.FIELD_ACTIVITY).setActivity(FitnessActivities.RUNNING); activityDataSet.add(secondRunActivityDataPoint);
Once you have populated the two DataSet
groups, it's time to create the Session
that you will insert. This Session
will need to set the name, unique identifier, description, start and end times, and activity properties. One important thing to be aware of is that the name that you use when inserting a Session
is the name that you need to use when attempting to read this session later.
Session session = new Session.Builder() .setName(SESSION_NAME) .setIdentifier(getString(R.string.app_name) + " " + System.currentTimeMillis()) .setDescription("Running in Segments") .setStartTime(startTime, TimeUnit.MILLISECONDS) .setEndTime(endTime, TimeUnit.MILLISECONDS) .setActivity(FitnessActivities.RUNNING) .build();
Now that your Session
is created, you need to create a SessionInsertRequest
, add your DataSet
objects to it, and then insert that Session
into Google Fit.
SessionInsertRequest insertRequest = new SessionInsertRequest.Builder() .setSession(session) .addDataSet(speedDataSet) .addDataSet(activityDataSet) .build(); PendingResult<Status> pendingResult = Fitness.SessionsApi.insertSession(mGoogleApiClient, insertRequest); pendingResult.setResultCallback(new ResultCallback<Status>() { @Override public void onResult(Status status) { if( status.isSuccess() ) { Log.i("Tuts+", "successfully inserted running session"); } else { Log.i("Tuts+", "Failed to insert running session: " + status.getStatusMessage()); } } });
Reading Session Data
No matter how much session data you store with your app, it won't do you much good unless you're able to read that data and use it to make your app better for your users. Luckily, Google Fit makes it incredibly easy to receive session data within a set timeframe.
First, you need to select the start and end time for the data you would like to receive. In this example, we request session data for the last month.
Calendar cal = Calendar.getInstance(); Date now = new Date(); cal.setTime(now); long endTime = cal.getTimeInMillis(); cal.add(Calendar.MONTH, -1); long startTime = cal.getTimeInMillis();
Once you have your window of time selected, you need to create a SessionReadRequest
. This request includes the time interval for your desired session data, the type of session data you are looking for (this tutorial will only look for speed data), and the name of the sessions that you are requesting, though, the time interval is the only required property.
SessionReadRequest readRequest = new SessionReadRequest.Builder() .setTimeInterval(startTime, endTime, TimeUnit.MILLISECONDS) .read(DataType.TYPE_SPEED) .setSessionName(SESSION_NAME) .build(); PendingResult<SessionReadResult> sessionReadResult = Fitness.SessionsApi.readSession(mGoogleApiClient, readRequest);
After you have called SessionsApi.readSession
, you receive a SessionReadResult
object that contains lists of Session
and DataSet
objects that you can use within your app. For this tutorial, we simply log the data.
sessionReadResult.setResultCallback(new ResultCallback<SessionReadResult>() { @Override public void onResult(SessionReadResult sessionReadResult) { if (sessionReadResult.getStatus().isSuccess()) { Log.i("Tuts+", "Successfully read session data"); for (Session session : sessionReadResult.getSessions()) { Log.i("Tuts+", "Session name: " + session.getName()); for (DataSet dataSet : sessionReadResult.getDataSet(session)) { for (DataPoint dataPoint : dataSet.getDataPoints()) { Log.i("Tuts+", "Speed: " + dataPoint.getValue(Field.FIELD_SPEED)); } } } } else { Log.i("Tuts+", "Failed to read session data"); } } });
If you run your application now and use the insert and read operations, you should receive the three speed DataPoint
objects that you created in the previous section.
I/Tuts+: Speed: 15.0 I/Tuts+: Speed: 5.0 I/Tuts+: Speed: 13.0
Deleting Session Data
There may be times where you need to delete some session information that is either incorrect or unnecessary. This can be done by using the History API to delete sessions that have been saved in the Google Fit data store.
First, you need to determine a time interval for the delete operation. You can then add additional information, such as a DataType
or other Session
properties to delete, or you can simply delete all session data within the time interval.
Calendar calendar = Calendar.getInstance(); Date now = new Date(); calendar.setTime(now); long endTime = calendar.getTimeInMillis(); calendar.add(Calendar.DAY_OF_YEAR, -1); long startTime = calendar.getTimeInMillis(); DataDeleteRequest request = new DataDeleteRequest.Builder() .setTimeInterval(startTime, endTime, TimeUnit.MILLISECONDS) .addDataType(DataType.TYPE_SPEED) .deleteAllSessions() .build(); PendingResult<Status> deleteRequest = Fitness.HistoryApi.deleteData(mGoogleApiClient, request); deleteRequest.setResultCallback(new ResultCallback<Status>() { @Override public void onResult(@NonNull Status status) { if( status.isSuccess() ) { Log.i("Tuts+", "Successfully deleted sessions"); } else { Log.i("Tuts+", "Failed to delete sessions"); } } });
Conclusion
As you have learned in this tutorial, the Sessions API is a powerful tool for organizing gathered data in blocks of time. It is a great compliment to the other Google Fit APIs. When used in conjunction with the History and Recording APIs, there's a lot you can do to understand the activity of your app's users and provide them with an excellent experience with your fitness apps.