◉RORK LABJP
●EXPO — EAS Observe now records native crashes (Oct 7). On SDK 57, update to 57.0.21 or later●SDK 58 — SDK 58 Beta has been out since Sep 15. The stable date is still unconfirmed●RN 0.88 — React Native 0.88.x is scheduled for Oct 12, 3 days left●Q&A — People are asking why expo-widgets render blank only in production builds●RORK — GPT-6.1 Sol was added on Sep 29, available on Pro and Max plans●NEW — Building an app for a client? Decide who owns the publishing account first●EXPO — EAS Observe now records native crashes (Oct 7). On SDK 57, update to 57.0.21 or later●SDK 58 — SDK 58 Beta has been out since Sep 15. The stable date is still unconfirmed●RN 0.88 — React Native 0.88.x is scheduled for Oct 12, 3 days left●Q&A — People are asking why expo-widgets render blank only in production builds●RORK — GPT-6.1 Sol was added on Sep 29, available on Pro and Max plans●NEW — Building an app for a client? Decide who owns the publishing account first
Articles/Dev Tools
⬡ Dev Tools/2026-05-10Intermediate

When expo-image-picker won't launch in your Rork app — Info.plist and permission fixes that actually worked

Your Rork-generated app's pick image button works in the simulator but does nothing on TestFlight builds. Walk through the four real-world causes I keep hitting: missing Info.plist keys, wrong permission order, iOS 14 Limited Photo Access, and Android 13+ media permissions.

expo-image-pickerInfo.plist6permissions7iOS115Android53troubleshooting67

You tap the "pick image" button in your Rork-generated app and absolutely nothing happens — but only on TestFlight, not in the simulator. I lost a full day to that exact pattern recently.

I have been shipping individual mobile apps since 2014, mostly wallpaper-style apps that have crossed 50 million cumulative downloads. Image picker has shipped in more than ten of them, and I still get bitten by Info.plist whenever I trust the AI-generated expo-image-picker snippet without double-checking the platform configuration. The four causes below cover almost every "won't launch" report I have seen, so working through them in order tends to unblock most teams.

Cause 1: Info.plist keys are missing (the first wall on iOS)

Since iOS 10, accessing the photo library or camera requires a usage-description string in Info.plist. Apps generated by Expo or Rork often start out without those keys at all on the first build.

The signature of this failure is that the simulator works fine, but on a real device — especially via TestFlight — tapping the button does nothing or the app crashes outright.

Add the keys under ios.infoPlist in app.json.

{
  "expo": {
    "ios": {
      "infoPlist": {
        "NSPhotoLibraryUsageDescription": "We use your photo library so you can choose a profile picture.",
        "NSCameraUsageDescription": "We use the camera so you can take a profile picture.",
        "NSPhotoLibraryAddUsageDescription": "We save images you create back to your photo library."
      }
    }
  }
}

Editing the file alone does not propagate the change. With Expo Go, restart the dev server; with a native build, you have to run EAS Build again. When someone tells me "I changed app.json and nothing happened," nine times out of ten they simply have not rebuilt the binary.

Cause 2: Calling launchImageLibraryAsync without requesting permission first

A common pattern in Rork-generated code is to call launchImageLibraryAsync directly without prompting for permission. Some platforms silently return an empty result in that case, which looks exactly like a button that does nothing.

The correct order is to request permission, handle the rejection path, and only then launch the picker.

import * as ImagePicker from 'expo-image-picker';
import { Alert, Linking } from 'react-native';
 
async function pickImage() {
  // 1. Request permission first; bail out clearly on rejection.
  const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
 
  if (status !== 'granted') {
    Alert.alert(
      'Photo access not allowed',
      'Please grant photo access from the Settings app.',
      [
        { text: 'Cancel', style: 'cancel' },
        { text: 'Open Settings', onPress: () => Linking.openSettings() },
      ]
    );
    return;
  }
 
  // 2. Launch the picker after the permission is granted.
  const result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ImagePicker.MediaTypeOptions.Images,
    allowsEditing: true,
    quality: 0.8,
  });
 
  if (!result.canceled) {
    // Expected output: result.assets[0].uri begins with file://
    console.log(result.assets[0].uri);
  }
}

The most important detail is what happens when the user denies permission. Once a user taps "Don't Allow," the OS prompt never appears again, so your app has to provide an alternative path back to Settings. Skip that and you end up with one-star reviews that simply read "the button does nothing." During the years my wallpaper apps were peaking on AdMob, I learned the cost of that oversight the hard way.

Cause 3: The iOS 14 Limited Photo Access trap

iOS 14 introduced Limited Photo Access — the option that lets the user share only selected photos. When the user picks "Selected Photos Only," your app can only see the photos they explicitly authorized.

requestMediaLibraryPermissionsAsync() returns granted even in Limited mode, so on paper everything looks fine. From the user's side, however, the picker shows almost no images, and they file a "the picker is broken" support ticket. I have shipped that bug into multiple apps before noticing.

If you want to provide a flow back to "All Photos," you need the iOS native API PHPhotoLibrary.shared().presentLimitedLibraryPicker(from:). Expo's stock module does not wrap it. For a solo developer, my pragmatic answer has been to design the experience so that Limited mode does not break the app:

  • Show a friendly "no photos selected" message when the result is empty.
  • Document the "All Photos" toggle in Settings with screenshots.
  • Recommend "All Photos" during onboarding when the feature really needs it.

For the broader question of how to design permission flows, troubleshooting permission dialogs that never appear in your Rork app is worth pairing with this article.

Cause 4: Android 13+ media permission changes

Until Android 12, READ_EXTERNAL_STORAGE was enough to read photos. Android 13 (API level 33) split that into READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, and READ_MEDIA_AUDIO.

Expo SDK 49+ ships an expo-image-picker that follows the new permissions internally, but if android.permissions in your app.json still references the old ones, you may see build-time warnings or a Play Store reviewer flagging your app for requesting unnecessary permissions.

{
  "expo": {
    "android": {
      "permissions": [
        "android.permission.READ_MEDIA_IMAGES",
        "android.permission.READ_MEDIA_VIDEO"
      ]
    }
  }
}

A subtler win on Android 13+ is the system-provided Photo Picker, which removes the need for any of those permissions altogether. expo-image-picker already routes through it for simple selections, so a basic profile-photo flow can ship with no permission request at all. If your picker "won't launch," it is also worth asking whether the runtime permission request is even running in the first place.

When you want to step back and look at the whole native build pipeline, working with Expo prebuild and custom native modules and fixing environment variable configuration errors in Rork help spot the cases where Info.plist alone is not the whole story.

One thing to do first

If only one fix fits in your day, it is this: add NSPhotoLibraryUsageDescription to ios.infoPlist in app.json, then rebuild with EAS Build. I have lost count of how many times that single move has solved the problem for me.

If this saves you a few hours of the same dead end I walked into, that is more than enough for me. Thank you for reading.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⬡ Dev Tools2026-10-07
Japanese Headings Won't Turn Bold on Android: How to Trace and Fix a fontWeight That Does Nothing
A troubleshooting note for Rork apps where headings look bold on iPhone but stay thin on Android. How to tell the symptoms apart, pick weights through fontFamily, and verify the fix.
⬡ Dev Tools2026-09-10
I Added a Dim Screen Button, and iPhones Stayed Dark After the App Was Closed
In expo-brightness, setBrightnessAsync applies only to the current activity on Android, but changes the device brightness itself on iOS. Here is why the cleanup burden falls on one platform only, and a hook that restores brightness through AppState.
⬡ Dev Tools2026-05-26
iOS Foreground Notifications Disappear in Expo — A Practical Fix Guide
A focused walkthrough of why iOS silently drops push notifications when the app is in the foreground, and how to wire setNotificationHandler correctly for iOS 14+ with expo-notifications.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links