diff --git a/ReactAndroid/src/androidTest/AndroidManifest.xml b/ReactAndroid/src/androidTest/AndroidManifest.xml
index 62c744e8f895bf..0ebc7b4768ecba 100644
--- a/ReactAndroid/src/androidTest/AndroidManifest.xml
+++ b/ReactAndroid/src/androidTest/AndroidManifest.xml
@@ -6,6 +6,8 @@
+
+
diff --git a/ReactAndroid/src/androidTest/buck-runner/AndroidManifest.xml b/ReactAndroid/src/androidTest/buck-runner/AndroidManifest.xml
index 8fd01fc76607a3..7672ea05ee6767 100644
--- a/ReactAndroid/src/androidTest/buck-runner/AndroidManifest.xml
+++ b/ReactAndroid/src/androidTest/buck-runner/AndroidManifest.xml
@@ -7,6 +7,8 @@
+
+
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ImageErrorTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ImageErrorTestCase.java
new file mode 100644
index 00000000000000..3676a9cd202cf1
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ImageErrorTestCase.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+package com.facebook.react.tests;
+
+import android.view.View;
+import com.facebook.react.testing.ReactAppInstrumentationTestCase;
+import com.facebook.react.testing.ReactInstanceSpecForTest;
+import com.facebook.react.testing.StringRecordingModule;
+
+/**
+ * Simple test case to check that onError does not get called with undefined
+ */
+public class ImageErrorTestCase extends ReactAppInstrumentationTestCase {
+
+ private StringRecordingModule mStringRecordingModule;
+
+ @Override
+ protected String getReactApplicationKeyUnderTest() {
+ return "ImageErrorTestApp";
+ }
+
+ public void testErrorHasCause() throws Exception {
+ assertNotNull(getViewByTestId("image-1"));
+ assertNotNull(getViewByTestId("image-2"));
+ assertNotNull(getViewByTestId("image-3"));
+
+ Thread.sleep(3000);
+
+ assertEquals(3, mStringRecordingModule.getCalls().size());
+ assertEquals("Got error: Unsupported uri scheme! Uri is: ", mStringRecordingModule.getCalls().get(0));
+ assertEquals("Got error: /does/not/exist: open failed: ENOENT (No such file or directory)", mStringRecordingModule.getCalls().get(1));
+ assertEquals("Got error: Unexpected HTTP code Response{protocol=http/1.1, code=404, message=Not Found, url=https://typo_error_facebook.github.io/react/logo-og.png}", mStringRecordingModule.getCalls().get(2));
+ }
+
+ @Override
+ protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
+ mStringRecordingModule = new StringRecordingModule();
+ return super.createReactInstanceSpecForTest()
+ .addNativeModule(mStringRecordingModule);
+ }
+}
diff --git a/ReactAndroid/src/androidTest/js/ImageErrorTestApp.js b/ReactAndroid/src/androidTest/js/ImageErrorTestApp.js
new file mode 100644
index 00000000000000..0afc6c925e1225
--- /dev/null
+++ b/ReactAndroid/src/androidTest/js/ImageErrorTestApp.js
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @format
+ */
+
+'use strict';
+
+const React = require('React');
+const Image = require('Image');
+const StyleSheet = require('StyleSheet');
+const View = require('View');
+
+const RecordingModule = require('NativeModules').Recording;
+
+class ImageErrorTestApp extends React.Component {
+ onError = e => {
+ RecordingModule.record('Got error: ' + e.nativeEvent.error);
+ };
+
+ render() {
+ // For some reason image-2 needs explicit height. Without it onError is not triggered.
+ return (
+
+
+
+
+
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ image: {
+ height: 50,
+ width: 50,
+ },
+});
+
+module.exports = ImageErrorTestApp;
diff --git a/ReactAndroid/src/androidTest/js/TestBundle.js b/ReactAndroid/src/androidTest/js/TestBundle.js
index 906a3601331bda..6fb617821b6754 100644
--- a/ReactAndroid/src/androidTest/js/TestBundle.js
+++ b/ReactAndroid/src/androidTest/js/TestBundle.js
@@ -58,6 +58,10 @@ const apps = [
appKey: 'ImageOverlayColorTestApp',
component: () => require('ImageOverlayColorTestApp'),
},
+ {
+ appKey: 'ImageErrorTestApp',
+ component: () => require('ImageErrorTestApp'),
+ },
{
appKey: 'InitialPropsTestApp',
component: () => require('InitialPropsTestApp'),
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java
index b897aac09b23de..43d4c152c48531 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java
@@ -33,13 +33,18 @@ public class ImageLoadEvent extends Event {
private final @Nullable String mImageUri;
private final int mWidth;
private final int mHeight;
+ private final @Nullable String mImageError;
public ImageLoadEvent(int viewId, @ImageEventType int eventType) {
this(viewId, eventType, null);
}
+ public ImageLoadEvent(int viewId, @ImageEventType int eventType, boolean error, String message) {
+ this(viewId, eventType, null, 0, 0, message);
+ }
+
public ImageLoadEvent(int viewId, @ImageEventType int eventType, String imageUri) {
- this(viewId, eventType, imageUri, 0, 0);
+ this(viewId, eventType, imageUri, 0, 0, null);
}
public ImageLoadEvent(
@@ -48,11 +53,22 @@ public ImageLoadEvent(
@Nullable String imageUri,
int width,
int height) {
+ this(viewId, eventType, imageUri, width, height, null);
+ }
+
+ public ImageLoadEvent(
+ int viewId,
+ @ImageEventType int eventType,
+ @Nullable String imageUri,
+ int width,
+ int height,
+ @Nullable String message) {
super(viewId);
mEventType = eventType;
mImageUri = imageUri;
mWidth = width;
mHeight = height;
+ mImageError = message;
}
public static String eventNameForType(@ImageEventType int eventType) {
@@ -88,7 +104,7 @@ public short getCoalescingKey() {
public void dispatch(RCTEventEmitter rctEventEmitter) {
WritableMap eventData = null;
- if (mImageUri != null || mEventType == ON_LOAD) {
+ if (mImageUri != null || (mEventType == ON_LOAD || mEventType == ON_ERROR)) {
eventData = Arguments.createMap();
if (mImageUri != null) {
@@ -103,6 +119,8 @@ public void dispatch(RCTEventEmitter rctEventEmitter) {
source.putString("url", mImageUri);
}
eventData.putMap("source", source);
+ } else if (mEventType == ON_ERROR) {
+ eventData.putString("error", mImageError);
}
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java
index 722dd1225095f8..305f69526a3b17 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java
@@ -262,9 +262,8 @@ public void onFinalImageSet(
@Override
public void onFailure(String id, Throwable throwable) {
mEventDispatcher.dispatchEvent(
- new ImageLoadEvent(getId(), ImageLoadEvent.ON_ERROR));
- mEventDispatcher.dispatchEvent(
- new ImageLoadEvent(getId(), ImageLoadEvent.ON_LOAD_END));
+ new ImageLoadEvent(getId(), ImageLoadEvent.ON_ERROR,
+ true, throwable.getMessage()));
}
};
}