matinzd / react-native-health-connect

React native library for health connect (Android only)
http://matinzd.github.io/react-native-health-connect
MIT License
203 stars 43 forks source link

requestPermission on Android 14 #50

Closed LeonNissen closed 6 months ago

LeonNissen commented 11 months ago

Describe the bug Calling requestPermission will lead into an error: "No Activity found to handle Intent { act=androidx.activity.result.contract.action.REQUEST_PERMISSIONS (has extras) }

However, on Android 13 the issue does not exist.

To Reproduce When trying to request permissions the application crashes with the error (see above)

Expected behavior HealthConnect shows the permission selection (Same behavior as on Android 13).

Screenshots

Environment:

matinzd commented 10 months ago

Please read the documentation for setting up the project. You may have missed setting up your AndroidManifest.xml configurations.

LeonNissen commented 10 months ago

"Thank you for your response! I've double-checked the configuration in AndroidManifest.xml, but the issue persists. For reference, I initiated a brand-new expo project and strictly followed the Get started guide.

Here is my AndroidManifest.xml for further reference:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  <uses-permission android:name="android.permission.INTERNET"/>
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
  <uses-permission android:name="android.permission.VIBRATE"/>
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  <uses-permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED"/>
  <queries>
    <intent>
      <action android:name="android.intent.action.VIEW"/>
      <category android:name="android.intent.category.BROWSABLE"/>
      <data android:scheme="https"/>
    </intent>
  </queries>
  <application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme">
    <meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
    <meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="49.0.0"/>
    <meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
    <meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
    <activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
      <intent-filter>
        <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
      </intent-filter>
      <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:scheme="com.test.hctest"/>
      </intent-filter>
    </activity>
    <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false"/>
  </application>
</manifest>
matinzd commented 10 months ago

Is this just happening on Android 14?

LeonNissen commented 10 months ago

Yes the issue occurs on Android 14 only. I've tested it with the emulator (API 34) and a physical device (Pixel 6 Pro). Android 13 (with the HealthConnect app) does work fine.

matinzd commented 10 months ago

Try submitting the issue in Google's Issue Tracker and see if they have answer for it:

https://issuetracker.google.com/u/1/issues?q=status:open%20componentid:1232764&s=created_time:desc

LeonNissen commented 10 months ago

FYI: https://issuetracker.google.com/u/1/issues/307049403

samwyness-district commented 10 months ago

Same issue for me when I run on Android 14

Found the migration plan here, hopefully this may help. https://developer.android.com/health-and-fitness/guides/health-connect/migrate/migrate-from-android-13-to-14

PopJoestar commented 10 months ago

Hello, I managed to fix it by adjusting the Intent action for Android 14 on the native side. In HealthConnectManager.kt, in the requestPermission function, replace the intent declaration with this

val intent = if(Build.Version.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
    Intent("android.health.connect.action.MANAGE_HEALTH_PERMISSIONS")
} else {
   HCPermissionManager(providerPackageName).healthPermissionContract.createIntent(applicationContext, latestPermissions!!)
}

Also, add this to your AndroidManifest.xml

<activity-alias>
      android:name="AndroidURationaleActivity"
      android:exported="true"
      android:targetActivity=".nameOfYourActivity"
      android:permission="android.permission.START_VIEW_PERMISSION_USAGE">
      <intent-filter>
        <action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
        <category android:name="android.intent.category.HEALTH_PERMISSIONS" />
      </intent-filter>
</activity-alias>

I am pretty sure, It still misses something but I am not very familiar with native Android development.

LeonNissen commented 10 months ago

@matinzd What do you think about the solution of @PopJoestar? Is it suitable for a PR?

matinzd commented 10 months ago

Hi

I reviewed the code, and it appears that this issue should not occur. Creating an intent should ideally be managed through the Google Health Connect.

I recommend following the document here and checking if you encounter any errors after making these modifications, without altering the underlying structure: https://developer.android.com/health-and-fitness/guides/health-connect/migrate/migrate-from-android-13-to-14#testing-apis-with-sdk

@LeonNissen

antontanderup commented 10 months ago

We're hit by the same issue. I've tried this solution and while it does prevent the crash, it simply opens Health Connect for us, and does not request permission. While the issue should not occur, it definitely does occur here.

fredefl commented 10 months ago

I've tried the same fix as @antontanderup — it does prevent the phone from crashing, but we don't get to actually authenticate with Home Connect.

The library works great on Android 13, but we get that crash every time on Android 14.

LeonNissen commented 10 months ago

Hi @antontanderup and @fredefl,

I was able to implement the solution suggested by @PopJoestar. Yes you were right, when using the Intent android.health.connect.action.MANAGE_HEALTH_PERMISSIONS the HealthConnect app will present all apps and their permissions. However, a hot fix for this would be to just select the app in this menu, allow the specific (or all) permissions and return to the app. Thereafter the app is able to access HealthConnect's data.

However, I think we would need a different intent but I'm unfortunately no android developer. Additionally, I agree with @matinzd's answer:

I reviewed the code, and it appears that this issue should not occur. Creating an intent should ideally be managed through the Google Health Connect.


https://github.com/matinzd/react-native-health-connect/assets/50104433/55b0e9b8-a4f3-4300-bd88-19d1b0363d99

matinzd commented 10 months ago

I'm holding off on updating this library until Google releases a stable version. We'll wait for that to ensure it's reliable. In the meantime, you're welcome to fork the project and customize it, or send a pull request.

chganesh commented 9 months ago

I got the same crash issue on android 14. if you any solution for this crash issue. please help me on this thanks in advance

kibagateaux commented 9 months ago

Came across this fix for a flutter plugin today. Dont have time to dig into this issue right now but I will circle back if its not resolved.

https://github.com/cph-cachet/flutter-plugins/issues/800

I believe this is the specific fix https://github.com/cph-cachet/flutter-plugins/pull/834/files#diff-f176f1231d82c07449b7bef3ffef63652c4704978f2ae846cac7259ff43a79ffR82-R91

Waiting for google to be stable before modifying is a good idea. Could also be forever haha

seanblonien commented 8 months ago

@LeonNissen it looks like you have a minimal expo android project/repo with the temporary fix that you implemented, do you mind sharing that minimal reproduction with us so we can see exactly how you implemented the fix?

LeonNissen commented 8 months ago

@seanblonien sure thing! I've pushed it into a new repo: https://github.com/LeonNissen/test-hc/tree/main

Most important, you need to follow @PopJoestar's answer: https://github.com/matinzd/react-native-health-connect/issues/50#issuecomment-1782411647

The actual fix for react-native-health-connect can be found here: https://github.com/LeonNissen/test-hc/blob/587d780301b7cade3b496cabb396d516f82fe2c7/node_modules/react-native-health-connect/android/src/main/java/dev/matinzd/healthconnect/HealthConnectManager.kt#L79

seanblonien commented 8 months ago

@LeonNissen Ah perfect, thank you for sharing, it helps a lot!

seanblonien commented 8 months ago

EDIT: nevermind it doesn't seem to work with health permissions 😢

I also discovered a JS-only workaround without changing internal package implementations (cause I had no idea how to persist node_modules changes for my CI environment) -- use the react-native PermissionsAndroid module to get the permissions yourself, like so:

import {PermissionsAndroid} from 'react-native';
import {initialize, readRecords} from 'react-native-health-connect';

// ...
const isInitialized = await initialize(); // this is still needed for readRecords to work
if (!isInitialized) {
  // handle
}
const permissionStatus = await PermissionsAndroid.request('android.permission.health.READ_STEPS' as Permission, {
  title: 'Steps Permission',
  message: 'App requires access to your step data for XYZ feature',
  buttonNegative: 'Cancel',
  buttonPositive: 'OK',
});

const isGrantedStepsPermission = permissionStatus === PermissionsAndroid.RESULTS.GRANTED;

if(!isGrantedStepsPermission) {
  // handle
}

// use the `readRecords` API like normal
const stepsRecords = await readRecords('Steps', {/* ... */});

react-native documentation

That documentation is very misleading tho, because it doesn't enumerate the health permissions, but it worked for me when I just encoded the permission string myself. <-- this is untrue, it doesn't work if it is not enumerated

To be honest, seeing the built-in native PermissionsAndroid modules makes me question why this package exposes a requestPermission function at all, if there is already a way to do this via react-native, seems like suggesting the use of this module would be best and less maintenance for this package. Curious if anyone else was doing this workaround or if they have more context into why this library has a custom requestPermission over the built-in react-native one

matinzd commented 8 months ago

If you want to make any small changes to your packages, use patch-package to patch your modules instead of commiting your node_modules folder.

chganesh commented 8 months ago

@matinzd I did @LeonNissen changes in node modules and created patch package after that i am getting issue for Permission Denial: starting Intent { act=android.health.connect.action.REQUEST_HEALTH_PERMISSIONS cmp=com.google.android.healthconnect.controller/com.android.healthconnect.controller.permissions.request.PermissionsActivity } requires android.permission.GRANT_RUNTIME_PERMISSIONS

Please help on this Thanks in advance

qtdinh commented 8 months ago

I'm also having the same issue that @chganesh does. I don't know what the problem is, I simply tried to run the repo @LeonNissen put up.

EDIT: Works on my other computer. Still would love to know how to fix the problem.

LeonNissen commented 8 months ago

Hi @chganesh and @qtdinh,

I'm sorry I haven't tested the code before committing...

I committed a new version with the intent android.health.connect.action.MANAGE_HEALTH_PERMISSIONS. It works now 🚀

Thanks @matinzd for the patch-package heads up, I will look into that!

sangle-zotek8 commented 8 months ago

Hello @LeonNissen , I edited according to you but I got an error yarn run v1.22.19 $ react-native run-android info Starting JS server... info Installing the app...

Task :react-native-health-connect:compileDebugKotlin FAILED 45 actionable tasks: 2 executed, 43 up-to-date

info 💡 Tip: Make sure that you have set up your development environment correctly, by running react-native doctor. To read more about doctor command visit: https://github.com/react-native-community/cli/blob/main/packages/cli-doctor/README.md#doctor

e: file:///E:/AppHealthConnect/node_modules/react-native-health-connect/android/src/main/java/dev/matinzd/healthconnect/HealthConnectManager.kt:85:23 Unresolved reference: Build e: file:///E:/AppHealthConnect/node_modules/react-native-health-connect/android/src/main/java/dev/matinzd/healthconnect/HealthConnectManager.kt:85:48 Unresolved reference: Build

FAILURE: Build failed with an exception.

BUILD FAILED in 5s error Failed to install the app. info Run CLI with --verbose flag for more details. error Command failed with exit code 1. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

LeonNissen commented 8 months ago

Hi @sangle-zotek8!

Have you imported Build? (add import android.os.Build at the top of HealthConnectManager.kt)

LimAlbert commented 7 months ago

Hello !

I have had the same issue for some months and since nothing has been done yet, I've looked into it. I have managed to make it work on Android 14 and from what I've seen, it seems that Android 14 doesn't like us using startActivityForResult to ask for permissions and wants registerForActivityResult instead. And on top of this, there's an issue with react-native that prevents registerForActivityResult from calling its callback ! (https://github.com/facebook/react-native/issues/42403)

Here's a project I made where you can find a working example: https://github.com/LimAlbert/react-native-health-connect-android14

The code is far from perfect since I'm not really a native developper but at least it works (I haven't tested everything though)

matinzd commented 7 months ago

It seems you are right @LimAlbert. The main issue when I was implementing this library was the RN limitation regarding the new way of handling activity results and permission contracts. The patch you provided seems ok, but it needs some more work because we are calling setProvider inside the MainActivity.kt which is not ideal.

@cortinico Are there any roadmaps/plan to support Androidx Activity Result APIs inside native modules?

matinzd commented 7 months ago

I started investigating this. @LimAlbert You are right. There is a problem with RN core. Do you want to send a pull request to fix that?

LimAlbert commented 7 months ago

@matinzd Thanks for your work ! I have made a PR for the onRequestPermissionsResult issue (https://github.com/facebook/react-native/pull/42478)

matinzd commented 7 months ago

For all those wondering about this issue, I have some updates:

  1. There is an open PR, as you can see, that can address this problem. Once it gets merged, there will be a breaking change in the library. You will need to modify your MainActivity file to set the permission delegate and set up the proper AndroidManifest for permission rationale on Android 14 and above. See here for more information: https://github.com/matinzd/react-native-health-connect/pull/60

  2. We are currently awaiting meta to backport the permission contract issue to older versions of RN. This implies that after the new release, we will likely only support RN 70 and above, and the latest patch version, as the backport may only be available for recent RN versions. If you want to follow up on this matter, please read here: https://github.com/reactwg/react-native-releases/discussions/98#discussioncomment-8208768. Thanks to @LimAlbert for sending a fix.

chganesh commented 7 months ago

@matinzd What is the process for updating your package or adding those changes to my current package and applying a patch? My Current Versions react-native-health-connect": "^1.1.0 react-native": "0.72.4"

@matinzd @LimAlbert Please help on this

AlbertCardi commented 7 months ago

@chganesh If you can't upgrade to react-native@0.73.3, follow what I've written regarding react-native here (Edit the source code and then use patch-package to create patch)

For react-native-health-connect, you can use the pr that @matinzd made, it should be working

chganesh commented 7 months ago

@AlbertCardi Edited my source code to reflect changes made by @LimAlbert and @matinzd PR. Thanks @matinzd @LimAlbert @AlbertCardi

As of now retrieve last 30 days steps below method follow const last30DaysMetrics: any = [] const last30Days: any = [] // Loop through the last 7 days for (let i = 30; i >= 0; i--) { const currentDate = new Date() currentDate.setDate(currentDate.getDate() - i) // Subtract i days // Get the day name (e.g., "Sunday", "Monday", etc.) const dayName = new Intl.DateTimeFormat('en-US', { weekday: 'long' }).format(currentDate) // Store the health metrics for the current day

        const steps = await readRecords('Steps', {
            timeRangeFilter: {
                operator: 'between',
                startTime: new Date(currentDate.setHours(0, 0, 0, 0)).toISOString(),
                endTime: new Date(currentDate.setHours(23, 59, 59, 999)).toISOString(),
            },
        }).then(result => {
            const daySteps = result.reduce((sum, cur) => sum + cur.count, 0)
            // let frontColor = ''
            // if (daySteps > 3000) {
            let frontColor = '#177AD5'
            // }
            last30Days.push({
                label: currentDate,
                value: daySteps,
                frontColor: frontColor,
            })
            last30DaysMetrics.push({
                label: moment.utc(currentDate).local().format('ddd DD'),
                value: daySteps,
                frontColor: frontColor,
            })
        })
    }
    setWeekData(last30DaysMetrics)
    setOneMonthSteps(last30Days)

if any better way to get last 30 days please let me know Thanks in advance

matinzd commented 6 months ago

Fixed in v2.0.0.

nHussain18 commented 6 months ago

@matinzd after upgrading to v2.0.0, it is not asking for permission for health connect datatypes in android 14. my App is not showing in health connect App permissions list

react-native: 0.73.4 react-native-health-connect: 2.0.0

WhatsApp Image 2024-02-26 at 10 55 35 AM

I also tested in android 13 device it is working fine and asking for permissions

Screenshot 2024-02-26 at 10 43 36 AM
matinzd commented 6 months ago

Have you checked the latest release for the changes? You need to add some code to your MainActivity.kt.

nHussain18 commented 6 months ago

@matinzd yes I already made the changes in MainActivity.kt

WhatsApp Image 2024-02-26 at 1 35 35 PM

nHussain18 commented 6 months ago

@matinzd is there anything else that I am missing..

matinzd commented 6 months ago

Try checking the example app changes and see if you are missing anything:

https://github.com/matinzd/react-native-health-connect/pull/60/files#diff-8fc083d75c4d06482dc1ee1c9ba3edab0192a99add6fa96679b6ed189a049c88

I may have missed something in the documentation.

nHussain18 commented 6 months ago

I tried running example app but getting this issue https://github.com/matinzd/react-native-health-connect/issues/64 node: v19.8.1

matinzd commented 6 months ago

@nHussain18 https://github.com/matinzd/react-native-health-connect/issues/64#issuecomment-1966093049

Use node 18 or there might be something wrong with your environment setup.

nHussain18 commented 6 months ago

Try checking the example app changes and see if you are missing anything:

https://github.com/matinzd/react-native-health-connect/pull/60/files#diff-8fc083d75c4d06482dc1ee1c9ba3edab0192a99add6fa96679b6ed189a049c88

I may have missed something in the documentation.

@matinzd I checked the example app changes and tried to run it, and it is working fine as aspected.

I notice one change that you have made in example app but not documented. I made these changes in my app, then it is also working fine.

Missed Changes from this PR::

  1. example/android/app/src/main/AndroidManifest.xml
  2. example/android/app/src/main/java/com/healthconnectexample/PermissionRationaleActivity.kt
cvetkovskin commented 5 months ago

Hi, I encountered the same issue "No Activity found to handle Intent...". After upgrading the plugin to version 2.0.1, as well as adding the extra changes as @nHussain18, my app no longer crashes when calling requestPermission. However, now the requestPermission method never returns on Android 14 when using await. Does anyone else has this problem?

I tried adding logs in the native code as well, and the following logs are not printed.

fun requestPermission(
    reactPermissions: ReadableArray, providerPackageName: String, promise: Promise
  ) {
    Log.d("HealthConnectManager", "before throwUnlessClientIsAvailable")
    throwUnlessClientIsAvailable(promise) {
      coroutineScope.launch {
        val granted = HealthConnectPermissionDelegate.launch(PermissionUtils.parsePermissions(reactPermissions))
        Log.d("@requestPermission", "inside coroutine")
        promise.resolve(PermissionUtils.mapPermissionResult(granted))
      }
    }
  }
requestPermission = activity.registerForActivityResult(contract) {
      coroutineScope.launch {
        Log.d("@activityResult", "inside coroutine")
        channel.send(it)
        coroutineContext.cancel()
      }
    }
julhenry commented 5 months ago

Hi, I encountered the same issue "No Activity found to handle Intent...". After upgrading the plugin to version 2.0.1, as well as adding the extra changes as @nHussain18, my app no longer crashes when calling requestPermission. However, now the requestPermission method never returns on Android 14 when using await. Does anyone else has this problem?

I tried adding logs in the native code as well, and the following logs are not printed.

fun requestPermission(
    reactPermissions: ReadableArray, providerPackageName: String, promise: Promise
  ) {
    Log.d("HealthConnectManager", "before throwUnlessClientIsAvailable")
    throwUnlessClientIsAvailable(promise) {
      coroutineScope.launch {
        val granted = HealthConnectPermissionDelegate.launch(PermissionUtils.parsePermissions(reactPermissions))
        Log.d("@requestPermission", "inside coroutine")
        promise.resolve(PermissionUtils.mapPermissionResult(granted))
      }
    }
  }
requestPermission = activity.registerForActivityResult(contract) {
      coroutineScope.launch {
        Log.d("@activityResult", "inside coroutine")
        channel.send(it)
        coroutineContext.cancel()
      }
    }

I'm encountering the same issue. Have you found a solution to fix it ?

darshan2911 commented 4 months ago

Hi, I encountered the same issue "No Activity found to handle Intent...". After upgrading the plugin to version 2.0.1, as well as adding the extra changes as @nHussain18, my app no longer crashes when calling requestPermission. However, now the requestPermission method never returns on Android 14 when using await. Does anyone else has this problem?

I tried adding logs in the native code as well, and the following logs are not printed.

fun requestPermission(
    reactPermissions: ReadableArray, providerPackageName: String, promise: Promise
  ) {
    Log.d("HealthConnectManager", "before throwUnlessClientIsAvailable")
    throwUnlessClientIsAvailable(promise) {
      coroutineScope.launch {
        val granted = HealthConnectPermissionDelegate.launch(PermissionUtils.parsePermissions(reactPermissions))
        Log.d("@requestPermission", "inside coroutine")
        promise.resolve(PermissionUtils.mapPermissionResult(granted))
      }
    }
  }
requestPermission = activity.registerForActivityResult(contract) {
      coroutineScope.launch {
        Log.d("@activityResult", "inside coroutine")
        channel.send(it)
        coroutineContext.cancel()
      }
    }

having same issue. Did you find any solution?

abey-thomas commented 4 months ago

For older versions, I added below code while reading permission

  let grantedPermissions = await getGrantedPermissions();
  if (grantedPermissions.length === 0) {
    // request permissions
    grantedPermissions = await requestPermission([
      {accessType: 'write', recordType: 'ExerciseSession'},
    ]);
  }

and asked users to manually give permissions via health connect app. Now there is no app crash. requestPermission is triggering the app crash.

ronal2s commented 4 months ago

Hi, I'm having this issue in lower versions of android, on android 14 is working great but in android 13 and below is crashing due to android.content.ActivityNotFoundException: No Activity found to handle Intent { act=androidx.health.ACTION_REQUEST_PERMISSIONS pkg=com.myapp.app (has extras) }

fasihkhan754 commented 3 months ago

@matinzd im using react-native cli "react-native": "0.72.3", .... im unable to configure health-connect.. there is some kind of issue

health-connect issue
matinzd commented 3 months ago

Your manifest is invalid. Please open the Android project in Android Studio for assistance from the IDE to resolve the issue.