Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Image] Add a way to prefetch remote images to cache with Image.prefetch #6774

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions Examples/UIExplorer/ImageExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,14 @@ var {
var base64Icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACSR7JhAAADtUlEQVR4Ac3YA2Bj6QLH0XPT1Fzbtm29tW3btm3bfLZtv7e2ObZnms7d8Uw098tuetPzrxv8wiISrtVudrG2JXQZ4VOv+qUfmqCGGl1mqLhoA52oZlb0mrjsnhKpgeUNEs91Z0pd1kvihA3ULGVHiQO2narKSHKkEMulm9VgUyE60s1aWoMQUbpZOWE+kaqs4eLEjdIlZTcFZB0ndc1+lhB1lZrIuk5P2aib1NBpZaL+JaOGIt0ls47SKzLC7CqrlGF6RZ09HGoNy1lYl2aRSWL5GuzqWU1KafRdoRp0iOQEiDzgZPnG6DbldcomadViflnl/cL93tOoVbsOLVM2jylvdWjXolWX1hmfZbGR/wjypDjFLSZIRov09BgYmtUqPQPlQrPapecLgTIy0jMgPKtTeob2zWtrGH3xvjUkPCtNg/tm1rjwrMa+mdUkPd3hWbH0jArPGiU9ufCsNNWFZ40wpwn+62/66R2RUtoso1OB34tnLOcy7YB1fUdc9e0q3yru8PGM773vXsuZ5YIZX+5xmHwHGVvlrGPN6ZSiP1smOsMMde40wKv2VmwPPVXNut4sVpUreZiLBHi0qln/VQeI/LTMYXpsJtFiclUN+5HVZazim+Ky+7sAvxWnvjXrJFneVtLWLyPJu9K3cXLWeOlbMTlrIelbMDlrLenrjEQOtIF+fuI9xRp9ZBFp6+b6WT8RrxEpdK64BuvHgDk+vUy+b5hYk6zfyfs051gRoNO1usU12WWRWL73/MMEy9pMi9qIrR4ZpV16Rrvduxazmy1FSvuFXRkqTnE7m2kdb5U8xGjLw/spRr1uTov4uOgQE+0N/DvFrG/Jt7i/FzwxbA9kDanhf2w+t4V97G8lrT7wc08aA2QNUkuTfW/KimT01wdlfK4yEw030VfT0RtZbzjeMprNq8m8tnSTASrTLti64oBNdpmMQm0eEwvfPwRbUBywG5TzjPCsdwk3IeAXjQblLCoXnDVeoAz6SfJNk5TTzytCNZk/POtTSV40NwOFWzw86wNJRpubpXsn60NJFlHeqlYRbslqZm2jnEZ3qcSKgm0kTli3zZVS7y/iivZTweYXJ26Y+RTbV1zh3hYkgyFGSTKPfRVbRqWWVReaxYeSLarYv1Qqsmh1s95S7G+eEWK0f3jYKTbV6bOwepjfhtafsvUsqrQvrGC8YhmnO9cSCk3yuY984F1vesdHYhWJ5FvASlacshUsajFt2mUM9pqzvKGcyNJW0arTKN1GGGzQlH0tXwLDgQTurS8eIQAAAABJRU5ErkJggg==';

var ImageCapInsetsExample = require('./ImageCapInsetsExample');
const IMAGE_PREFETCH_URL = 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1&t=' + Date.now();
var prefetchTask = Image.prefetch(IMAGE_PREFETCH_URL);

var NetworkImageCallbackExample = React.createClass({
getInitialState: function() {
return {
events: [],
startLoadPrefetched: false,
mountTime: new Date(),
};
},
Expand All @@ -58,9 +61,26 @@ var NetworkImageCallbackExample = React.createClass({
style={[styles.base, {overflow: 'visible'}]}
onLoadStart={() => this._loadEventFired(`✔ onLoadStart (+${new Date() - mountTime}ms)`)}
onLoad={() => this._loadEventFired(`✔ onLoad (+${new Date() - mountTime}ms)`)}
onLoadEnd={() => this._loadEventFired(`✔ onLoadEnd (+${new Date() - mountTime}ms)`)}
onLoadEnd={() => {
this._loadEventFired(`✔ onLoadEnd (+${new Date() - mountTime}ms)`);
this.setState({startLoadPrefetched: true}, () => {
prefetchTask.then(() => {
this._loadEventFired(`✔ Prefetch OK (+${new Date() - mountTime}ms)`);
}, error => {
this._loadEventFired(`✘ Prefetch failed (+${new Date() - mountTime}ms)`);
});
});
}}
/>

{this.state.startLoadPrefetched ?
<Image
source={this.props.prefetchedSource}
style={[styles.base, {overflow: 'visible'}]}
onLoadStart={() => this._loadEventFired(`✔ (prefetched) onLoadStart (+${new Date() - mountTime}ms)`)}
onLoad={() => this._loadEventFired(`✔ (prefetched) onLoad (+${new Date() - mountTime}ms)`)}
onLoadEnd={() => this._loadEventFired(`✔ (prefetched) onLoadEnd (+${new Date() - mountTime}ms)`)}
/>
: null}
<Text style={{marginTop: 20}}>
{this.state.events.join('\n')}
</Text>
Expand Down Expand Up @@ -173,7 +193,8 @@ exports.examples = [
title: 'Image Loading Events',
render: function() {
return (
<NetworkImageCallbackExample source={{uri: 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1'}}/>
<NetworkImageCallbackExample source={{uri: 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1&t=' + Date.now()}}
prefetchedSource={{uri: IMAGE_PREFETCH_URL}}/>
);
},
},
Expand Down
12 changes: 11 additions & 1 deletion Libraries/Image/Image.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ var StyleSheetPropType = require('StyleSheetPropType');
var View = require('View');

var flattenStyle = require('flattenStyle');
var invariant = require('fbjs/lib/invariant');
var merge = require('merge');
var requireNativeComponent = require('requireNativeComponent');
var resolveAssetSource = require('resolveAssetSource');

var {
ImageLoader,
} = NativeModules;

/**
* <Image> - A react component for displaying different types of images,
* including network images, static resources, temporary local images, and
Expand Down Expand Up @@ -110,6 +113,13 @@ var Image = React.createClass({

statics: {
resizeMode: ImageResizeMode,
/**
* Prefetches a remote image for later use by downloading it to the disk
* cache
*/
prefetch(url: string) {
return ImageLoader.prefetchImage(url);
},
},

mixins: [NativeMethodsMixin],
Expand Down
16 changes: 11 additions & 5 deletions Libraries/Image/Image.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,22 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType');
var ImageResizeMode = require('ImageResizeMode');
var ImageStylePropTypes = require('ImageStylePropTypes');
var NativeMethodsMixin = require('NativeMethodsMixin');
var NativeModules = require('NativeModules');
var PropTypes = require('ReactPropTypes');
var React = require('React');
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
var View = require('View');
var StyleSheet = require('StyleSheet');
var StyleSheetPropType = require('StyleSheetPropType');

var flattenStyle = require('flattenStyle');
var invariant = require('fbjs/lib/invariant');
var requireNativeComponent = require('requireNativeComponent');
var resolveAssetSource = require('resolveAssetSource');
var warning = require('fbjs/lib/warning');

var {
ImageLoader,
ImageViewManager,
NetworkImageViewManager,
} = require('NativeModules');
} = NativeModules;

/**
* A React component for displaying different types of images,
Expand Down Expand Up @@ -181,7 +180,14 @@ var Image = React.createClass({
ImageViewManager.getSize(uri, success, failure || function() {
console.warn('Failed to get size for image: ' + uri);
});
}
},
/**
* Prefetches a remote image for later use by downloading it to the disk
* cache
*/
prefetch(url: string) {
return ImageLoader.prefetchImage(url);
},
},

mixins: [NativeMethodsMixin],
Expand Down
24 changes: 24 additions & 0 deletions Libraries/Image/RCTImageLoader.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
#import "RCTNetworking.h"
#import "RCTUtils.h"

static NSString *const RCTErrorInvalidURI = @"E_INVALID_URI";
static NSString *const RCTErrorPrefetchFailure = @"E_PREFETCH_FAILURE";

@implementation UIImage (React)

- (CAKeyframeAnimation *)reactKeyframeAnimation
Expand Down Expand Up @@ -634,6 +637,27 @@ - (RCTImageLoaderCancellationBlock)getImageSize:(NSString *)imageTag
}];
}

#pragma mark - Bridged methods

RCT_EXPORT_METHOD(prefetchImage:(NSString *)uri
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
{
if (!uri.length) {
reject(RCTErrorInvalidURI, @"Cannot prefetch an image for an empty URI", nil);
return;
}

[_bridge.imageLoader loadImageWithTag:uri callback:^(NSError *error, UIImage *image) {
if (error) {
reject(RCTErrorPrefetchFailure, nil, error);
return;
}

resolve(@YES);
}];
}

#pragma mark - RCTURLRequestHandler

- (BOOL)canHandleRequest:(NSURLRequest *)request
Expand Down
23 changes: 23 additions & 0 deletions ReactAndroid/src/main/java/com/facebook/react/modules/image/BUCK
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
include_defs('//ReactAndroid/DEFS')

android_library(
name = 'image',
srcs = glob(['*.java']),
deps = [
react_native_target('java/com/facebook/react/bridge:bridge'),
react_native_target('java/com/facebook/react/common:common'),
react_native_dep('libraries/fresco/fresco-react-native:fbcore'),
react_native_dep('libraries/fresco/fresco-react-native:fresco-react-native'),
react_native_dep('libraries/fresco/fresco-react-native:fresco-drawee'),
react_native_dep('libraries/fresco/fresco-react-native:imagepipeline'),
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
react_native_dep('third-party/java/jsr-305:jsr-305'),
],
visibility = [
'PUBLIC',
],
)

project_config(
src_target = ':image',
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

package com.facebook.react.modules.image;

import android.net.Uri;

import com.facebook.common.executors.CallerThreadExecutor;
import com.facebook.datasource.BaseDataSubscriber;
import com.facebook.datasource.DataSource;
import com.facebook.datasource.DataSubscriber;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

public class ImageLoaderModule extends ReactContextBaseJavaModule {

private static final String ERROR_INVALID_URI = "E_INVALID_URI";
private static final String ERROR_PREFETCH_FAILURE = "E_PREFETCH_FAILURE";

public ImageLoaderModule(ReactApplicationContext reactContext) {
super(reactContext);
}

@Override
public String getName() {
return "ImageLoader";
}

/**
* Prefetches the given image to the Fresco image disk cache.
*
* @param uriString the URI of the remote image to prefetch
* @param promise the promise that is fulfilled when the image is successfully prefetched
* or rejected when there is an error
*/
@ReactMethod
public void prefetchImage(String uriString, final Promise promise) {
if (uriString == null || uriString.isEmpty()) {
promise.reject(ERROR_INVALID_URI, "Cannot prefetch an image for an empty URI");
return;
}

Uri uri = Uri.parse(uriString);
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri).build();

DataSource<Void> prefetchSource = Fresco.getImagePipeline().prefetchToDiskCache(request, this);
DataSubscriber<Void> prefetchSubscriber = new BaseDataSubscriber<Void>() {
@Override
protected void onNewResultImpl(DataSource<Void> dataSource) {
if (!dataSource.isFinished()) {
return;
}
try {
promise.resolve(true);
} finally {
dataSource.close();
}
}

@Override
protected void onFailureImpl(DataSource<Void> dataSource) {
try {
promise.reject(ERROR_PREFETCH_FAILURE, dataSource.getFailureCause());
} finally {
dataSource.close();
}
}
};
prefetchSource.subscribe(prefetchSubscriber, CallerThreadExecutor.getInstance());
}
}
1 change: 1 addition & 0 deletions ReactAndroid/src/main/java/com/facebook/react/shell/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ android_library(
react_native_target('java/com/facebook/react/modules/debug:debug'),
react_native_target('java/com/facebook/react/modules/dialog:dialog'),
react_native_target('java/com/facebook/react/modules/fresco:fresco'),
react_native_target('java/com/facebook/react/modules/image:image'),
react_native_target('java/com/facebook/react/modules/intent:intent'),
react_native_target('java/com/facebook/react/modules/location:location'),
react_native_target('java/com/facebook/react/modules/netinfo:netinfo'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.facebook.react.modules.datepicker.DatePickerDialogModule;
import com.facebook.react.modules.dialog.DialogModule;
import com.facebook.react.modules.fresco.FrescoModule;
import com.facebook.react.modules.image.ImageLoaderModule;
import com.facebook.react.modules.intent.IntentModule;
import com.facebook.react.modules.location.LocationModule;
import com.facebook.react.modules.netinfo.NetInfoModule;
Expand Down Expand Up @@ -76,6 +77,7 @@ public List<NativeModule> createNativeModules(ReactApplicationContext reactConte
new DialogModule(reactContext),
new FrescoModule(reactContext),
new ImageEditingManager(reactContext),
new ImageLoaderModule(reactContext),
new ImageStoreManager(reactContext),
new IntentModule(reactContext),
new LocationModule(reactContext),
Expand Down