react-native: Assets uri's that begin with ph:// cause error

Description

When giving the Image component a uri beginning with ph:// like so:

<Image source={{ uri: "ph://*" }} />

the image fails to render:

eed5a1d1-5ffc-438c-96bf-ff3b79a8a6b5

In my scenario, I used expo-media-library to retrieve the uri’s. I built my app with the old architecture, and this error did not persist.

Version

0.71.1

Output of npx react-native info

System: OS: macOS 13.2 CPU: (8) arm64 Apple M1 Memory: 75.11 MB / 16.00 GB Shell: 5.8.1 - /bin/zsh Binaries: Node: 18.13.0 - ~/.nvm/versions/node/v18.13.0/bin/node Yarn: 1.22.19 - ~/.nvm/versions/node/v18.13.0/bin/yarn npm: 8.19.3 - ~/.nvm/versions/node/v18.13.0/bin/npm Watchman: 2023.01.30.00 - /opt/homebrew/bin/watchman Managers: CocoaPods: 1.11.3 - /opt/homebrew/bin/pod SDKs: iOS SDK: Platforms: DriverKit 22.2, iOS 16.2, macOS 13.1, tvOS 16.1, watchOS 9.1 Android SDK: Not Found IDEs: Android Studio: Not Found Xcode: 14.2/14C18 - /usr/bin/xcodebuild Languages: Java: Not Found npmPackages: @react-native-community/cli: Not Found react: 18.2.0 => 18.2.0 react-native: 0.71.1 => 0.71.1 react-native-macos: Not Found npmGlobalPackages: react-native: Not Found

Steps to reproduce

  1. get a uri starting with ph:// by any means
  2. render an image with that uri

Snack, code example, screenshot, or link to a repository

Not sure how to switch a snack into the new architecture, but this code in the new architecture will fail:

https://snack.expo.dev/gv6bMd2MY?platform=ios

UPDATE: Providing a simpler example to convey that this is a react native issue and not due to any dependency. The following code fails on the new archatecure with the error above:

import React from 'react';
import { Image } from 'react-native';

const App = () => {
  return <Image source={{ uri: 'ph://B84E8479-475C-4727-A4A4-B77AA9980897' }} style={{ width: 100, height: 100 }} />;
};

export default App;

but it runs exactly as expected in the old one. It looks like the new architecture can not handle iOS’s ph:// routes.

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 19 (9 by maintainers)

Most upvoted comments

@norbusonam @mrousavy @tsapeta I spin up an app with 0.71.1, and the Old Architecture fails in the same way as the new one.

Repro:

npx react-native@0.71.1 init TestImage71_1 --version 0.71.1 #this installs pods for the old arch by default.
cd TestImage71_1/ios
open TestImage71_1.xcworkspace
cd ..
yarn start

I retrieved the ID by:

  1. Adding the NSPhotoLibraryUsageDescription in the capabilities in Xcode
  2. Adding the Photos framework in the Frameworks, Libraries and Embedded Contents in the General tab of the project in Xcode
  3. Adding this code to the app delegate to read the id:
#import <Photos/PHAsset.h>
//...
PHFetchResult<PHAsset *> * res = [PHAsset fetchAssetsWithOptions:nil];
PHAsset * first = res.firstObject;
NSLog(@"Id: %@", first.localIdentifier);

Then, replace the App.tsx content with the following:

import React from 'react';
import {
  SafeAreaView,
  ScrollView,
  StatusBar,
  useColorScheme,
  Image,
  View,
} from 'react-native';

import {Colors} from 'react-native/Libraries/NewAppScreen';

function App(): JSX.Element {
  const isDarkMode = useColorScheme() === 'dark';

  const backgroundStyle = {
    backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
  };

  return (
    <SafeAreaView style={backgroundStyle}>
      <StatusBar
        barStyle={isDarkMode ? 'light-content' : 'dark-content'}
        backgroundColor={backgroundStyle.backgroundColor}
      />
      <ScrollView
        contentInsetAdjustmentBehavior="automatic"
        style={backgroundStyle}>
        <View
          style={{
            backgroundColor: isDarkMode ? Colors.black : Colors.white,
          }}>
          <Image
            source={{uri: 'ph://106E99A1-4F6A-45A2-B320-B0AD4A8E8473'}}
            style={{width: 100, height: 100}}
          />
        </View>
      </ScrollView>
    </SafeAreaView>
  );
}

export default App;

For my simulator, 106E99A1-4F6A-45A2-B320-B0AD4A8E8473 is a valid id for an asset in the camera roll.

When I run the app in the Old Architecture, I get the same error message: Screenshot 2024-03-06 at 10 29 26


Could it be that, in the Old Architecture, the libraries were able to self register their image handler in the list of handlers? So, what I think was happening:

  1. yarn add react-native-cameraroll #and example of library that can handle ph:// url schemas
  2. Add NSPhotoLibraryUsageDescription
  3. Use `<Image source:{{ uri: “ph://*” }} />

And it looked like React Native is able to manage the ph:// directly, but it was actually a library handling that?

That fits with my understanding and as a difference between New/Old Arch. In fact, libraries couldn’t, before 0.74, register themselves as URL Handlers/Image Loaders in the New Architecture.

up, we don’t want for this issue to be reaped. We will work on this next half.

Closing this as it is an intended React Native behaviour.

The Old Architecture is relying on some library to pick up the ph:// protocol. Libraries on the Old Architecture can register automatically to respond to custom URL protocols, that’s why it looked like React Native was able to load images using the ph:// protocol.

In the New Architecture, before 0.74, libraries could not register themselves as URL handler, hence, this issue has been opened. In 0.74, thanks to this PR, libraries can now register themselves as URL handler, filling the gap between the Old and the New architecture.

Sharing some findings and a solution that worked for me:

Findings

According to this issue React Native no longer support’s ph:// routes after @react-native-camera-roll/camera-roll was separated, and installing @react-native-camera-roll/camera-roll should integrate the proper handler. However, according to this issue the new architecture hard codes RCTURLRequestHandler, which means simply installing @react-native-camera-roll/camera-roll won’t be enough to solve the problem.

Solution

Ultimetly, I was able to work around this by:

  1. install @react-native-camera-roll/camera-roll
  2. Override getModuleInstanceFromClass in your AppDelegate.mm to use @react-native-camera-roll/camera-roll’s RNCAssetsLibraryRequestHandler handler:
...

#import "RNCAssetsLibraryRequestHandler.h"
#import <React/RCTImageLoader.h>
#import <React/RCTDataRequestHandler.h>
#import <React/RCTFileRequestHandler.h>
#import <React/RCTGIFImageDecoder.h>
#import <React/RCTHTTPRequestHandler.h>
#import <React/RCTLocalAssetImageLoader.h>
#import <React/RCTNetworking.h>

...

- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
{
  // Set up the default RCTImageLoader and RCTNetworking modules.
  if (moduleClass == RCTImageLoader.class) {
    return [[moduleClass alloc] initWithRedirectDelegate:nil
        loadersProvider:^NSArray<id<RCTImageURLLoader>> *(RCTModuleRegistry *) {
          return @[ [RCTLocalAssetImageLoader new] ];
        }
        decodersProvider:^NSArray<id<RCTImageDataDecoder>> *(RCTModuleRegistry *) {
          return @[ [RCTGIFImageDecoder new] ];
        }];
  } else if (moduleClass == RCTNetworking.class) {
    return [[moduleClass alloc] initWithHandlersProvider:^NSArray<id<RCTURLRequestHandler>> *(RCTModuleRegistry *) {
      return @[
        [RCTHTTPRequestHandler new],
        [RCTDataRequestHandler new],
        [RCTFileRequestHandler new],
        [RNCAssetsLibraryRequestHandler new],
      ];
    }];
  }
  // No custom initializer here.
  return [moduleClass new];
}

...

Extra Info

After these finding’s I realized the reason ph:// routes were working on the old architecture was likely because expo provides a EXImageLoader. And since the new architecture hardcodes RCTURLRequestHandler, it did now work there.

Hi @norbusonam, thank you for opening the issue and to explore it further. We are aware of this limitation for the new Architecture, unfortunately. I see you found the PR trying to fix that, but we are not convinced by that fix yet.

Your override is a good workaround for the time being, while we find a better solution.

What does the ph:// prefix mean? What kind of permissions does the user need to grant?

Just mean that the URI starts with ph://. These URI’s route to PHAssets in iOS (basically just photo library assets).

That issue with permissions was just an expo-image thing.

The problem here is that React Native’s core Image component errors when it tries to render any image with a ph:// on the new architecture. I confirmed this was a regression from the old architecture.

EDIT: To clarify, the user does need to grant either limited or full access to photo library to render a photo library image, but that is not the main issue here

please save me!!!

In my scenario, I used expo-media-library to retrieve the uri’s. I built my app with the old architecture, and this error did not persist.

As this is related to expo-media-library (which lives here https://github.com/expo/expo/tree/main/packages/expo-media-library) and their support for New Architecture, you should open this issue against Expo’s repo.