Files
react-native/packages/react-native/Libraries/Image/ImageViewNativeComponent.js
T
Mateo Guzmán 665212e22c Fix Android Image defaultSource runtime error (#49097)
Summary:
Fixes https://github.com/facebook/react-native/issues/49075

The Image `defaultSource` prop is causing a runtime error from 0.77 just by using it in the Image component (see error in the linked issue). This might be a regression from some changes in the prop processing logic from either https://github.com/facebook/react-native/issues/47710, https://github.com/facebook/react-native/issues/47713 or https://github.com/facebook/react-native/issues/47754.

[ANDROID] [FIXED] - Fix Image defaultSource runtime error

Pull Request resolved: https://github.com/facebook/react-native/pull/49097

Test Plan:
- Verify it doesn't throw any error on runtime anymore for Android.
- iOS behaviour should not be impacted as changes are Android specific

In the RNTester, you can use the following component:

```tsx
import * as React from 'react';
import {Image, View} from 'react-native';

function Playground() {
  return (
    <View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
      <Image
        defaultSource={require('../../assets/bandaged.png')}
        source={{
          uri: 'https://i.natgeofe.com/n/548467d8-c5f1-4551-9f58-6817a8d2c45e/NationalGeographic_2572187_4x3.jpg',
        }}
        style={{width: 200, height: 200}}
      />
    </View>
  );
}
```

Also, this `defaultSource` prop is ignored in debug builds for Android ([as per the docs](https://reactnative.dev/docs/image#defaultsource)) – but I've verified we get the defaultSource as a string, which is what we expect on the native side:

<details>
<summary>Screenshot of the Android logs</summary>

![image](https://github.com/user-attachments/assets/e62ae2c3-6a93-4e44-a2d7-c913f5db2173)

</details>

Reviewed By: javache

Differential Revision: D69052723

Pulled By: cortinico

fbshipit-source-id: 2860dd4c18cefcfcbc4e39f94dfa6305f45773a3
2025-03-18 11:42:15 +00:00

174 lines
4.9 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {ViewProps} from '../Components/View/ViewPropTypes';
import type {
HostComponent,
HostInstance,
PartialViewConfig,
} from '../Renderer/shims/ReactNativeTypes';
import type {
ColorValue,
DangerouslyImpreciseStyle,
ImageStyleProp,
} from '../StyleSheet/StyleSheet';
import type {ResolvedAssetSource} from './AssetSourceResolver';
import type {ImageProps} from './ImageProps';
import type {ImageSource} from './ImageSource';
import * as NativeComponentRegistry from '../NativeComponent/NativeComponentRegistry';
import {ConditionallyIgnoredEventHandlers} from '../NativeComponent/ViewConfigIgnore';
import codegenNativeCommands from '../Utilities/codegenNativeCommands';
import Platform from '../Utilities/Platform';
type Props = $ReadOnly<{
...ImageProps,
...ViewProps,
style?: ImageStyleProp | DangerouslyImpreciseStyle,
// iOS native props
tintColor?: ColorValue,
// Android native props
shouldNotifyLoadEvents?: boolean,
src?:
| ?ResolvedAssetSource
| ?$ReadOnlyArray<?$ReadOnly<{uri?: ?string, ...}>>,
headers?: ?{[string]: string},
defaultSource?: ?ImageSource | ?string,
loadingIndicatorSrc?: ?string,
}>;
interface NativeCommands {
+setIsVisible_EXPERIMENTAL: (
viewRef: HostInstance,
isVisible: boolean,
time: number,
) => void;
}
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
supportedCommands: ['setIsVisible_EXPERIMENTAL'],
});
export const __INTERNAL_VIEW_CONFIG: PartialViewConfig =
Platform.OS === 'android'
? {
uiViewClassName: 'RCTImageView',
bubblingEventTypes: {},
directEventTypes: {
topLoadStart: {
registrationName: 'onLoadStart',
},
topProgress: {
registrationName: 'onProgress',
},
topError: {
registrationName: 'onError',
},
topLoad: {
registrationName: 'onLoad',
},
topLoadEnd: {
registrationName: 'onLoadEnd',
},
},
validAttributes: {
blurRadius: true,
defaultSource: true,
internal_analyticTag: true,
resizeMethod: true,
resizeMode: true,
resizeMultiplier: true,
tintColor: {
process: require('../StyleSheet/processColor').default,
},
borderBottomLeftRadius: true,
borderTopLeftRadius: true,
src: true,
// NOTE: New Architecture expects this to be called `source`,
// regardless of the platform, therefore propagate it as well.
// For the backwards compatibility reasons, we keep both `src`
// and `source`, which will be identical at this stage.
source: true,
borderRadius: true,
headers: true,
shouldNotifyLoadEvents: true,
overlayColor: {
process: require('../StyleSheet/processColor').default,
},
borderColor: {
process: require('../StyleSheet/processColor').default,
},
accessible: true,
progressiveRenderingEnabled: true,
fadeDuration: true,
borderBottomRightRadius: true,
borderTopRightRadius: true,
loadingIndicatorSrc: true,
},
}
: {
uiViewClassName: 'RCTImageView',
bubblingEventTypes: {},
directEventTypes: {
topLoadStart: {
registrationName: 'onLoadStart',
},
topProgress: {
registrationName: 'onProgress',
},
topError: {
registrationName: 'onError',
},
topPartialLoad: {
registrationName: 'onPartialLoad',
},
topLoad: {
registrationName: 'onLoad',
},
topLoadEnd: {
registrationName: 'onLoadEnd',
},
},
validAttributes: {
blurRadius: true,
capInsets: {
diff: require('../Utilities/differ/insetsDiffer'),
},
defaultSource: {
process: require('./resolveAssetSource'),
},
internal_analyticTag: true,
resizeMode: true,
source: true,
tintColor: {
process: require('../StyleSheet/processColor').default,
},
...ConditionallyIgnoredEventHandlers({
onLoadStart: true,
onLoad: true,
onLoadEnd: true,
onProgress: true,
onError: true,
onPartialLoad: true,
}),
},
};
const ImageViewNativeComponent: HostComponent<Props> =
NativeComponentRegistry.get<Props>(
'RCTImageView',
() => __INTERNAL_VIEW_CONFIG,
);
export default ImageViewNativeComponent;