expo: Permissions.askAsync never resolves on iOS Standalone app

With out recent app release, we have found an apparent bug in Expo in the standalone app which caused Apple to reject the submission. It turns out that the Pemissions.askAsync is often not resolving the promise when the user clicks “Allow” or “Don’t Allow” on the native dialog popup.

We were able to resolve this in two ways:

  • Change the screen automatically in the background and don’t wait for a resolved response.
  • Use a timeout around the permissions dialog (calling Permissions.getAsync to attempt to get the response).

Ensure the promise resolved with a timeout:

Note: You should probably not let the UI wait for this response.

    askNotificationPermissions_withTimeout = () => {
        // NOTE: Permissions.askAsync seems to never resolve sometimes, so using a timeout to check status

        return new Promise((resolve, reject) => {
            const timeoutId = setTimeout(async () => {
                const { status } = await Permissions.getAsync(
                    Permissions.NOTIFICATIONS
                );
                resolve(status === 'granted');
            }, 5000);

            const promise = Permissions.askAsync(
                Permissions.NOTIFICATIONS
            );

            promise.then(r => {
                clearTimeout(timeoutId);

                const { status } = r;
                resolve(status === 'granted');
            });
        });

    };

Context

  • Expo SDK 24.0.0
  • iOS Standalone

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 37 (9 by maintainers)

Most upvoted comments

Hi, we just ran into the same issue with the latest version of Expo. Here are the steps to reproduce the issue:

  1. Ask the user for notifications permissions using askAsync
  2. Allow notification permissions
  3. Go to Expo settings and turn off notifications permissions
  4. Ask the user for notifications permissions again using askAsync

At that point, the app hangs waiting for the promise askAsync returns to resolve.

@brentvatne @bbarthec just to note: this issue was re-introduced in 'expo-permissions' (at least 6.0.0).

@terribleben I can confirm that this is happening on current builds too as @rmushtaq21 described. Should the issue be re-opened?

@kulikalov yeah, sure. Here is the example of my custom workaround:

import { useCameraPermission } from '../hooks/useCameraPermission'

const MyComponent = () => {
  // use the variable in your component: null - still polling, true - granted, false - declined
  const hasCameraPermission = useCameraPermission()
  ...
}

Implementation:

// hooks/useCameraPermission.js
import { useState, useEffect } from 'react'
import * as Permissions from 'expo-permissions'

const STATUS_GRANTED = 'granted'
const PERMISSION_CHECK_INTERVAL = 500 // ms
var PERMISSION_INTERVAL = null // quick HACK

const clear = () => {
  if (PERMISSION_INTERVAL) {
    clearInterval(PERMISSION_INTERVAL)
    PERMISSION_INTERVAL = null
  }
}

const checkStatus = ({ status, setHasCameraPermission }) => {
  const hasCameraPermission = status === STATUS_GRANTED
  if (hasCameraPermission) clear()
  setHasCameraPermission(hasCameraPermission)
}

const detectCameraPermission = async ({ setHasCameraPermission }) => {
  // check if permissions were given previously
  const { status: initialStatus } = await Permissions.getAsync(Permissions.CAMERA)

  if (initialStatus === STATUS_GRANTED) {
    setHasCameraPermission(true)
  } else {
    // set "long polling" interval first
    PERMISSION_INTERVAL = setInterval(async () => {
      const { status } = await Permissions.getAsync(Permissions.CAMERA)
      checkStatus({ status, setHasCameraPermission })
    }, PERMISSION_CHECK_INTERVAL)

    // this one hangs
    const { status } = await Permissions.askAsync(Permissions.CAMERA)
    // doesn't hit this line
    checkStatus({ status, setHasCameraPermission })
  }
}

export const useCameraPermission = () => {
  const [hasCameraPermission, setHasCameraPermission] = useState(null)

  useEffect(() => {
    detectCameraPermission({ setHasCameraPermission })
    return () => clear()
  }, [])

  return hasCameraPermission
}

@brentvatne I get the prompt, but after I tap “allow”, the code hangs on await Permissions.askAsync and does not allow me to persist the token.

@terribleben @watadarkstar pls reopen this issue, it’s not resolved.

@bbarthec Can you hit me with some knowledge on this issue pretty please? 😃 Thanks in advance!

there is an open WIP PR to fix this issue: https://github.com/expo/expo/pull/3737

Can confirm that SDK32 removes the hang.

However, I’m running into the issue where if the user declines the first time, askAsync will not prompt the user a second time. Is this still the standard iOS behavior as discussed here: https://forums.expo.io/t/asking-for-permissions-again-when-denied-the-first-time/10218?

yes it is

Can confirm – I observed the same thing on an app I released around the same time. Problem disappeared with a new build. Bites that it required re-approval, but it worked!