From c991e1c4cc686ed6ef36a672a46a10e53dff6bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastiaan=20Versteeg?= Date: Tue, 12 Mar 2019 19:17:24 -0700 Subject: [PATCH] Add Image.getSizeWithHeaders (#18850) Summary: This adds new functionality to the `Image` component by allowing you to retrieve the width and height of an image just like you'd do with [`Image.getSize`](https://facebook.github.io/react-native/docs/image.html#getsize) but _with_ the ability to provide headers to your request. Why would you need this you ask? Well, imagine that you have an image that you're loading into your `Image` component that is protected and you get access by using a token in a header (or something similar). That would work. However, getting the dimensions isn't possible since you can't provide those same headers. This is something that is bothering me when using a third-party library (https://github.com/archriss/react-native-image-gallery) and instead of implementing this just for that single library I imagined that it would be useful for anyone else that needs to get the image dimensions before displaying it. [Android] [Added] - Added Image.getSizeWithHeaders [iOS] [Added] - Added Image.getSizeWithHeaders Pull Request resolved: https://github.com/facebook/react-native/pull/18850 Differential Revision: D14434599 Pulled By: cpojer fbshipit-source-id: 56d5e58889ddf7ddc12d5f6f7d9dc6921fa17884 --- Libraries/Image/Image.android.js | 41 ++++++++++++ Libraries/Image/Image.ios.js | 30 +++++++++ Libraries/Image/RCTImageViewManager.m | 15 +++++ .../com/facebook/react/modules/image/BUCK | 1 + .../modules/image/ImageLoaderModule.java | 63 +++++++++++++++++++ 5 files changed, 150 insertions(+) diff --git a/Libraries/Image/Image.android.js b/Libraries/Image/Image.android.js index 422aeda2d7e77c..dcfd770b0e5c49 100644 --- a/Libraries/Image/Image.android.js +++ b/Libraries/Image/Image.android.js @@ -123,6 +123,11 @@ const ImageProps = { ]), }; +/** + * Retrieve the width and height (in pixels) of an image prior to displaying it + * + * See https://facebook.github.io/react-native/docs/image.html#getsize + */ function getSize( url: string, success: (width: number, height: number) => void, @@ -140,6 +145,30 @@ function getSize( ); } +/** + * Retrieve the width and height (in pixels) of an image prior to displaying it + * with the ability to provide the headers for the request + * + * See https://facebook.github.io/react-native/docs/image.html#getsizewithheaders + */ +function getSizeWithHeaders( + url: string, + headers: {[string]: string}, + success: (width: number, height: number) => void, + failure?: (error: any) => void, +) { + return ImageLoader.getSizeWithHeaders(url, headers) + .then(function(sizes) { + success(sizes.width, sizes.height); + }) + .catch( + failure || + function() { + console.warn('Failed to get size for image: ' + url); + }, + ); +} + function prefetch(url: string, callback: ?Function) { const requestId = generateRequestId(); callback && callback(requestId); @@ -165,6 +194,7 @@ declare class ImageComponentType extends ReactNative.NativeComponent< ImagePropsType, > { static getSize: typeof getSize; + static getSizeWithHeaders: typeof getSizeWithHeaders; static prefetch: typeof prefetch; static abortPrefetch: typeof abortPrefetch; static queryCache: typeof queryCache; @@ -271,6 +301,17 @@ Image.displayName = 'Image'; * comment and run Flow. */ Image.getSize = getSize; +/** + * Retrieve the width and height (in pixels) of an image prior to displaying it + * with the ability to provide the headers for the request + * + * See https://facebook.github.io/react-native/docs/image.html#getsizewithheaders + */ +/* $FlowFixMe(>=0.89.0 site=react_native_android_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ +Image.getSizeWithHeaders = getSizeWithHeaders; + /** * Prefetches a remote image for later use by downloading it to the disk * cache diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 7fb0462bcf6af2..72c5d31fdb230a 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -42,6 +42,24 @@ function getSize( ); } +function getSizeWithHeaders( + uri: string, + headers: {[string]: string}, + success: (width: number, height: number) => void, + failure?: (error: any) => void, +) { + return ImageViewManager.getSizeWithHeaders({uri, headers}) + .then(function(sizes) { + success(sizes.width, sizes.height); + }) + .catch( + failure || + function() { + console.warn('Failed to get size for image: ' + uri); + }, + ); +} + function prefetch(url: string) { return ImageViewManager.prefetchImage(url); } @@ -56,6 +74,7 @@ declare class ImageComponentType extends ReactNative.NativeComponent< ImagePropsType, > { static getSize: typeof getSize; + static getSizeWithHeaders: typeof getSizeWithHeaders; static prefetch: typeof prefetch; static queryCache: typeof queryCache; static resolveAssetSource: typeof resolveAssetSource; @@ -136,6 +155,17 @@ Image.displayName = 'Image'; * comment and run Flow. */ Image.getSize = getSize; +/** + * Retrieve the width and height (in pixels) of an image prior to displaying it + * with the ability to provide the headers for the request. + * + * See https://facebook.github.io/react-native/docs/image.html#getsizewithheaders + */ +/* $FlowFixMe(>=0.89.0 site=react_native_ios_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ +Image.getSizeWithHeaders = getSizeWithHeaders; + /** * Prefetches a remote image for later use by downloading it to the disk * cache. diff --git a/Libraries/Image/RCTImageViewManager.m b/Libraries/Image/RCTImageViewManager.m index 70c5cb98cd52ad..7d92500f4e7934 100644 --- a/Libraries/Image/RCTImageViewManager.m +++ b/Libraries/Image/RCTImageViewManager.m @@ -10,6 +10,7 @@ #import #import +#import #import "RCTImageLoader.h" #import "RCTImageShadowView.h" @@ -63,6 +64,20 @@ - (UIView *)view }]; } +RCT_EXPORT_METHOD(getSizeWithHeaders:(RCTImageSource *)source + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +{ + [self.bridge.imageLoader getImageSizeForURLRequest:source.request + block:^(NSError *error, CGSize size) { + if (error) { + reject(@"E_GET_SIZE_FAILURE", nil, error); + return; + } + resolve(@{@"width":@(size.width),@"height":@(size.height)}); + }]; +} + RCT_EXPORT_METHOD(prefetchImage:(NSURLRequest *)request resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/image/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/image/BUCK index 030dafb0faa714..7f98a94b57beb0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/image/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/image/BUCK @@ -15,6 +15,7 @@ rn_android_library( react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_target("java/com/facebook/react/bridge:bridge"), react_native_target("java/com/facebook/react/common:common"), + react_native_target("java/com/facebook/react/modules/fresco:fresco"), react_native_target("java/com/facebook/react/module/annotations:annotations"), ], ) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.java index e6d5a49d0506c6..6a4c9c7cee6ed0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.java @@ -22,6 +22,7 @@ import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequestBuilder; +import com.facebook.react.modules.fresco.ReactNetworkImageRequest; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.GuardedAsyncTask; import com.facebook.react.bridge.LifecycleEventListener; @@ -30,6 +31,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; import com.facebook.react.module.annotations.ReactModule; @@ -119,6 +121,67 @@ protected void onFailureImpl(DataSource> data dataSource.subscribe(dataSubscriber, CallerThreadExecutor.getInstance()); } + /** + * Fetch the width and height of the given image with headers. + * + * @param uriString the URI of the remote image to prefetch + * @param headers headers send with the request + * @param promise the promise that is fulfilled when the image is successfully prefetched + * or rejected when there is an error + */ + @ReactMethod + public void getSizeWithHeaders( + final String uriString, + final ReadableMap headers, + final Promise promise) { + if (uriString == null || uriString.isEmpty()) { + promise.reject(ERROR_INVALID_URI, "Cannot get the size of an image for an empty URI"); + return; + } + + Uri uri = Uri.parse(uriString); + ImageRequestBuilder imageRequestBuilder = ImageRequestBuilder.newBuilderWithSource(uri); + ImageRequest request = ReactNetworkImageRequest.fromBuilderWithHeaders(imageRequestBuilder, headers); + + DataSource> dataSource = + Fresco.getImagePipeline().fetchDecodedImage(request, mCallerContext); + + DataSubscriber> dataSubscriber = + new BaseDataSubscriber>() { + @Override + protected void onNewResultImpl( + DataSource> dataSource) { + if (!dataSource.isFinished()) { + return; + } + CloseableReference ref = dataSource.getResult(); + if (ref != null) { + try { + CloseableImage image = ref.get(); + + WritableMap sizes = Arguments.createMap(); + sizes.putInt("width", image.getWidth()); + sizes.putInt("height", image.getHeight()); + + promise.resolve(sizes); + } catch (Exception e) { + promise.reject(ERROR_GET_SIZE_FAILURE, e); + } finally { + CloseableReference.closeSafely(ref); + } + } else { + promise.reject(ERROR_GET_SIZE_FAILURE); + } + } + + @Override + protected void onFailureImpl(DataSource> dataSource) { + promise.reject(ERROR_GET_SIZE_FAILURE, dataSource.getFailureCause()); + } + }; + dataSource.subscribe(dataSubscriber, CallerThreadExecutor.getInstance()); + } + /** * Prefetches the given image to the Fresco image disk cache. *