diff --git a/Examples/UIExplorer/XHRExample.android.js b/Examples/UIExplorer/XHRExample.android.js
index 7cadaa4ff..991f88d45 100644
--- a/Examples/UIExplorer/XHRExample.android.js
+++ b/Examples/UIExplorer/XHRExample.android.js
@@ -24,6 +24,8 @@ var {
TextInput,
TouchableHighlight,
View,
+ Image,
+ CameraRoll
} = ReactNative;
var XHRExampleHeaders = require('./XHRExampleHeaders');
@@ -127,6 +129,8 @@ class Downloader extends React.Component {
}
}
+var PAGE_SIZE = 20;
+
class FormUploader extends React.Component {
_isMounted: boolean;
@@ -143,6 +147,8 @@ class FormUploader extends React.Component {
this._isMounted = true;
this._addTextParam = this._addTextParam.bind(this);
this._upload = this._upload.bind(this);
+ this._fetchRandomPhoto = this._fetchRandomPhoto.bind(this);
+ this._fetchRandomPhoto();
}
_addTextParam() {
@@ -151,6 +157,25 @@ class FormUploader extends React.Component {
this.setState({textParams});
}
+ _fetchRandomPhoto() {
+ CameraRoll.getPhotos(
+ {first: PAGE_SIZE}
+ ).then(
+ (data) => {
+ if (!this._isMounted) {
+ return;
+ }
+ var edges = data.edges;
+ var edge = edges[Math.floor(Math.random() * edges.length)];
+ var randomPhoto = edge && edge.node && edge.node.image;
+ if (randomPhoto) {
+ this.setState({randomPhoto});
+ }
+ },
+ (error) => undefined
+ );
+ }
+
componentWillUnmount() {
this._isMounted = false;
}
@@ -201,19 +226,29 @@ class FormUploader extends React.Component {
this.state.textParams.forEach(
(param) => formdata.append(param.name, param.value)
);
- if (xhr.upload) {
- xhr.upload.onprogress = (event) => {
- console.log('upload onprogress', event);
- if (event.lengthComputable) {
- this.setState({uploadProgress: event.loaded / event.total});
- }
- };
+ if (this.state.randomPhoto) {
+ formdata.append('image', {...this.state.randomPhoto, type:'image/jpg', name: 'image.jpg'});
}
+ xhr.upload.onprogress = (event) => {
+ console.log('upload onprogress', event);
+ if (event.lengthComputable) {
+ this.setState({uploadProgress: event.loaded / event.total});
+ }
+ };
xhr.send(formdata);
this.setState({isUploading: true});
}
render() {
+ var image = null;
+ if (this.state.randomPhoto) {
+ image = (
+
+ );
+ }
var textItems = this.state.textParams.map((item, index) => (
+
+
+ Random photo from your library
+ (
+ update
+ )
+
+ {image}
+
{textItems}
formdata.append(param.name, param.value)
);
- if (xhr.upload) {
- xhr.upload.onprogress = (event) => {
- console.log('upload onprogress', event);
- if (event.lengthComputable) {
- this.setState({uploadProgress: event.loaded / event.total});
- }
- };
- }
+ xhr.upload.onprogress = (event) => {
+ console.log('upload onprogress', event);
+ if (event.lengthComputable) {
+ this.setState({uploadProgress: event.loaded / event.total});
+ }
+ };
+
xhr.send(formdata);
this.setState({isUploading: true});
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java
index 97a803965..91afa7862 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java
@@ -56,7 +56,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
private static final String REQUEST_BODY_KEY_URI = "uri";
private static final String REQUEST_BODY_KEY_FORMDATA = "formData";
private static final String USER_AGENT_HEADER_NAME = "user-agent";
-
+ private static final int CHUNK_TIMEOUT_NS = 100 * 1000000; // 100ms
private static final int MAX_CHUNK_SIZE_BETWEEN_FLUSHES = 8 * 1024; // 8K
private final OkHttpClient mClient;
@@ -239,7 +239,19 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
if (multipartBuilder == null) {
return;
}
- requestBuilder.method(method, multipartBuilder.build());
+
+ requestBuilder.method(method, RequestBodyUtil.createProgressRequest(multipartBuilder.build(), new ProgressRequestListener() {
+ long last = System.nanoTime();
+
+ @Override
+ public void onRequestProgress(long bytesWritten, long contentLength, boolean done) {
+ long now = System.nanoTime();
+ if (done || shouldDispatch(now, last)) {
+ onDataSend(executorToken, requestId, bytesWritten,contentLength);
+ last = now;
+ }
+ }
+ }));
} else {
// Nothing in data payload, at least nothing we could understand anyway.
requestBuilder.method(method, RequestBodyUtil.getEmptyBody(method));
@@ -298,6 +310,18 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
}
}
+ private static boolean shouldDispatch(long now, long last) {
+ return last + CHUNK_TIMEOUT_NS < now;
+ }
+
+ private void onDataSend(ExecutorToken ExecutorToken, int requestId, long progress, long total) {
+ WritableArray args = Arguments.createArray();
+ args.pushInt(requestId);
+ args.pushInt((int) progress);
+ args.pushInt((int) total);
+ getEventEmitter(ExecutorToken).emit("didSendNetworkData", args);
+ }
+
private void onDataReceived(ExecutorToken ExecutorToken, int requestId, String data) {
WritableArray args = Arguments.createArray();
args.pushInt(requestId);
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressRequestBody.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressRequestBody.java
new file mode 100644
index 000000000..0e511f926
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressRequestBody.java
@@ -0,0 +1,70 @@
+/**
+ * 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.network;
+
+import java.io.IOException;
+import okhttp3.MediaType;
+import okhttp3.RequestBody;
+import okhttp3.internal.Util;
+import okio.BufferedSink;
+import okio.Buffer;
+import okio.Sink;
+import okio.ForwardingSink;
+import okio.ByteString;
+import okio.Okio;
+import okio.Source;
+
+public class ProgressRequestBody extends RequestBody {
+
+ private final RequestBody mRequestBody;
+ private final ProgressRequestListener mProgressListener;
+ private BufferedSink mBufferedSink;
+
+ public ProgressRequestBody(RequestBody requestBody, ProgressRequestListener progressListener) {
+ mRequestBody = requestBody;
+ mProgressListener = progressListener;
+ }
+
+ @Override
+ public MediaType contentType() {
+ return mRequestBody.contentType();
+ }
+
+ @Override
+ public long contentLength() throws IOException {
+ return mRequestBody.contentLength();
+ }
+
+ @Override
+ public void writeTo(BufferedSink sink) throws IOException {
+ if (mBufferedSink == null) {
+ mBufferedSink = Okio.buffer(sink(sink));
+ }
+ mRequestBody.writeTo(mBufferedSink);
+ mBufferedSink.flush();
+ }
+
+ private Sink sink(Sink sink) {
+ return new ForwardingSink(sink) {
+ long bytesWritten = 0L;
+ long contentLength = 0L;
+
+ @Override
+ public void write(Buffer source, long byteCount) throws IOException {
+ super.write(source, byteCount);
+ if (contentLength == 0) {
+ contentLength = contentLength();
+ }
+ bytesWritten += byteCount;
+ mProgressListener.onRequestProgress(bytesWritten, contentLength, bytesWritten == contentLength);
+ }
+ };
+ }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressRequestListener.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressRequestListener.java
new file mode 100644
index 000000000..10230e6dc
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ProgressRequestListener.java
@@ -0,0 +1,15 @@
+/**
+ * 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.network;
+
+
+public interface ProgressRequestListener {
+ void onRequestProgress(long bytesWritten, long contentLength, boolean done);
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java
index 0290a23fd..1d5a5e1d9 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java
@@ -114,6 +114,13 @@ import okio.Source;
};
}
+ /**
+ * Creates a ProgressRequestBody that can be used for showing uploading progress
+ */
+ public static ProgressRequestBody createProgressRequest(RequestBody requestBody, ProgressRequestListener listener) {
+ return new ProgressRequestBody(requestBody, listener);
+ }
+
/**
* Creates a empty RequestBody if required by the http method spec, otherwise use null
*/
diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java
index ec04b5cbd..8e5f40cb7 100644
--- a/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java
+++ b/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java
@@ -60,6 +60,8 @@ import static org.mockito.Mockito.when;
Arguments.class,
Call.class,
RequestBodyUtil.class,
+ ProgressRequestBody.class,
+ ProgressRequestListener.class,
MultipartBody.class,
MultipartBody.Builder.class,
NetworkingModule.class,
@@ -262,6 +264,7 @@ public class NetworkingModuleTest {
.thenReturn(mock(InputStream.class));
when(RequestBodyUtil.create(any(MediaType.class), any(InputStream.class)))
.thenReturn(mock(RequestBody.class));
+ when(RequestBodyUtil.createProgressRequest(any(RequestBody.class), any(ProgressRequestListener.class))).thenCallRealMethod();
JavaOnlyMap body = new JavaOnlyMap();
JavaOnlyArray formData = new JavaOnlyArray();
@@ -316,6 +319,7 @@ public class NetworkingModuleTest {
.thenReturn(mock(InputStream.class));
when(RequestBodyUtil.create(any(MediaType.class), any(InputStream.class)))
.thenReturn(mock(RequestBody.class));
+ when(RequestBodyUtil.createProgressRequest(any(RequestBody.class), any(ProgressRequestListener.class))).thenCallRealMethod();
List headers = Arrays.asList(
JavaOnlyArray.of("Accept", "text/plain"),
@@ -378,6 +382,7 @@ public class NetworkingModuleTest {
when(RequestBodyUtil.getFileInputStream(any(ReactContext.class), any(String.class)))
.thenReturn(inputStream);
when(RequestBodyUtil.create(any(MediaType.class), any(InputStream.class))).thenCallRealMethod();
+ when(RequestBodyUtil.createProgressRequest(any(RequestBody.class), any(ProgressRequestListener.class))).thenCallRealMethod();
when(inputStream.available()).thenReturn("imageUri".length());
final MultipartBody.Builder multipartBuilder = mock(MultipartBody.Builder.class);