Google fit SessionAPI: SessionReadRequest is taking infinite time with no result - android

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...

Related

How to rate-limit the API requests to x per second?

My app is making repeat API requests to download chunks of data. Unfortunately the server limits the API requests to 3 per second. In the code below, how can I rate limit the requests to X per second ?
private void getHistoricalPrices(String currency, String start_date, String end_date, int granularity){
// show download status
currentDlPageIndex++;
view.showDownloadingStatus(currentDlPageIndex, totalDlPages);
// make the API call
addDisposable(
model.isNetworkAvailable()
.doOnSuccess(isNetworkAvailable -> {
if (!isNetworkAvailable) {
showErrorMessage();
Timber.v("no internet");
}
})
.filter(isNetworkAvailable -> true)
.flatMapSingle(isNetworkAvailable -> model.getHistoricalPrices(currency, start_date, end_date, String.valueOf(granularity)))
.subscribeOn(rxSchedulers.io())
.observeOn(rxSchedulers.mainThread())
.subscribe((Response<List<List<String>>> responseData) -> {
if (responseData != null && responseData.code() == HTTP_OK) {
List<List<String>> response = responseData.body();
if (response != null) {
// create list from downloaded data
ArrayList<HistoricPrice> tmpHistoricPriceList = new ArrayList<>(response.size());
for (List<String> rawHistoricPrice : response) {
HistoricPrice historicPrice = new HistoricPrice(rawHistoricPrice.get(0), rawHistoricPrice.get(1), rawHistoricPrice.get(2), rawHistoricPrice.get(3), rawHistoricPrice.get(4), rawHistoricPrice.get(5));
tmpHistoricPriceList.add(0, historicPrice);
}
// add the downloaded list to the main list being recreated
model.historicPriceList.addAll(tmpHistoricPriceList);
Timber.d("added %d records to memory", response.size());
// if there's more to download, download another chunk
if (intermediateDateSecs != null && intermediateDateSecs < endDateSecs){
startDateSecs = tmpHistoricPriceList.get(tmpHistoricPriceList.size()-1).time + granularity;// add "granularity" to startDateSecs to avoid getting two exact data for the same time
Date startDate = new Date(startDateSecs * 1000L);//requires milliseconds, not epoch
String startStrDate = DateUtils.fromDateToString(startDate);
intermediateDateSecs = startDateSecs + ((ApiService.MAX_HISTORIC_RETURN_VALUES - 1) * granularity);
if (intermediateDateSecs > endDateSecs) intermediateDateSecs = endDateSecs;
Date intermediateDate = new Date(intermediateDateSecs * 1000L);
String intermediateStrDate = DateUtils.fromDateToString(intermediateDate);
getHistoricalPrices(currency, startStrDate, intermediateStrDate, granularity);
} else {
// no more to download, save data
Timber.d("downloaded total of %d records", model.historicPriceList.size());
view.hideDownloadingStatus();
showSaveDataMessage();
}
}
}
}, error -> {
showErrorMessage();
})
);
}
you can see that the method getHistoricalPrices() calls itself to continue downloading. This implementation works well, except for the server complaining when there's too many API requests per second.
You can perform your request every X milliseconds / seconds like so:
long interval = 0L; // Your desired interval here in milliseconds
Observable.interval(0, interval, TimeUnit.MILLISECONDS)
.takeUntil(x -> isTaskComplete == true)
// Other configuration calls go here
.subscribe(); // make request here or in doOnNext()

Google Fit API: Store heart rate bmp datapoints

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.

How to get all google fit activities performed for a day

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");
}
});
}

Google Fit: save medical data

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!");

How to get steps same as googlefit app steps using googlefit history api?

With the following code, i could get the response but with a empty dataset. where as in googlefit app, steps are shown. how to get exact steps shown in googlefit app through history api? i tried for weight, calories.. every time, response is coming as a empty dataset! I have wasted last 3 days on this.
DataSource ESTIMATED_STEP_DELTAS = new DataSource.Builder()
.setDataType(DataType.TYPE_STEP_COUNT_DELTA)
.setType(DataSource.TYPE_DERIVED)
.setStreamName("estimated_steps")
.setAppPackageName("com.google.android.gms")
.build();
DataReadRequest readRequest = new DataReadRequest.Builder()
.aggregate(DataType.TYPE_STEP_COUNT_DELTA,DataType.AGGREGATE_STEP_COUNT_DELTA)
.bucketByTime(1, TimeUnit.DAYS)
.setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS)
.build();
PendingResult<DataReadResult> pendingResult = Fitness.HistoryApi.readData(mClient, readRequest);
pendingResult.setResultCallback(new ResultCallback<DataReadResult>(){
#Override
public void onResult(#NonNull DataReadResult dataReadResult) {
parseData(dataReadResult);
}
});
private static void parseData(DataReadResult dataReadResult) {
if (dataReadResult.getBuckets().size() > 0) {
Log.i(TAG,
"Number of returned buckets of DataSets is: " + dataReadResult.getBuckets().size());
for (Bucket bucket : dataReadResult.getBuckets()) {
List<DataSet> dataSets = bucket.getDataSets();
for (DataSet dataSet : dataSets) {
dumpDataSet(dataSet);
}
}
} else if (dataReadResult.getDataSets().size() > 0) {
Log.i(TAG, "Number of returned DataSets is: " + dataReadResult.getDataSets().size());
for (DataSet dataSet : dataReadResult.getDataSets()) {
dumpDataSet(dataSet);
}
}
}
Related SO thread.
Before you retrieve data using History API you have to record it first by using the Recording API:
Your app can record the user's step count by using the Recording API to create a subscription to the DataType.TYPE_STEP_COUNT_CUMULATIVE data type, as shown in the following example:
public void subscribe() {
// To create a subscription, invoke the Recording API. As soon as the subscription is
// active, fitness data will start recording.
Fitness.RecordingApi.subscribe(mClient, DataType.TYPE_STEP_COUNT_CUMULATIVE)
.setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
if (status.isSuccess()) {
if (status.getStatusCode()
== FitnessStatusCodes.SUCCESS_ALREADY_SUBSCRIBED) {
Log.i(TAG, "Existing subscription for activity detected.");
} else {
Log.i(TAG, "Successfully subscribed!");
}
} else {
Log.w(TAG, "There was a problem subscribing.");
}
}
});
}
Google Fit stores the step data from the subscription even if the app
is not running, and restores the subscription when the system
restarts. Your app can read the daily step total from the user's
fitness history.
This exact same question was addressed in the Google Fit FAQs which
is found
here.
The Fit app uses a specific data source for steps. It adds the
following functionality on top of the default merged steps stream:
Steps recorded during biking, driving, and some other non-moving
activities are removed. When an activity that involves steps (such as
walking or running) doesn't have a reasonable number of steps
associated with it, steps for the activity are estimated. You can
access the "estimated" steps stream as shown here:
Via the REST API:
derived:com.google.step_count.delta:com.google.android.gms:estimated_step>s Via the Android API:
new DataSource.Builder()
.setAppPackageName(“com.google.android.gms”)
.setDataType(DataType.TYPE_STEP_COUNT_DELTA)
.setType(DataSource.TYPE_DERIVED)
.setStreamName("estimated_steps")
.build();
Note that even when using the right data source, your step count may
still be different from that of the Google Fit app. This could be due
to one of the following reasons:
On Wear, the Fit MicroApp when connected will display step counts
queried on the phone and transferred over via the Wearable APIs. Other
MicroApps accessing local-only data will only get watch steps. We are
working on making this easier for developers. Sometimes the step
calculation code for the Google Fit app is updated with bug fixes
before we are able to release the fixes to developers (which requires
a Google Play Services release). We are also working on making it
possible for developers to access fixes at the same time.
My way: case DataStepCounterRequest:
final DataSource ds = new DataSource.Builder()
.setAppPackageName("com.google.android.gms")
.setDataType(DataType.TYPE_STEP_COUNT_DELTA)
.setType(DataSource.TYPE_DERIVED)
.setStreamName("estimated_steps")
.build();
return new DataReadRequest.Builder()
.aggregate(ds, DataType.AGGREGATE_STEP_COUNT_DELTA)
.bucketByTime(1, TimeUnit.DAYS)
.setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS)
.enableServerQueries()
.build();

Categories

Resources