Note
I couldn't find any similar question on StackOverflow. In the only threads I found, they asked about reading, not writing.
Issue
I am integrating GoogleFit but I am not able to insert blood pressure data to the HistoryApi. I successfully login, but when adding data, I always get:
Status{statusCode=TIMEOUT, resolution=null}
I tried putting the code in a AsyncTask and inserting synchronously with .await(1, TimeUnit.MINUTES) but still getting the same error.
I also tried uninstalling GoogleFit and I have internet access via WiFi.
If it helps,S Health is working fine.
Code
public static void saveBloodPressure(Context context, long timestampMillis, int systolic, int diastolic){
// Create DataSource
DataSource bloodPressureSource = new DataSource.Builder()
.setDataType(HealthDataTypes.TYPE_BLOOD_PRESSURE)
.setAppPackageName(context)
.setStreamName(TAG + " - blood pressure")
.setType(DataSource.TYPE_RAW)
.build();
// Create DataPoint with DataSource
DataPoint bloodPressure = DataPoint.create(bloodPressureSource);
bloodPressure.setTimestamp(timestampMillis, TimeUnit.MILLISECONDS);
bloodPressure.getValue(HealthFields.FIELD_BLOOD_PRESSURE_SYSTOLIC).setFloat(systolic);
bloodPressure.getValue(HealthFields.FIELD_BLOOD_PRESSURE_DIASTOLIC).setFloat(diastolic);
// Create DataSet
DataSet dataSet = DataSet.create(bloodPressureSource);
dataSet.add(bloodPressure);
// Create Callback to manage Result
ResultCallback<com.google.android.gms.common.api.Status> callback = new ResultCallback<com.google.android.gms.common.api.Status>() {
#Override
public void onResult(#NonNull com.google.android.gms.common.api.Status status) {
if (status.isSuccess()) {
Log.v("GoogleFit", "Success: " + status);
}else{
Log.v("GoogleFit", "Error: " + status);
}
}
};
// Execute insert
Fitness.HistoryApi.insertData(mGoogleApiClient, dataSet)
.setResultCallback(callback, 1, TimeUnit.MINUTES);
}
In case someone asks, I will also put the GoogleApiClient initialization below.
GoogleApiClient initialization
public static void initialize(final FragmentActivity activity){
// Setup Callback listener
GoogleApiClient.ConnectionCallbacks connectionCallbacks = new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(Bundle bundle) {
Log.i(TAG, "Connected! ");
// Now you can make calls to the Fitness APIs.
//subscribe();
}
#Override
public void onConnectionSuspended(int i) {
// If your connection to the sensor gets lost at some point,
// you'll be able to determine the reason and react to it here.
if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_NETWORK_LOST) {
Log.i(TAG, "1 Connection lost. Cause: Network Lost.");
} else if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) {
Log.i(TAG, "2 Connection lost. Reason: Service Disconnected");
}
}
};
// Handle Failed connection
GoogleApiClient.OnConnectionFailedListener connectionFailed = new GoogleApiClient.OnConnectionFailedListener() {
#Override
public void onConnectionFailed(#NonNull ConnectionResult result) {
Log.i(TAG, "3 Google Play services connection failed. Cause: " + result.toString());
Toast.makeText(activity, "4 Exception while connecting to Google Play services: " +
result.getErrorMessage() + ":" + result.getErrorCode(), Toast.LENGTH_SHORT).show();
}
};
// Create Google Api Client
mGoogleApiClient = new GoogleApiClient.Builder(activity)
.addConnectionCallbacks(connectionCallbacks)
.enableAutoManage(activity, connectionFailed)
.addScope(new Scope(Scopes.FITNESS_BODY_READ_WRITE))
.addApi(Fitness.HISTORY_API)
.build();
}
Thanks!
Even though it seems a connection timeout error, it seems to me that you are missing something.
I'm not sure if this will help but FITNESS_BODY_READ_WRITE scope needs permissions.
Are you authorizing with Fitness API before calling Fitness.HistoryApi.insertData?
For which user are you inserting data?
See here: https://developers.google.com/android/guides/permissions
And here (Authorization): https://developers.google.com/android/reference/com/google/android/gms/fitness/Fitness
Follow the guide on Insert data
Insert data
To insert data into the fitness history, first create a DataSet
instance:
// Set a start and end time for our data, using a start time of 1 hour before this moment.
Calendar cal = Calendar.getInstance();
Date now = new Date();
cal.setTime(now);
long endTime = cal.getTimeInMillis();
cal.add(Calendar.HOUR_OF_DAY, -1);
long startTime = cal.getTimeInMillis();
// 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
int stepCountDelta = 950;
DataSet dataSet = DataSet.create(dataSource);
// For each data point, specify a start time, end time, and the data value -- in this case,
// the number of new steps.
DataPoint dataPoint = dataSet.createDataPoint()
.setTimeInterval(startTime, endTime, TimeUnit.MILLISECONDS);
dataPoint.getValue(Field.FIELD_STEPS).setInt(stepCountDelta);
dataSet.add(dataPoint);
After you create a DataSet instance, use the HistoryApi.insertData
method and wait synchronously or provide a callback method to check
the status of the insertion.
// Then, invoke the History API to insert the data and await the result, which is // possible here because of the {#link AsyncTask}. Always include a timeout when calling // await() to prevent hanging that can occur from the service being shutdown because // of low memory or other conditions. Log.i(TAG, "Inserting the dataset in the History API."); com.google.android.gms.common.api.Status insertStatus
=
Fitness.HistoryApi.insertData(mClient, dataSet)
.await(1, TimeUnit.MINUTES);
// Before querying the data, check to see if the insertion succeeded. if (!insertStatus.isSuccess()) {
Log.i(TAG, "There was a problem inserting the dataset.");
return null; }
// At this point, the data has been inserted and can be read. Log.i(TAG, "Data insert was successful!");
Related
I'm trying to implement Google Fit into my app, but I'm having trouble with the permission to store heart rate bpm datapoints. At first I only tried to insert activity, speed, distance and step rate data and that worked. But as soon as I added the heart rate bpm permission and datapoints I got an error 5000 from the api.
These are the fitness permissions that I request:
FitnessOptions.builder()
.addDataType(DataType.TYPE_ACTIVITY_SEGMENT, FitnessOptions.ACCESS_WRITE)
.addDataType(DataType.TYPE_SPEED, FitnessOptions.ACCESS_WRITE)
.addDataType(DataType.TYPE_DISTANCE_CUMULATIVE, FitnessOptions.ACCESS_WRITE)
.addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE, FitnessOptions.ACCESS_WRITE)
.addDataType(DataType.TYPE_HEART_RATE_BPM, FitnessOptions.ACCESS_WRITE)
.build();
Then when I'm trying to store a DataSet with DataType TYPE_HEART_RATE_BPM using the sessions api I the the error 5000.
I've also tried to completeley remove the permission of my app in the Google Fit app and then add the permission again, but I'm still receiving the error. Is there maybe an additional permission required to store heart rate data? Or is it only allowed to read heart rate data?
I have previously worked on getting the heart rate data using Google fit. Initially, I have faced the same issue. If you go through the documentation in the following link
https://developers.google.com/android/reference/com/google/android/gms/fitness/data/DataType.html#TYPE_HEART_RATE_BPM
It is clearly mentioned that you need to get BODY_SENSORS permission
"Registering to, or subscribing to data of this type requires BODY_SENSORS"
If the user doesn't grant the permission for BODY_SENSORS, then we will be getting the error as we won't be able to access or insert Heart rate data.
You may use the below code to request the user for granting permission
ActivityCompat.requestPermissions(context, new String[]{Manifest.permission.BODY_SENSORS},
BODY_SENSOR_PERMISSION_REQUEST_CODE);
You can check if the user has granted permission in the 'onRequestPermissionsResult' callback and then request for Heart rate data.
Adding sample code as requested.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{android.Manifest.permission.BODY_SENSORS},
BODY_SENSOR_PERMISSION_REQUEST_CODE);
}
private class InsertAndVerifyDataTask extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... params) {
// Create a new dataset and insertion request.
DataSet dataSet = insertHeartData();
// [START insert_dataset]
// Then, invoke the History API to insert the data and await the result, which is
// possible here because of the {#link AsyncTask}. Always include a timeout when calling
// await() to prevent hanging that can occur from the service being shutdown because
// of low memory or other conditions.
com.google.android.gms.common.api.Status insertStatus =
Fitness.HistoryApi.insertData(connectFit.returnClient(), dataSet)
.await(1, TimeUnit.MINUTES);
// Before querying the data, check to see if the insertion succeeded.
if (!insertStatus.isSuccess()) {
return null;
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
Toast.makeText(MainActivity.this, "Added", Toast.LENGTH_SHORT).show();
}
}
private DataSet insertHeartData() {
// [START build_insert_data_request]
try {
Calendar cal = Calendar.getInstance();
Date now = new Date();
cal.setTime(now);
long endTime = cal.getTimeInMillis();
cal.add(Calendar.HOUR_OF_DAY, -1);
long startTime = cal.getTimeInMillis();
// Create a data source
DataSource dataSource = new DataSource.Builder()
.setAppPackageName(this)
.setDataType(DataType.TYPE_HEART_RATE_BPM)
.setStreamName(" - heart count")
.setType(DataSource.TYPE_DERIVED)
.build();
// Create a data set
float hearRate = Float.parseFloat(((EditText) (findViewById(R.id.heartRate))).getText().toString().trim());
DataSet dataSet = DataSet.create(dataSource);
// For each data point, specify a start time, end time, and the data value -- in this case,
// the number of new steps.
DataPoint dataPoint = dataSet.createDataPoint()
.setTimeInterval(startTime, endTime, MILLISECONDS);
dataPoint.getValue(Field.FIELD_BPM).setFloat(hearRate);
dataSet.add(dataPoint);
// [END build_insert_data_request]
return dataSet;
} catch (Exception e) {
return null;
}
}
This worked for me.
I know how to get various data from google Fit like steps or calories when I specifically subscribe to them.
However how can I retrieve all activities that user performed without knowing which ones they did exactly?
Also how can I get values for activities such as Stairs climbing?
It isn't available in DataType class, the samples on google developer website only show steps and calories.
Thanks
I found that I can only find all exercises using the Sessions API.
Crete a request first.
The:
.read(DataType.TYPE_WORKOUT_EXERCISE)
is important here to get all workouts.
private SessionReadRequest readFitnessSession() {
Calendar cal = Calendar.getInstance();
Date now = new Date();
cal.setTime(now);
long endTime = cal.getTimeInMillis();
cal.add(Calendar.WEEK_OF_YEAR, -1);
long startTime = cal.getTimeInMillis();
// Build a session read request
SessionReadRequest readRequest = new SessionReadRequest.Builder()
.setTimeInterval(startTime, endTime, TimeUnit.MILLISECONDS)
.read(DataType.TYPE_WORKOUT_EXERCISE)
.readSessionsFromAllApps()
.build();
// [END build_read_session_request]
return readRequest;
}
And read the sessions data
private void readSessionsApiAllSessions() {
SessionReadRequest readRequest = readFitnessSession();
Fitness.getSessionsClient(getActivity(), GoogleSignIn.getLastSignedInAccount(getActivity()))
.readSession(readRequest)
.addOnSuccessListener(new OnSuccessListener<SessionReadResponse>() {
#Override
public void onSuccess(SessionReadResponse sessionReadResponse) {
// Get a list of the sessions that match the criteria to check the result.
List<Session> sessions = sessionReadResponse.getSessions();
Log.i(TAG, "Session read was successful. Number of returned sessions is: "
+ sessions.size());
for (Session session : sessions) {
// Process the session
dumpSession(session);
// Process the data sets for this session
List<DataSet> dataSets = sessionReadResponse.getDataSet(session);
for (DataSet dataSet : dataSets) {
dumpDataSet(dataSet);
}
}
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.i(TAG, "Failed to read session");
}
});
}
I'm using Google Fit API to develop a fitness app.
I'm trying to use SessionApi with SessionReadRequest to read all the sessions in the past week created with my app. Here is the code I wrote:
/**
* Creates and executes a {#link SessionReadRequest} using {#link
* com.google.android.gms.fitness.SessionsClient} to get last week sessions data
*/
private Task<SessionReadResponse> readLastWeekSessions() {
loader.smoothToShow();
// Begin by creating the query.
SessionReadRequest readRequest = readLastWeekFitnessSessions();
// [START read_session]
// Invoke the Sessions API to fetch the session with the query and wait for the result
// of the read request. Note: Fitness.SessionsApi.readSession() requires the
// ACCESS_FINE_LOCATION permission.
return Fitness.getSessionsClient(getActivity(), GoogleSignIn.getLastSignedInAccount(getContext()))
.readSession(readRequest)
.addOnCompleteListener(new OnCompleteListener<SessionReadResponse>() {
#Override
public void onComplete(#NonNull Task<SessionReadResponse> task) {
Log.i(TAG, "Session read completed");
}
})
.addOnSuccessListener(new OnSuccessListener<SessionReadResponse>() {
#Override
public void onSuccess(SessionReadResponse sessionReadResponse) {
// Get a list of the sessions that match the criteria to check the result.
List<Session> sessions = sessionReadResponse.getSessions();
Log.i(TAG, "Session read was successful. Number of returned sessions is: "
+ sessions.size());
for (Session session : sessions) {
// Process the session
dumpSession(session);
// Process the data sets for this session
List<DataSet> dataSets = sessionReadResponse.getDataSet(session);
for (DataSet dataSet : dataSets) {
dumpDataSet(dataSet);
}
}
loader.smoothToHide();
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.i(TAG, "Failed to read sessions");
}
});
// [END read_session]
}
/**
* Returns a {#link SessionReadRequest} for all the data of the last week sessions
*/
private SessionReadRequest readLastWeekFitnessSessions() {
Log.i(TAG, "Reading History API results for all the last week session");
// [START build_read_session_request]
Calendar cal = Calendar.getInstance();
Date now = new Date();
cal.setTime(now);
long endTime = cal.getTimeInMillis();
cal.add(Calendar.WEEK_OF_YEAR, -1);
long startTime = cal.getTimeInMillis();
// Build a session read request
SessionReadRequest readRequest = new SessionReadRequest.Builder()
.setTimeInterval(startTime, endTime, TimeUnit.MILLISECONDS)
.read(DataType.TYPE_CALORIES_EXPENDED)
.read(DataType.TYPE_DISTANCE_DELTA)
.read(DataType.TYPE_SPEED)
.enableServerQueries()
.build();
// [END build_read_session_request]
return readRequest;
}
It works when I have a small amount of data (only a few sessions). But if I want to read calories burned, total distance and average speed for the last month sessions (which is around 45 sessions I created for testing), it is loading infinitely (I waited more than 10 minutes wich is unacceptable for such a request).
Is it a normal behavior? Am I supposed to destroy regularly the old sessions? Am I supposed the use aggregate data? (which I don't know how to do...)
EDIT: I guess that the problem comes from the fact that I'm getting a huge amount of datapoint for the speed which is not what I want. I want to get the average speed but don't know how to do...
I am attempting to record Google Fit Sessions for periods in sports activity in my Referee Watch. I start a session at the beginning of a period and then stop it at the end of the period.
I sometimes get an interesting exception when I start a Fitness session using this code:
private Session startFitSession(final Game currentGame) {
final Session fitSession;
try {
String sessionBaseName = currentGame.getGameTitle();
if (sessionBaseName.isEmpty()) sessionBaseName = currentGame.getGameLocation();
if (sessionBaseName.isEmpty()) sessionBaseName = RefWatchUtil.timeMsToString(currentGame.getActualStartMs(),RefWatchUtil.dateTimeFormatStart);
final String sessionName = sessionBaseName + ": "
+ String.format(getResources().getString(R.string.fitness_period_label),mCurrentPeriod);
//use this to try to avoid error message about creating a session in the future
final long startTime = System.currentTimeMillis()-TimeUnit.SECONDS.toMillis(10);
fitSession = new Session.Builder()
.setName(sessionName)
.setIdentifier(sessionName)
.setDescription(mCurrentGame.getGameDescription())
.setStartTime(startTime, TimeUnit.MILLISECONDS)
.setActivity(FitnessActivities.RUNNING_JOGGING)
.build();
Fitness.SessionsApi.startSession(mGoogleApiClient, fitSession)
.setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(#NonNull Status status) {
if (status.isSuccess()) {
Log.i(TAG, "Successfully started Session " + sessionName);
} else {
Log.i(TAG, "There was a problem starting the session " + sessionName
+ ": " + status.getStatusMessage());
}
}
});
} catch (RuntimeException e){
Log.i(TAG, "There was a runtime exception starting the session: " + e.getLocalizedMessage());
return null;
}
return fitSession;
}
The exception is:
There was a runtime exception starting the session: Cannot start a session in the future
So it occurred to me that perhaps Session.Builder() looks at the "present" as when the new Builder() call, so I changed to the following code:
...
final long startTime = System.currentTimeMillis();
fitSession = new Session.Builder()
.setName(sessionName)
.setIdentifier(sessionName)
.setDescription(mCurrentGame.getGameDescription())
.setStartTime(startTime, TimeUnit.MILLISECONDS)
...
Same error.
So now I subtract an arbitrary 10 seconds from the startTime and that seems to have fixed the problem.
But (a) is there a better way of making this call (afaict, you can't call setStartTime with a 0 argument to get "current time") and (b) is this a common pattern for Builder type calls?
if i connect my google watch with a mobile device successfully, and then disable the bluetooth connection (for test reasons) and make a google api client call to my mobile device, the pending result always returns the status code success, even if its not successfull because there is no more connection
async task for the request
class DataTask extends AsyncTask<Node, Void, Void> {
#Override
protected Void doInBackground(Node... nodes) {
Gson gson = new Gson();
Request requestObject = new Request();
requestObject.setType(Constants.REQUEST_TYPE);
String jsonString = gson.toJson(requestObject);
PutDataMapRequest dataMap = PutDataMapRequest.create(Constants.PATH_REQUEST);
dataMap.setUrgent();
dataMap.getDataMap().putString(Constants.KEY_REQUEST, jsonString);
PutDataRequest request = dataMap.asPutDataRequest();
DataApi.DataItemResult dataItemResult = Wearable.DataApi
.putDataItem(googleApiClient, request).await();
boolean connected = googleApiClient.isConnected();
PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi.putDataItem(googleApiClient, request);
pendingResult.setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
#Override
public void onResult(#NonNull DataApi.DataItemResult dataItemResult) {
com.google.android.gms.common.api.Status status = dataItemResult.getStatus();
DataItem dataItem = dataItemResult.getDataItem();
boolean dataValid = dataItemResult.getDataItem().isDataValid();
boolean canceled = status.isCanceled();
boolean interrupted = status.isInterrupted();
float statusCode = status.getStatusCode();
if(status.isSuccess()){ // expected to be false because there is no bluetooth connection anymore
Log.d(TAG, "Success");
}else{
Log.d(TAG, "Failure");
}
}
});
return null;
}
}
why do i not get a false for status.isSuccess?
the only solution i found is to write following code inside the AsyncTask:
Wearable.NodeApi.getConnectedNodes(googleApiClient).await().getNodes();
if(connectedNodes.size() == 0){
// no connection
}
is it not possible to check if the request was successfully inside the ResultCallback?
I believe that the getStatus() call for DataItemResult is only indicating whether the call was successfully passed off to the Data API, not whether it was successfully relayed to another node. The Data API is asynchronous - it's a "store and forward" architecture - so it's not reasonable to expect it to notify you immediately of successful delivery.
In fact, I don't think that there is a way to determine from the Data API when your DataItem has been delivered at all. Your getConnectedNodes technique is only telling you that the watch is connected, not that the data has been delivered. If you need proof of delivery, you'll probably have to implement that yourself, perhaps using the Message API.
One other note: given you've wrapped your code in an AsyncTask, there's no need to use PendingResult.setResultCallback. You can simply await the result inline: http://developer.android.com/training/wearables/data-layer/events.html#sync-waiting