I have successfully uploaded my custom data to google fit server. using following code.
com.google.android.gms.common.api.Status insertStatus =
Fitness.HistoryApi.insertData(mClient, dataSet)
.await(1, TimeUnit.MINUTES);
if (!insertStatus.isSuccess()) {
Log.i(TAG, "There was a problem inserting the dataset.");
return null;
}
Log.i(TAG, "Data insert was successful!");
I found that also I can read custom data value within my app, but I am not getting it. Following is the code to retrieve it.
final PendingResult<DataTypeResult> pendingResult = Fitness.ConfigApi.readDataType(
mClient, "com.fitnessapi.custom_data_type");
pendingResult.setResultCallback(
new ResultCallback<DataTypeResult>() {
#Override
public void onResult(DataTypeResult dataTypeRes) {
DataSet dataSet = DataSet.create(dataSource);
List<DataPoint> points= dataSet.getDataPoints();
for(int i=0; i<points.size();i++)
{
//Never getting values
}
}
});
}
The readDataType method will only return a dataType by name.
docs
Once it does you have to set a dataType variable to the results for use.
DataType dataTypeFromResult = dataTypeRes.getDataType();
Then pass it to what you need.
DataReadRequest readRequest = new DataReadRequest.Builder()
.read(dataTypeFromResult)
.setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS)
.build();
Related
I have just started to explore WorkManager in my app. My app will mostly be offline, so all the data is stored locally using room db. As soon as the device gets connected to a network I want to sync local data to server and then get the latest data and sync local db again. This is my doWork() method implementation -
#NonNull
#Override
public Result doWork() {
Worker.Result[] result = {Worker.Result.retry()};
count = new CountDownLatch(1);
Context context = getApplicationContext();
try {
new NetworkHelper.NetworkBuilder(context)
.setRequestMethod(NetworkHelper.NetworkBuilder.RequestMethod.GET)
.setTag(NetworkHelper.NetworkBuilder.TAG.FETCH)
.setResponseListener((response, requestMethod, isError) -> {
Utils.printError("onResponse " + isError);
if (!isError) {
clearDataAndInsert(String.valueOf(response));
}
})
.build().callFetchData();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
try {
count.await();
} catch (InterruptedException e) {
Utils.printDebug(e.getMessage());
}
Utils.printError(result[0].toString());
return result[0];
}
in clearDataAndInsert() I am inserting the data that was fetched from server to local db using room and for this, I built my own callback listeners and checking if all my data is successfully inserted in the db using atmoic integer like this -
#Override
public void onTaskComplete() {
int remaining = task.decrementAndGet();
if (remaining == 0) {
Data source = new Data.Builder()
.putString("workInfo", "completed")
.build();
result[0] = Worker.Result.success(source);
count.countDown();
}
}
This is how I am enqueing the data -
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresDeviceIdle(false)
.build();
Data source = new Data.Builder()
.putString("workType", "OneTime")
.build();
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(SyncWorker.class)
.setConstraints(constraints)
.setInputData(source)
.build();
WorkManager.getInstance(context).enqueue(request);
As you can see I am using CountDownLatch to wait for my fetch and insert in local db process to finish and then return success result from doWork(). But my problem is, my doWork gets called multiple times, I am guessing that's because result is returning as retry? But I cannot figure out why?
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");
}
});
}
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!");
I have a following code I am using to retrieve a list of user's activities from Google Fit:
public void getActivitiesData(Date from, Date till) {
DataReadRequest readRequest = new DataReadRequest.Builder()
.aggregate(DataType.TYPE_ACTIVITY_SEGMENT, DataType.AGGREGATE_ACTIVITY_SUMMARY)
.bucketByTime(1, TimeUnit.DAYS)
.setTimeRange(from.getTime(), till.getTime(), TimeUnit.MILLISECONDS)
.build();
Fitness.HistoryApi.readData(apiClient, readRequest).setResultCallback(new com.google.android.gms.common.api.ResultCallback<DataReadResult>() {
#Override
public void onResult(DataReadResult dataReadResult) {
Status status = dataReadResult.getStatus();
if (status.isSuccess()) {
for (Bucket bucket : dataReadResult.getBuckets()) {
if (!bucket.getDataSets().isEmpty()) {
DataSet dataSet = bucket.getDataSets().get(0);
String sourceAppPackageName = getSourceAppPackageNameFromDataSet(dataSet);
for (DataPoint dp : dataSet.getDataPoints()) {
for (Field field : dp.getDataType().getFields()) {
String fieldName = field.getName();
if (fieldName != null && fieldName.equals("activity")) {
String type = FitnessActivities.getValue(dp);
Date from = new Date(dp.getStartTime(TimeUnit.MILLISECONDS));
Date till = new Date(dp.getEndTime(TimeUnit.MILLISECONDS));
// store retrieved values to the data object, omitted
}
}
}
}
}
}
}
});
}
private static String getSourceAppPackageNameFromDataSet(DataSet dataSet) {
String result = null;
if (dataSet.getDataSource() != null) {
result = dataSet.getDataSource().getAppPackageName();
}
return result;
}
To insert activities into Google Fit, I've used the Google Fit app and Runkeeper (right now, these apps seem to be only ones that are integrated with Fit).
My code retrieves these activities as expected, however, for each activity, my getSourceAppPackageNameFromDataSet() method returns "com.google.android.gms" as a package name. As per Data Attribution section in Google Fit documentation, I would expect the method to return a package name of either Runkeeper or Google Fit, but this does not happen.
Am I doing something horribly wrong, or is this a bug in Google Fit?
DataPoint.getOriginalDataSource().getAppPackageName() will do the trick. It returns com.withings.wiscale2 for my Withings scale, while DataSet.getDataSource().getAppPackageName()always returns com.google.android.gms.
There's a similar question right here: DataSource.getAppPackageName() always returns "com.google.android.gms" in Google Fit