ImagePicker not opening in Android production builds, works fine in expo client app

  1. SDK Version: 37
  2. Platforms(Android/iOS/web/all): Android 10 (onepus 6t/7)

I’ve been using the ImagePicker api in a managed Expo workflow for a while, suddenly this stopped working for my app in production builds (not through expo client app, it works fine here) but only on android (iOS also works fine). I can no longer access the gallery or the camera. it just doesn’t do anything. I’ve seen other people have come across this in like 2017. I tried a few solutions posted on various site but none have made a different. I’ve also tried upgrading the sdk version but this also did not fix it. Has anyone else come across this or have a solution?

Hi, I’m having the same problem and was about to post a new topic when I found yours. I don’t have a solution, I’ll just add some info that may help find one.

  1. SDK 37
  2. Platforms Android/iOS/web: Android 9 and 10 on AVD emulator and BlueStacks and physical devices as well.

Other relevant info…

  • Managed workflow
  • The problem exists only on APK and doesn’t happen on Expo client (on iOS or Android). Haven’t done a TestFlight yet.
  • I think the problem began when I updated SDK from 36 to 37. Same code was working before the update.

app.json permissions…

     "permissions": [
        "ACCESS_COARSE_LOCATION",
        "ACCESS_FINE_LOCATION",
        "CAMERA",
        "READ_EXTERNAL_STORAGE",
        "READ_INTERNAL_STORAGE",
        "WRITE_EXTERNAL_STORAGE"
      ]

I’ve seen the problem described multiple times with earlier versions of SDK since 20 something. I think I tried most of the solutions proposed but I’m relatively new to Expo so the problem could very well be “between the keyboard and the chair”.

I’ll share the relevant part of the code that was working before the upgrade to 37.

useEffect(() => {
    getPermissionAsync()
  }, [])

  const getPermissionAsync = async () => {
    try {
      const { status } = await Permissions.askAsync(Permissions.CAMERA_ROLL)
      if (status !== "granted") {
        alert("Se necesita permiso para la cámara...")
      }
    } catch (e) {
      console.log(e)
    }
  }

and then I have a button that calls this next function onPress…

const _takePicture = async () => {
    try {
      let result = await ImagePicker.launchCameraAsync({
        allowsEditing: false,
        aspect: [4, 3],
        quality: 0.8,
        base64: true,
      })
      props.addPhoto(result)
    } catch (e) {
      console.log(e)
      alert(e)
    }
  }

Since then I’ve added askAsync for Permissions.CAMERA as well in a few different ways (not sure which is preferred yet):

const statuses = await Promise.all([
  Permissions.askAsync(Permissions.CAMERA_ROLL), 
  Permissions.askAsync(Permissions.CAMERA)
])

and also…

await Permissions.getAsync(Permissions.CAMERA_ROLL, Permissions.CAMERA)

and

await Permissions.askAsync(Permissions.CAMERA_ROLL)
await Permissions.askAsync(Permissions.CAMERA)

Still when ImagePicker.launchCameraAsync(…) gets called I catch the error:

Error: user rejected permissions.

I think the askAsync(Permission.CAMERA) never resolves…

Thank you for Expo and any help!

It looks like your asking for the wrong permission. CAMERA_ROLL is for the gallery and CAMERA is for the camera.
you need this if you wanna access the camera.

      const { status } = await Permissions.askAsync(Permissions.CAMERA)

Yes, thank you. You are right about that but like I wrote further down in my original post, I’ve tried CAMERA too. I actually need both in the real component but is good to clarify for anybody reading this later.

I finally managed to get it working but it’s hard to know what did it. I think it had nothing to do with how askAsync was called, in my case. The current working version is this:

const getPermissionAsync = async () => {
    try {
      if (Constants.platform.ios) {
        const cam = await ImagePicker.requestCameraPermissionsAsync()
        if (cam.status !== "granted") {
          alert("Se necesita permiso para la cámara...")
        }

        const roll = await ImagePicker.requestCameraRollPermissionsAsync()
        if (roll.status !== "granted") {
          alert("Se necesita permiso para camera roll...")
        }
      }
    } catch (e) {
      console.log(e)
    }
  }

But that didn’t solve it either when I changed it.I suspect that the problem had more to do with the android.permissions array in app.json…

    "permissions": [
        "ACCESS_COARSE_LOCATION",
        "ACCESS_FINE_LOCATION",
        "CAMERA",
        "READ_EXTERNAL_STORAGE",
        "READ_INTERNAL_STORAGE",
        "WRITE_EXTERNAL_STORAGE"
      ]

I added the last 3 at some point but as I was pushing new changes OTA, i.e “expo publish” they never really get to the apk (maybe somebody can confirm this makes sense?).

So, the effort that fixed it finally involved wipe of node_modules, do a yarn again… and then build a new apk.

I realize this is like “have you tried turning it off and on again” but in my case it may make sense if “expo publish” ignores changes in app.json.

It also goes to show there’s little chance this has anything to do with SDK 37, right?

Hope it helps.

got it working now. i changed 2 things.

  1. changed how i handle the response from asking for the permissions from
const permission = await Permissions.askAsync(Permissions.CAMERA_ROLL);
if (!permission.granted) return;

to this

let permission = await Permissions.getAsync(Permissions.CAMERA_ROLL);
if (permission.status !== "granted") permission = await Permissions.askAsync(Permissions.CAMERA_ROLL);

if (permission.status !== "granted") {
	alert("Hey, we need camera roll permissions to make this work!");
	return;
}
  1. added 2 more permissions to the app.json file, so the android permissions section now looks like this
"permissions": ["CAMERA", "WRITE_EXTERNAL_STORAGE", "READ_EXTERNAL_STORAGE"]

i also found that adding an Alert.alert(e.toString()); to the catch block provides a rudimentary way to debug the standalone builds.
i dunno how the expo ota stuff works under the hood, it’s magic.