OvalMoney / react-native-fitness

A React Native module to interact with Apple Healthkit and Google Fit.
MIT License
345 stars 68 forks source link

getSteps return empty array #60

Open losheredos opened 4 years ago

losheredos commented 4 years ago

As @ibraude mentioned before I still get the empty array. It works on both android & IOS like authorizaiton etc. but when I get results of steps its just empty array. I thought it could be cuz of simulator but I test it in real device now, looks same..

Permissions:

const permissions = [
          {
              kind: Fitness.PermissionKind.Step,
              access: Fitness.PermissionAccess.Read
          },
];

Can anyone help about it?

ibraude commented 4 years ago

Hey,

the docs' usage example is as follows:

import Fitness from '@ovalmoney/react-native-fitness';

const permissions = [
  { kind: Fitness.PermissionKind.Step, access: Fitness.PermissionAccess.Write },
];

Fitness.isAuthorized(permissions)
  .then((authorized) => {
    // Do something
  })
  .catch((error) => {
    // Do something
  });

Notice that the permissions access is Fitness.PermissionAccess.Write while in your example it is Fitness.PermissionAccess.Read. This may be an issue as the device can't write new activity to Google Fit / Healthkit. Try changing the permissions:

const permissions = [
  { kind: Fitness.PermissionKind.Step, access: Fitness.PermissionAccess.Write },
{ kind: Fitness.PermissionKind.Step, access: Fitness.PermissionAccess.Read}
];

If not, can you share more of your code?

Cheers,

losheredos commented 4 years ago

Actually I have changed my code to your example after I shared, I think it did not change but I will check more and let you know.

By the way I thought that this APIs just get data from phone and let us get data through this package but as you mentioned, it only saves data after we let permission to write? Did I understand it right? Also in this case should we let app stay open to be able to write this data?

ibraude commented 4 years ago

No, there is no need to keep the app open. And yes, this package uses the Google Fit or Apple Healthkit APIs behind the scenes based on the platform your React Native app is currently running on.

losheredos commented 4 years ago

Thanks for your answers. After few days of trying actually IOS started to return some response from the API but android is still same. Here is some questions.

1) Does this package should actually work fine in simulators? If yes then we can just debug/test it normally? 2) I checked react-native-google-fit package and there its written that:

  1. In order for the library to work correctly, you'll need following SDK setups: Android Support Repository Android Support Library Google Play services Google Repository Google Play APK Expansion Library

Do they mean some extra package, or these are some default packages normally available in android devices?

Francesco-Voto commented 4 years ago

Hi @losheredos

  1. Yes it works fine in simulator, as long as simulator has and you are using an account with some data on it.

  2. Th simulator should contains Google Play Services in order to make this work property. I don't think the other packages you mentions are necessary

losheredos commented 4 years ago

Still same for android, I mean what I can do I dont know. Here is the full code example which I use:


import Fitness from '@ovalmoney/react-native-fitness';

export async function generateWeeklyAnalysis() {
    let datesArray = [
        {day: 'Pzt', distance: 0},
        {day: 'Sal', distance: 0},
        {day: 'Çar', distance: 0},
        {day: 'Per', distance: 0},
        {day: 'Cum', distance: 0},
        {day: 'Cmt', distance: 0},
        {day: 'Paz', distance: 0}
    ];
    let totalDistance = 0;
    let today = new Date().getDay();

    for (let i = 0; i < today; i++){
        let initialDate = new Date(Date.now() - 86400000*(today-i));
        let startDate = new Date(initialDate.getFullYear(), initialDate.getMonth(), initialDate.getDate(), 0, 0, 0);
        let endDate = new Date(initialDate.getFullYear(), initialDate.getMonth(), initialDate.getDate(), 23, 59, 59);

        let res = await getSteps(startDate, endDate, 'hour');

        let distance = res.length === 0 ? getRandomInt(6) : res;
        datesArray[i].distance = distance;
        totalDistance += distance*1000*2;
    }

    return {
        datesArray,
        totalDistance
    };
}

function getSteps(startDate, endDate, hour) {
    return Fitness.getSteps({startDate, endDate, hour});
}

function getRandomInt(max) {
    let integer = Math.floor(Math.random() * max) + 1;
    let double = (integer*.25);
    return (integer + double).toFixed(2);
}

I tried date with different formats but nothing changes. Am I wrong in something? By the way thanks for your quick answers to help, I appricate it mate.

rlems commented 4 years ago

@losheredos Just a side note: in your function getSteps you're passing a parameter named hour to Fitness.getSteps, this should be named interval.

And this is my implementation of getting the steps of today and the previous 6 days. I find that using momentjs makes working with dates a lot easier.

const getWeekDaysArray = function() {
  const day = moment().startOf('day');

  const arr = [moment(day)];
  for (let i = 0; i <= 5; i++) {
    arr.push(moment(day.subtract(1, 'days')));
  }
  return arr;
};

async function getSteps(startDate, endDate, interval = 'days') {
  return await Fitness.getSteps({ startDate, endDate, interval });
}

async function getStepsForWeek() {
  const daysOfWeek = getWeekDaysArray();
  const startDate = daysOfWeek[daysOfWeek.length - 1].toISOString();
  const endDate = moment(daysOfWeek[0]).endOf('day').toISOString();

  const res = await getSteps(startDate, endDate);
  const newSteps = daysOfWeek.map(date => {
    const resultForDayOfWeek = res.find(resultDay => {
      const resultDate = moment(resultDay.startDate).startOf('day');
      return resultDate.valueOf() === date.startOf('day').valueOf();
    });
    return {
      date,
      quantity: resultForDayOfWeek ? resultForDayOfWeek.quantity : 0,
    };
  });
  return newSteps;
}
losheredos commented 4 years ago

@rlems I think you read it wrong maybe, because its written in documentation

Set interval to decide how detailed the returned data is, set it to hour or minute otherwise it defaults to days.

If I'm not wrong.

rlems commented 4 years ago

@losheredos You've got these 2 parts in your code:

let res = await getSteps(startDate, endDate, 'hour');

function getSteps(startDate, endDate, hour) {
    return Fitness.getSteps({startDate, endDate, hour});
}

This object is wrong: {startDate, endDate, hour} because of the hour param. Should be {startDate, endDate, interval: hour}

The docs give this example: Fitness.getSteps({ startDate: string, endDate: string, interval: string })

interval parameter is of type string ('hour' or 'days')

losheredos commented 4 years ago

Ah yeah got it, right. But anyways I added this interval option later to check if it will make difference in results. It didn't make any difference. I will even check again and write here.

Edit: Yeah checked again and results are same.

marlene89 commented 4 years ago

Having the same issue. Getting empty array on emulator and real device. here is my implementation.

    let currentDate = new Date();
    let end = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate(), 23, 59, 59);
    let start = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate(), 0, 0, 0);
    let formattedStartDate = start.toString();
    let formattedEndDate = end.toString();
    let stepsResult = await Fitness.getSteps({
      startDate: formattedStartDate,
      endDate: formattedEndDate,
      interval: 'days',
    });

Am I missing something? Shouldn't it at least return 0 if no steps were recorded for the day?

Francesco-Voto commented 4 years ago

Hi @marlene89, if no steps were recorded for the day, no data will be returned.

losheredos commented 4 years ago

So there was no data then I decided to try react-native-google-fit . There I could at least get array with source data and steps arrays(which was empty as well). But noticed that in that package there is method to start recording. After I started that method data was recorded and I could get steps of the same day. So maybe in this package there should be a method to record (or it does automatically and there is issue about it?) for android side of it.

You may try the same steps as I did and check if you will be able to get something more @marlene89

rlems commented 4 years ago

@losheredos There is a method to subscribe to steps: Fitness.subscribeToSteps()

Check the documentation for more info.

losheredos commented 4 years ago

@rlems yeah thanks for warning I missed it, but if this was the case for this issue probably this method could be in top section of methods..

marlene89 commented 4 years ago

@losheredos will def try your suggestion. I'll post results when I have them. thanks :)

ibraude commented 4 years ago

Hey, thought this might be related. So to be able to record steps\activity on Google Fit you must call the Fitness.subscribeToSteps() or Fitness.subscribeToActivity().

if these methods return false (failed to subscribe) it probably means the app doesn't have the required permissions, because as of Android 10 I think, physical activity is defined as a sensitive permission and requires Android permissions to be granted. (see https://developers.google.com/fit/android/authorization#requesting_android_permissions )

To solve this, use react-native-permissions to request for the ACTIVITY_RECOGNITION permission.

It can be used like this:

import {check, request, PERMISSIONS, RESULTS} from 'react-native-permissions';
import Fitness from '@ovalmoney/react-native-fitness';

const requestActivityPermission = async () => {
    try {
       const result = await check(PERMISSIONS.ANDROID.ACTIVITY_RECOGNITION)
                switch (result) {
                    case RESULTS.UNAVAILABLE:
                        console.log(
                            'This feature is not available (on this device / in this context)',
                        );
                        break;
                    case RESULTS.DENIED:
                        console.log(
                            'The permission has not been requested / is denied but requestable',
                        );
                        await request(PERMISSIONS.ANDROID.ACTIVITY_RECOGNITION)
                        break;
                    case RESULTS.GRANTED:
                        console.log('The permission is granted');
                        break;
                    case RESULTS.BLOCKED:
                        console.log('The permission is denied and not requestable anymore');
                        break;
                }
    } catch (err) {
        console.warn(err);
    }
};

// wherever you subscribe to steps or activity, add requestActivityPermission() if the method returns false
   const handleSubscribe = async () => {
        try{
            const subscribedToSteps = await  Fitness.subscribeToSteps()
                    if(subscribedToSteps){
                        console.log("subscribed to step counter")
                    } else {
                       await requestActivityPermission()
                    }
        } catch (e) {
            console.log(e)
        }
    }

I'll try and open a PR to add this functionality.

Hope this helps in the meantime

StarksJohn commented 4 years ago

@ibraude , Hello, I used the Fitness.subscribeToSteps() method when the app started,and I agree to step authorization 。 Then I called the Fitness.isAuthorized(permissions) method ,it returns false, then I called Fitness.requestPermissions(permissions) method ,but still return false , please help me ,thanks

ibraude commented 4 years ago

Hey, Did you set up an OAuth 2.0 Client ID with the Google API console? See how to do that here: https://developers.google.com/fit/android/get-api-key

StarksJohn commented 4 years ago

Haha, it's my problem. The debug package I used for testing but it should be the release package. Now I use the Fitness.subscribeToActivity() method to determine whether it is authorized, and then use the react-native-permissions library to request the two permissions of ACTIVITY_RECOGNITION and ACCESS_FINE_LOCATION. Now I have the user’s Steps, Calories and Distance, thank you

GuleriaAshish commented 4 years ago

@cham1985 can you share your code , because i am still getting the empty array.

PaLaMuNDeR commented 4 years ago

Any progress on this @GuleriaAshish ? @cham1985 could you share your setup? For me it also doesn't work on Android. For some reason it shows only the first screen to choose your account for the permissions, but not the actual permissions questions. I gave more details in this thread https://github.com/OvalMoney/react-native-fitness/issues/48#issuecomment-688761194

ipsips commented 3 years ago

Ugh, never mind right after posting this it started working :D Seems like there's quite a delay in data syncing

I'm having the same unexpected result: getSteps() yields an empty array. I'm trying to fetch tracked steps on Android Emulator without having Google Fit app installed. I am leveraging subscribeToSteps() method as documentation suggests. I am certain that I have some steps tracked in given period because I can see them on Google Fit app that I have installed on my physical phone and I can confirm that permissions are successfully granted since my emulated app appears in Google Fit settings under "Third-party apps with account access".

I could swear it did work when I last worked on the project couple a weeks ago.. Did Google change something on their end? @Francesco-Voto

This is my control flow (simplified):

async function myFitness() {
  const permissions = [
    {
      kind: Fitness.PermissionKinds.Steps,
      access: Fitness.PermissionAccesses.Read,
    },
  ];
  const period = {
    startDate: '2020-12-29T22:00:00.000Z',
    endDate: '2021-01-05T21:59:59.999Z',
    interval: 'days'
  };

  let isAuthorized = await Fitness.isAuthorized(permissions);
  if (!isAuthorized) {
    isAuthorized = await Fitness.requestPermissions(permissions);
    if (!isAuthorized) {
      return;
    }
  }
  isAuthorized = await Fitness.subscribeToSteps();
  if (!isAuthorized) {
    return;
  }
  const trackedSteps = await Fitness.getSteps(period);

  console.log(trackedSteps); // logs empty array
}
juliomdsneto commented 3 years ago

Hey, thought this might be related. So to be able to record steps\activity on Google Fit you must call the Fitness.subscribeToSteps() or Fitness.subscribeToActivity().

if these methods return false (failed to subscribe) it probably means the app doesn't have the required permissions, because as of Android 10 I think, physical activity is defined as a sensitive permission and requires Android permissions to be granted. (see https://developers.google.com/fit/android/authorization#requesting_android_permissions )

To solve this, use react-native-permissions to request for the ACTIVITY_RECOGNITION permission.

It can be used like this:

import {check, request, PERMISSIONS, RESULTS} from 'react-native-permissions';
import Fitness from '@ovalmoney/react-native-fitness';

const requestActivityPermission = async () => {
    try {
       const result = await check(PERMISSIONS.ANDROID.ACTIVITY_RECOGNITION)
                switch (result) {
                    case RESULTS.UNAVAILABLE:
                        console.log(
                            'This feature is not available (on this device / in this context)',
                        );
                        break;
                    case RESULTS.DENIED:
                        console.log(
                            'The permission has not been requested / is denied but requestable',
                        );
                        await request(PERMISSIONS.ANDROID.ACTIVITY_RECOGNITION)
                        break;
                    case RESULTS.GRANTED:
                        console.log('The permission is granted');
                        break;
                    case RESULTS.BLOCKED:
                        console.log('The permission is denied and not requestable anymore');
                        break;
                }
    } catch (err) {
        console.warn(err);
    }
};

// wherever you subscribe to steps or activity, add requestActivityPermission() if the method returns false
   const handleSubscribe = async () => {
        try{
            const subscribedToSteps = await  Fitness.subscribeToSteps()
                    if(subscribedToSteps){
                        console.log("subscribed to step counter")
                    } else {
                       await requestActivityPermission()
                    }
        } catch (e) {
            console.log(e)
        }
    }

I'll try and open a PR to add this functionality.

Hope this helps in the meantime

but, if i get true from these methods I should be able to get some data, right? But i still get empty array

here is my code

[...]

`export const StepCountPage = () => {

 const permission = [
        { kind: 0, access: 0 },
        { kind: 2, access: 0 },
        { kind: 0, access: 1 },
        { kind: 1, access: 0 },
        { kind: 1, access: 1 },
        { kind: 4, access: 0 },
        { kind: 4, access: 1 },
        { kind: 2, access: 1 }
    ];
    const period = {
        startDate: '2017-07-17T00:00:17.971Z',
        endDate: '2021-01-05T21:59:59.999Z',
        Interval: 'day'
    };
    useEffect(() => {
        console.log("permissions: ", permission)
        Fitness.requestPermissions(permission)
            .then((authorized) => {
                console.log("status? ", authorized)
                if (authorized) {
                    howManySteps();
                } else {
                    Fitness.requestPermissions(permission).
                        then((authorized) => {
                            howManySteps();
                            console.log("status?", authorized)
                        })
                        .catch((error) => {
                            console.log("denied", error)
                        })
                }
            })
            .catch((error) => {
                // Do something
            });
    }, []);
    const howManySteps = async () => {
        const subSteps = await Fitness.subscribeToSteps()
      console.log("subscribeToSteps:", subSteps)
        const steps = await Fitness.getSteps(period)
        console.log("steps: ", steps)
    };

[...]

}`

here is my output:

LOG permissions: [{"access": 0, "kind": 0}, {"access": 0, "kind": 2}, {"access": 1, "kind": 0}, {"access": 0, "kind": 1}, {"access": 1, "kind": 1}, {"access": 0, "kind": 4}, {"access": 1, "kind": 4}, {"access": 1, "kind": 2}] LOG status? true LOG subscribeToSteps: true LOG steps: []