Skip to main

How to Integrate Google Fit With Your Android App

by Ziemowit Pazderski

How to Integrate Google Fit With Your Android App

With the influx of health-conscious customers, ensuring that your fitness product delivers a holistic, integrated service is growing ever more important.

Every creator of health-oriented applications is practically obliged to connect it to a fitness service to facilitate the seamless transfer of data between various (often competing) applications. In the Android world, that solution is the Google Fit API.

Copy link

As is standard for Google’s server APIs, there are two ways to communicate with the server:

  • classic REST API

  • through SDK, which connects to the server on its own

These solutions do not share a code base, so for example, slightly different fitness activity (as in “rowing” or “kayaking”, not MainActivity) types are available:

(Note the lack of a “Guided breathing” activity type for SDK.)

The choice between the two mainly depends on the level of technical sophistication of the SDK - if it’s advanced enough, well-maintained, and documented, it is almost always more convenient to use than raw REST endpoints.

However, some optimization techniques, especially in using cached data, might mean that the data provided by the SDK is less reliable than that acquired directly. At least until recently, this was the case with the Fit APIs (plural), with its Local Storage (described below), so that is something worth testing before deciding on an approach. 

Copy link
Local Storage vs Fit server

In order to improve performance and reduce server load, most API calls do not connect to the Google Fit server, using the local store for reading and writing data instead.

Unless explicitly demanded, data propagation happens only every few hours. This can lead to confusion if, for example, you expect the changed data to be immediately available on another device. On the other hand, this does make the data accessible to any local Fit-integrated applications even without a network. 

This is particularly important to keep in mind when testing the application since with data sync not happening immediately, false negatives can be reported.

On the other hand, all local changes will be available immediately, even if the device doesn’t have a network connection at the moment. This is a great solution performance-wise, but quite confusing (and currently under-documented) during testing. With that, of great help will be the Playground:

Copy link
Oauth 2.0 Playground

One feature of Google APIs I personally found particularly useful was the possibility of seamless online testing of its REST APIs. Not just for Fit - the list is astonishingly long and only keeps growing. With real data underneath, the Playground allows for quick verification of the stored data, before writing a single line of code. 

Copy link
Recognizing custom activities

As is often the case, an application might need to separate the Fit activities created by it from the remaining activities. There are two approaches to achieve that:

  • using the app package name

  • using a unique activity session identifier pattern

The first one is straightforward - simply call setAppPackageName when creating an activity, and verify that value when checking the downloaded activity’s type. The obvious downside is that this value will be identical for all activities an app creates, so if a more sophisticated separation is required, the package name alone is not enough. That’s when the naming pattern comes in - set the session identifier according to the chosen pattern and then verify, like below:

1 2 3 4 private fun activityIsCustomActivity(session: Session): Boolean { return session.activity == FitnessActivities.KITESURFING && CUSTOM_SESSION_KEY_REGEX.matches(session.identifier) }

Copy link
Data type permissions

In times when user’s privacy is an ever-growing concern, it is increasingly important to guarantee that an application will know about the user only as much (or as little) as it is required for it to function. It is entirely feasible for a user to wish to allow an app’s access to his basic biological data, but withhold some more sensitive data such as heart rate. In response, Fit allows very detailed compartmentalization of the accessed data. 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 fun syncOptions(): GoogleSignInOptionsExtension = FitnessOptions.builder() .addDataType(DataType.TYPE_ACTIVITY_SEGMENT, FitnessOptions.ACCESS_READ) .addDataType(DataType.TYPE_ACTIVITY_SEGMENT, FitnessOptions.ACCESS_WRITE) .addDataType(DataType.TYPE_HEART_RATE_BPM, FitnessOptions.ACCESS_READ) .addDataType(DataType.TYPE_HEART_RATE_BPM, FitnessOptions.ACCESS_WRITE) .addDataType(DataType.TYPE_HEIGHT, FitnessOptions.ACCESS_READ) .addDataType(DataType.TYPE_HEIGHT, FitnessOptions.ACCESS_WRITE) .addDataType(DataType.TYPE_WEIGHT, FitnessOptions.ACCESS_READ) .addDataType(DataType.TYPE_WEIGHT, FitnessOptions.ACCESS_WRITE) .build()

List of Google Fit datatypes.

Copy link
Read data - example

For a simple example, let’s read the user’s height data from the server. First, we need a working Google Account object:

1 val signInAccount = GoogleSignIn.getLastSignedInAccount(context)

If the signInAccount value is not null, the app is authenticated, but not yet authorized. We need to request the permission to read/write data of the required data types.

1 2 3 4 5 6 fun syncOptions(): GoogleSignInOptionsExtension = FitnessOptions.builder() .addDataType(DataType.TYPE_HEIGHT, FitnessOptions.ACCESS_READ) .build() val requestCode = 123 GoogleSignIn.requestPermissions(activity, requestCode, googleSignInAccount, syncOptions())

The SDK will display a permissions dialog to the user. The result of the user’s action will be returned in a standard onActivityResult callback. To use the SDK later, we only need to check if the permissions are still granted (as in they have not been revoked)

1 val hasSyncPermission = GoogleSignIn.hasPermissions(signInAccount, syncOptions())

If permitted, it is finally time to read the data from the server. We only need the latest height data, so the limit will be set to 1. We want to make sure that we get data from the server and not just from the local storage, so we set the “enableServerQueries” flag. And of course with the full-time limit, to get even the oldest data.

1 2 3 4 5 6 7 8 9 10 11 fun readPersonalProperty( signInAccount: GoogleSignInAccount, dataType: DataType): Task<DataReadResponse> = Fitness.getHistoryClient(context, signInAccount) .readData(DataReadRequest.Builder() .read(dataType) .setTimeRange(1L,, TimeUnit.SECONDS) .setLimit(1) .enableServerQueries() .build()) readPersonalProperty(signInAccount, DataType.TYPE_HEIGHT)

Copy link
Sensors data

Of all the APIs that compose the full fitness solution, Sensors Api is of particular importance. For all health applications that do not have a backing hardware device (like a bracelet or ring), the smartphone will be the source of all new data. 

In general, sensors on Android devices belong to three categories:

  • Motion sensors - measure acceleration and rotation along three axes

  • Environmental sensors - measure i.e. light, temperature, ambiance or air pressure

  • Position sensors - measure the physical position of a device

The availability of sensor data depends on the presence of sensors of different types inside the device, hence the need to verify that in your application, before querying for the data. Therefore, a fallback mechanism needs to be implemented to provide a satisfying user experience even if most sensors are absent or malfunctioning.

Most of these sensors can have varying accuracy, of which the application will be notified by the onAccuracyChanged callback - particularly important for uses that require fine-grained data.

If you're interested in health-oriented digital products, check out how we helped to create the world's first wellness ring – Oura.


Related Articles