Skip to content

Commit 4d4c48d

Browse files
lexsfacebook-github-bot-3
authored andcommitted
Send HEADERS_RECEIVED and LOADING events on Android
Summary: Send part of the response body every 100 ms if the client has set onreadystatechange. This is done by using the same events as the iOS code and removing the callback that Android previously used. Reconsolidate iOS and Android implementations. Closes #3772 public Reviewed By: mkonicek Differential Revision: D2647005 fb-gh-sync-id: d006e566867fa47d5f8dff71219cb390bcb8e15a
1 parent a99fabd commit 4d4c48d

7 files changed

Lines changed: 246 additions & 164 deletions

File tree

Examples/UIExplorer/XHRExample.android.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
var React = require('react-native');
1919
var {
2020
PixelRatio,
21+
ProgressBarAndroid,
2122
StyleSheet,
2223
Text,
2324
TextInput,
@@ -61,7 +62,6 @@ class Downloader extends React.Component {
6162
this.setState({
6263
downloaded: xhr.responseText.length,
6364
});
64-
console.log(xhr.responseText.length);
6565
} else if (xhr.readyState === xhr.DONE) {
6666
if (this.cancelled) {
6767
this.cancelled = false;
@@ -83,6 +83,8 @@ class Downloader extends React.Component {
8383
}
8484
};
8585
xhr.open('GET', 'http://www.gutenberg.org/cache/epub/100/pg100.txt');
86+
// Avoid gzip so we can actually show progress
87+
xhr.setRequestHeader('Accept-Encoding', '');
8688
xhr.send();
8789
this.xhr = xhr;
8890

@@ -114,6 +116,8 @@ class Downloader extends React.Component {
114116
return (
115117
<View>
116118
{button}
119+
<ProgressBarAndroid progress={(this.state.downloaded / this.state.contentSize)}
120+
styleAttr="Horizontal" indeterminate={false} />
117121
<Text>{this.state.status}</Text>
118122
</View>
119123
);

Libraries/Network/RCTNetworking.android.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ var generateRequestId = function() {
2525
*/
2626
class RCTNetworking {
2727

28-
static sendRequest(method, url, headers, data, callback) {
28+
static sendRequest(method, url, headers, data, useIncrementalUpdates) {
2929
var requestId = generateRequestId();
3030
RCTNetworkingNative.sendRequest(
3131
method,
3232
url,
3333
requestId,
3434
headers,
3535
data,
36-
callback);
36+
useIncrementalUpdates);
3737
return requestId;
3838
}
3939

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @providesModule RCTNetworking
10+
*/
11+
'use strict';
12+
13+
module.exports = require('NativeModules').Networking;

Libraries/Network/XMLHttpRequest.android.js

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,6 @@ function convertHeadersMapToArray(headers: Object): Array<Header> {
2626
}
2727

2828
class XMLHttpRequest extends XMLHttpRequestBase {
29-
30-
_requestId: ?number;
31-
32-
constructor() {
33-
super();
34-
this._requestId = null;
35-
}
36-
3729
sendImpl(method: ?string, url: ?string, headers: Object, data: any): void {
3830
var body;
3931
if (typeof data === 'string') {
@@ -49,17 +41,15 @@ class XMLHttpRequest extends XMLHttpRequestBase {
4941
body = data;
5042
}
5143

52-
this._requestId = RCTNetworking.sendRequest(
44+
var useIncrementalUpdates = this.onreadystatechange ? true : false;
45+
var requestId = RCTNetworking.sendRequest(
5346
method,
5447
url,
5548
convertHeadersMapToArray(headers),
5649
body,
57-
this.callback.bind(this)
50+
useIncrementalUpdates
5851
);
59-
}
60-
61-
abortImpl(): void {
62-
this._requestId && RCTNetworking.abortRequest(this._requestId);
52+
this.didCreateRequest(requestId);
6353
}
6454
}
6555

Libraries/Network/XMLHttpRequest.ios.js

Lines changed: 3 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -12,95 +12,18 @@
1212
'use strict';
1313

1414
var FormData = require('FormData');
15-
var RCTNetworking = require('NativeModules').Networking;
15+
var RCTNetworking = require('RCTNetworking');
1616
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
1717

1818
var XMLHttpRequestBase = require('XMLHttpRequestBase');
1919

2020
class XMLHttpRequest extends XMLHttpRequestBase {
21-
22-
_requestId: ?number;
23-
_subscriptions: [any];
24-
upload: {
25-
onprogress?: (event: Object) => void;
26-
};
27-
2821
constructor() {
2922
super();
30-
this._requestId = null;
31-
this._subscriptions = [];
23+
// iOS supports upload
3224
this.upload = {};
3325
}
3426

35-
_didCreateRequest(requestId: number): void {
36-
this._requestId = requestId;
37-
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
38-
'didSendNetworkData',
39-
(args) => this._didUploadProgress.call(this, args[0], args[1], args[2])
40-
));
41-
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
42-
'didReceiveNetworkResponse',
43-
(args) => this._didReceiveResponse.call(this, args[0], args[1], args[2])
44-
));
45-
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
46-
'didReceiveNetworkData',
47-
(args) => this._didReceiveData.call(this, args[0], args[1])
48-
));
49-
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
50-
'didCompleteNetworkResponse',
51-
(args) => this._didCompleteResponse.call(this, args[0], args[1])
52-
));
53-
}
54-
55-
_didUploadProgress(requestId: number, progress: number, total: number): void {
56-
if (requestId === this._requestId && this.upload.onprogress) {
57-
var event = {
58-
lengthComputable: true,
59-
loaded: progress,
60-
total,
61-
};
62-
this.upload.onprogress(event);
63-
}
64-
}
65-
66-
_didReceiveResponse(requestId: number, status: number, responseHeaders: ?Object): void {
67-
if (requestId === this._requestId) {
68-
this.status = status;
69-
this.setResponseHeaders(responseHeaders);
70-
this.setReadyState(this.HEADERS_RECEIVED);
71-
}
72-
}
73-
74-
_didReceiveData(requestId: number, responseText: string): void {
75-
if (requestId === this._requestId) {
76-
if (!this.responseText) {
77-
this.responseText = responseText;
78-
} else {
79-
this.responseText += responseText;
80-
}
81-
this.setReadyState(this.LOADING);
82-
}
83-
}
84-
85-
_didCompleteResponse(requestId: number, error: string): void {
86-
if (requestId === this._requestId) {
87-
if (error) {
88-
this.responseText = error;
89-
}
90-
this._clearSubscriptions();
91-
this._requestId = null;
92-
this.setReadyState(this.DONE);
93-
}
94-
}
95-
96-
_clearSubscriptions(): void {
97-
for (var i = 0; i < this._subscriptions.length; i++) {
98-
var sub = this._subscriptions[i];
99-
sub.remove();
100-
}
101-
this._subscriptions = [];
102-
}
103-
10427
sendImpl(method: ?string, url: ?string, headers: Object, data: any): void {
10528
if (typeof data === 'string') {
10629
data = {string: data};
@@ -115,17 +38,9 @@ class XMLHttpRequest extends XMLHttpRequestBase {
11538
headers,
11639
incrementalUpdates: this.onreadystatechange ? true : false,
11740
},
118-
this._didCreateRequest.bind(this)
41+
this.didCreateRequest.bind(this)
11942
);
12043
}
121-
122-
abortImpl(): void {
123-
if (this._requestId) {
124-
RCTNetworking.cancelRequest(this._requestId);
125-
this._clearSubscriptions();
126-
this._requestId = null;
127-
}
128-
}
12944
}
13045

13146
module.exports = XMLHttpRequest;

Libraries/Network/XMLHttpRequestBase.js

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
*/
1212
'use strict';
1313

14+
var RCTNetworking = require('RCTNetworking');
15+
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
16+
1417
/**
1518
* Shared base for platform-specific XMLHttpRequest implementations.
1619
*/
@@ -30,6 +33,13 @@ class XMLHttpRequestBase {
3033
responseText: ?string;
3134
status: number;
3235

36+
upload: ?{
37+
onprogress?: (event: Object) => void;
38+
};
39+
40+
_requestId: ?number;
41+
_subscriptions: [any];
42+
3343
_method: ?string;
3444
_url: ?string;
3545
_headers: Object;
@@ -60,9 +70,81 @@ class XMLHttpRequestBase {
6070
this.responseText = '';
6171
this.status = 0;
6272

73+
this._requestId = null;
74+
6375
this._headers = {};
6476
this._sent = false;
6577
this._lowerCaseResponseHeaders = {};
78+
79+
this._clearSubscriptions();
80+
}
81+
82+
didCreateRequest(requestId: number): void {
83+
this._requestId = requestId;
84+
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
85+
'didSendNetworkData',
86+
(args) => this._didUploadProgress.call(this, ...args)
87+
));
88+
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
89+
'didReceiveNetworkResponse',
90+
(args) => this._didReceiveResponse.call(this, ...args)
91+
));
92+
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
93+
'didReceiveNetworkData',
94+
(args) => this._didReceiveData.call(this, ...args)
95+
));
96+
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
97+
'didCompleteNetworkResponse',
98+
(args) => this._didCompleteResponse.call(this, ...args)
99+
));
100+
}
101+
102+
_didUploadProgress(requestId: number, progress: number, total: number): void {
103+
if (requestId === this._requestId && this.upload && this.upload.onprogress) {
104+
var event = {
105+
lengthComputable: true,
106+
loaded: progress,
107+
total,
108+
};
109+
this.upload.onprogress(event);
110+
}
111+
}
112+
113+
_didReceiveResponse(requestId: number, status: number, responseHeaders: ?Object): void {
114+
if (requestId === this._requestId) {
115+
this.status = status;
116+
this.setResponseHeaders(responseHeaders);
117+
this.setReadyState(this.HEADERS_RECEIVED);
118+
}
119+
}
120+
121+
_didReceiveData(requestId: number, responseText: string): void {
122+
if (requestId === this._requestId) {
123+
if (!this.responseText) {
124+
this.responseText = responseText;
125+
} else {
126+
this.responseText += responseText;
127+
}
128+
this.setReadyState(this.LOADING);
129+
}
130+
}
131+
132+
_didCompleteResponse(requestId: number, error: string): void {
133+
if (requestId === this._requestId) {
134+
if (error) {
135+
this.responseText = error;
136+
}
137+
this._clearSubscriptions();
138+
this._requestId = null;
139+
this.setReadyState(this.DONE);
140+
}
141+
}
142+
143+
_clearSubscriptions(): void {
144+
(this._subscriptions || []).forEach(sub => {
145+
sub.remove();
146+
});
147+
this._subscriptions = [];
66148
}
67149

68150
getAllResponseHeaders(): ?string {
@@ -108,10 +190,6 @@ class XMLHttpRequestBase {
108190
throw new Error('Subclass must define sendImpl method');
109191
}
110192

111-
abortImpl(): void {
112-
throw new Error('Subclass must define abortImpl method');
113-
}
114-
115193
send(data: any): void {
116194
if (this.readyState !== this.OPENED) {
117195
throw new Error('Request has not been opened');
@@ -125,7 +203,10 @@ class XMLHttpRequestBase {
125203

126204
abort(): void {
127205
this._aborted = true;
128-
this.abortImpl();
206+
if (this._requestId) {
207+
console.log('calling abort', this._requestId);
208+
RCTNetworking.abortRequest(this._requestId);
209+
}
129210
// only call onreadystatechange if there is something to abort,
130211
// below logic is per spec
131212
if (!(this.readyState === this.UNSENT ||
@@ -138,16 +219,6 @@ class XMLHttpRequestBase {
138219
this._reset();
139220
}
140221

141-
callback(status: number, responseHeaders: ?Object, responseText: string): void {
142-
if (this._aborted) {
143-
return;
144-
}
145-
this.status = status;
146-
this.setResponseHeaders(responseHeaders || {});
147-
this.responseText = responseText;
148-
this.setReadyState(this.DONE);
149-
}
150-
151222
setResponseHeaders(responseHeaders: ?Object): void {
152223
this.responseHeaders = responseHeaders || null;
153224
var headers = responseHeaders || {};

0 commit comments

Comments
 (0)