react-nativeexpoexpo-image-picker

How to access actual (not cached) location of an image with Expo image picker in React Native app?


Background: I'm building a mobile app with React Native and Expo managed workflow (currently deploying to iOS).

What I want to do: I want the user to be able to either choose an image from the media library, or take a picture with the camera and in that case persist it in the library. The app should store the location of the image, so that the user can later upload it to a server. Later means hours or even a day later, so I need the real location of the image, not a cached one.

What I have already: For choosing an image, I got it to work in the following way:

const permissionResponse = await MediaLibrary.requestPermissionsAsync();
if (permissionResponse.granted) {
  const result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ImagePicker.MediaTypeOptions.Images
  });

  for (const asset of (result?.assets || [])) {
    const info = await MediaLibrary.getAssetInfoAsync(asset.assetId);
    await storeImageLocation(info.localUri);
  }
}

(Where storeImageLocation is my own function.)

The problem: For taking a picture with the camera, persisting it in the library, and then storing the actual location, I started with the following code:

const permission = await MediaLibrary.requestPermissionsAsync();
if (permission.granted) {
  const result = await ImagePicker.launchCameraAsync({
    mediaTypes: ImagePicker.MediaTypeOptions.Images,
    allowsEditing: true
  });

  for (const asset of (result?.assets || [])) {
    const savedAsset = await MediaLibrary.createAssetAsync(asset.uri);
    await storeImageLocation(savedAsset.uri);   
  }
}  

But when I close the app and come back later, I can't access the image.

I also tried to use MediaLibrary.getAssetInfoAsync to get the localUri like in the first case, with either the asset or the savedAsset, but whatever I try, I don't get it to work.

Any hints?

Or some background on how image locations work on an iPhone?


Solution

  • I have solved similar case by copying the photo to the app's interal document directory after the user has selected or taken the photo and photo path is known. This approach ensures that the photo remains secure from any third-party alterations or deletions and allows me to access it reliably in the future.

    Code would look something like this:

    export const PHOTOS_FOLDER = `${FileSystem.documentDirectory || ''}photos`
    
    async function initializeFolder() {
        const info = await FileSystem.getInfoAsync(PHOTOS_FOLDER)
    
        if (info.exists) {
            return Promise.resolve()
        }
    
        return await FileSystem.makeDirectoryAsync(PHOTOS_FOLDER, { intermediates: true })
    }
    
    await initializeFolder()
    
    const key = uuidv4() // any unique identifer will work
    const newUri = `${PHOTOS_FOLDER}/${key}.jpg`
    
    const photo: Asset =  // TODO: get it from MediaLibrary / Camera
    
    
    await FileSystem.copyAsync({ from: photo.uri, to: newUri })
    
    // TODO: save the newUri to some storage so you know where to look later when you actually want to do the upload
    

    Note that the files in FileSystem.documentDirectory are kept there until the app deletes them or the app is uninstalled. More info: https://docs.expo.dev/versions/latest/sdk/filesystem/#filesystemdocumentdirectory