diff --git a/Examples/UIExplorer/XHRExample.android.js b/Examples/UIExplorer/XHRExample.android.js
index 3696a2bfb..ab272d133 100644
--- a/Examples/UIExplorer/XHRExample.android.js
+++ b/Examples/UIExplorer/XHRExample.android.js
@@ -18,6 +18,7 @@
var React = require('react-native');
var {
PixelRatio,
+ ProgressBarAndroid,
StyleSheet,
Text,
TextInput,
@@ -61,7 +62,6 @@ class Downloader extends React.Component {
this.setState({
downloaded: xhr.responseText.length,
});
- console.log(xhr.responseText.length);
} else if (xhr.readyState === xhr.DONE) {
if (this.cancelled) {
this.cancelled = false;
@@ -83,6 +83,8 @@ class Downloader extends React.Component {
}
};
xhr.open('GET', 'http://www.gutenberg.org/cache/epub/100/pg100.txt');
+ // Avoid gzip so we can actually show progress
+ xhr.setRequestHeader('Accept-Encoding', '');
xhr.send();
this.xhr = xhr;
@@ -114,6 +116,8 @@ class Downloader extends React.Component {
return (
{button}
+
{this.state.status}
);
diff --git a/Libraries/Network/RCTNetworking.android.js b/Libraries/Network/RCTNetworking.android.js
index 8d21d8133..e8c4be0bd 100644
--- a/Libraries/Network/RCTNetworking.android.js
+++ b/Libraries/Network/RCTNetworking.android.js
@@ -25,7 +25,7 @@ var generateRequestId = function() {
*/
class RCTNetworking {
- static sendRequest(method, url, headers, data, callback) {
+ static sendRequest(method, url, headers, data, useIncrementalUpdates) {
var requestId = generateRequestId();
RCTNetworkingNative.sendRequest(
method,
@@ -33,7 +33,7 @@ class RCTNetworking {
requestId,
headers,
data,
- callback);
+ useIncrementalUpdates);
return requestId;
}
diff --git a/Libraries/Network/RCTNetworking.ios.js b/Libraries/Network/RCTNetworking.ios.js
new file mode 100644
index 000000000..75e85b4fd
--- /dev/null
+++ b/Libraries/Network/RCTNetworking.ios.js
@@ -0,0 +1,13 @@
+/**
+ * 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.
+ *
+ * @providesModule RCTNetworking
+ */
+'use strict';
+
+module.exports = require('NativeModules').Networking;
diff --git a/Libraries/Network/XMLHttpRequest.android.js b/Libraries/Network/XMLHttpRequest.android.js
index ba7a84ad9..e0ebc77d0 100644
--- a/Libraries/Network/XMLHttpRequest.android.js
+++ b/Libraries/Network/XMLHttpRequest.android.js
@@ -26,14 +26,6 @@ function convertHeadersMapToArray(headers: Object): Array {
}
class XMLHttpRequest extends XMLHttpRequestBase {
-
- _requestId: ?number;
-
- constructor() {
- super();
- this._requestId = null;
- }
-
sendImpl(method: ?string, url: ?string, headers: Object, data: any): void {
var body;
if (typeof data === 'string') {
@@ -49,17 +41,15 @@ class XMLHttpRequest extends XMLHttpRequestBase {
body = data;
}
- this._requestId = RCTNetworking.sendRequest(
+ var useIncrementalUpdates = this.onreadystatechange ? true : false;
+ var requestId = RCTNetworking.sendRequest(
method,
url,
convertHeadersMapToArray(headers),
body,
- this.callback.bind(this)
+ useIncrementalUpdates
);
- }
-
- abortImpl(): void {
- this._requestId && RCTNetworking.abortRequest(this._requestId);
+ this.didCreateRequest(requestId);
}
}
diff --git a/Libraries/Network/XMLHttpRequest.ios.js b/Libraries/Network/XMLHttpRequest.ios.js
index 29b5597ad..56b319a25 100644
--- a/Libraries/Network/XMLHttpRequest.ios.js
+++ b/Libraries/Network/XMLHttpRequest.ios.js
@@ -12,95 +12,18 @@
'use strict';
var FormData = require('FormData');
-var RCTNetworking = require('NativeModules').Networking;
+var RCTNetworking = require('RCTNetworking');
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var XMLHttpRequestBase = require('XMLHttpRequestBase');
class XMLHttpRequest extends XMLHttpRequestBase {
-
- _requestId: ?number;
- _subscriptions: [any];
- upload: {
- onprogress?: (event: Object) => void;
- };
-
constructor() {
super();
- this._requestId = null;
- this._subscriptions = [];
+ // iOS supports upload
this.upload = {};
}
- _didCreateRequest(requestId: number): void {
- this._requestId = requestId;
- this._subscriptions.push(RCTDeviceEventEmitter.addListener(
- 'didSendNetworkData',
- (args) => this._didUploadProgress.call(this, args[0], args[1], args[2])
- ));
- this._subscriptions.push(RCTDeviceEventEmitter.addListener(
- 'didReceiveNetworkResponse',
- (args) => this._didReceiveResponse.call(this, args[0], args[1], args[2])
- ));
- this._subscriptions.push(RCTDeviceEventEmitter.addListener(
- 'didReceiveNetworkData',
- (args) => this._didReceiveData.call(this, args[0], args[1])
- ));
- this._subscriptions.push(RCTDeviceEventEmitter.addListener(
- 'didCompleteNetworkResponse',
- (args) => this._didCompleteResponse.call(this, args[0], args[1])
- ));
- }
-
- _didUploadProgress(requestId: number, progress: number, total: number): void {
- if (requestId === this._requestId && this.upload.onprogress) {
- var event = {
- lengthComputable: true,
- loaded: progress,
- total,
- };
- this.upload.onprogress(event);
- }
- }
-
- _didReceiveResponse(requestId: number, status: number, responseHeaders: ?Object): void {
- if (requestId === this._requestId) {
- this.status = status;
- this.setResponseHeaders(responseHeaders);
- this.setReadyState(this.HEADERS_RECEIVED);
- }
- }
-
- _didReceiveData(requestId: number, responseText: string): void {
- if (requestId === this._requestId) {
- if (!this.responseText) {
- this.responseText = responseText;
- } else {
- this.responseText += responseText;
- }
- this.setReadyState(this.LOADING);
- }
- }
-
- _didCompleteResponse(requestId: number, error: string): void {
- if (requestId === this._requestId) {
- if (error) {
- this.responseText = error;
- }
- this._clearSubscriptions();
- this._requestId = null;
- this.setReadyState(this.DONE);
- }
- }
-
- _clearSubscriptions(): void {
- for (var i = 0; i < this._subscriptions.length; i++) {
- var sub = this._subscriptions[i];
- sub.remove();
- }
- this._subscriptions = [];
- }
-
sendImpl(method: ?string, url: ?string, headers: Object, data: any): void {
if (typeof data === 'string') {
data = {string: data};
@@ -115,17 +38,9 @@ class XMLHttpRequest extends XMLHttpRequestBase {
headers,
incrementalUpdates: this.onreadystatechange ? true : false,
},
- this._didCreateRequest.bind(this)
+ this.didCreateRequest.bind(this)
);
}
-
- abortImpl(): void {
- if (this._requestId) {
- RCTNetworking.cancelRequest(this._requestId);
- this._clearSubscriptions();
- this._requestId = null;
- }
- }
}
module.exports = XMLHttpRequest;
diff --git a/Libraries/Network/XMLHttpRequestBase.js b/Libraries/Network/XMLHttpRequestBase.js
index 0621589e8..e37bf6621 100644
--- a/Libraries/Network/XMLHttpRequestBase.js
+++ b/Libraries/Network/XMLHttpRequestBase.js
@@ -11,6 +11,9 @@
*/
'use strict';
+var RCTNetworking = require('RCTNetworking');
+var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
+
/**
* Shared base for platform-specific XMLHttpRequest implementations.
*/
@@ -30,6 +33,13 @@ class XMLHttpRequestBase {
responseText: ?string;
status: number;
+ upload: ?{
+ onprogress?: (event: Object) => void;
+ };
+
+ _requestId: ?number;
+ _subscriptions: [any];
+
_method: ?string;
_url: ?string;
_headers: Object;
@@ -60,9 +70,81 @@ class XMLHttpRequestBase {
this.responseText = '';
this.status = 0;
+ this._requestId = null;
+
this._headers = {};
this._sent = false;
this._lowerCaseResponseHeaders = {};
+
+ this._clearSubscriptions();
+ }
+
+ didCreateRequest(requestId: number): void {
+ this._requestId = requestId;
+ this._subscriptions.push(RCTDeviceEventEmitter.addListener(
+ 'didSendNetworkData',
+ (args) => this._didUploadProgress.call(this, ...args)
+ ));
+ this._subscriptions.push(RCTDeviceEventEmitter.addListener(
+ 'didReceiveNetworkResponse',
+ (args) => this._didReceiveResponse.call(this, ...args)
+ ));
+ this._subscriptions.push(RCTDeviceEventEmitter.addListener(
+ 'didReceiveNetworkData',
+ (args) => this._didReceiveData.call(this, ...args)
+ ));
+ this._subscriptions.push(RCTDeviceEventEmitter.addListener(
+ 'didCompleteNetworkResponse',
+ (args) => this._didCompleteResponse.call(this, ...args)
+ ));
+ }
+
+ _didUploadProgress(requestId: number, progress: number, total: number): void {
+ if (requestId === this._requestId && this.upload && this.upload.onprogress) {
+ var event = {
+ lengthComputable: true,
+ loaded: progress,
+ total,
+ };
+ this.upload.onprogress(event);
+ }
+ }
+
+ _didReceiveResponse(requestId: number, status: number, responseHeaders: ?Object): void {
+ if (requestId === this._requestId) {
+ this.status = status;
+ this.setResponseHeaders(responseHeaders);
+ this.setReadyState(this.HEADERS_RECEIVED);
+ }
+ }
+
+ _didReceiveData(requestId: number, responseText: string): void {
+ if (requestId === this._requestId) {
+ if (!this.responseText) {
+ this.responseText = responseText;
+ } else {
+ this.responseText += responseText;
+ }
+ this.setReadyState(this.LOADING);
+ }
+ }
+
+ _didCompleteResponse(requestId: number, error: string): void {
+ if (requestId === this._requestId) {
+ if (error) {
+ this.responseText = error;
+ }
+ this._clearSubscriptions();
+ this._requestId = null;
+ this.setReadyState(this.DONE);
+ }
+ }
+
+ _clearSubscriptions(): void {
+ (this._subscriptions || []).forEach(sub => {
+ sub.remove();
+ });
+ this._subscriptions = [];
}
getAllResponseHeaders(): ?string {
@@ -108,10 +190,6 @@ class XMLHttpRequestBase {
throw new Error('Subclass must define sendImpl method');
}
- abortImpl(): void {
- throw new Error('Subclass must define abortImpl method');
- }
-
send(data: any): void {
if (this.readyState !== this.OPENED) {
throw new Error('Request has not been opened');
@@ -125,7 +203,10 @@ class XMLHttpRequestBase {
abort(): void {
this._aborted = true;
- this.abortImpl();
+ if (this._requestId) {
+ console.log('calling abort', this._requestId);
+ RCTNetworking.abortRequest(this._requestId);
+ }
// only call onreadystatechange if there is something to abort,
// below logic is per spec
if (!(this.readyState === this.UNSENT ||
@@ -138,16 +219,6 @@ class XMLHttpRequestBase {
this._reset();
}
- callback(status: number, responseHeaders: ?Object, responseText: string): void {
- if (this._aborted) {
- return;
- }
- this.status = status;
- this.setResponseHeaders(responseHeaders || {});
- this.responseText = responseText;
- this.setReadyState(this.DONE);
- }
-
setResponseHeaders(responseHeaders: ?Object): void {
this.responseHeaders = responseHeaders || null;
var headers = responseHeaders || {};
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 3ebf4cd06..39d2cb810 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
@@ -13,17 +13,20 @@ import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
+import java.io.Reader;
import com.facebook.react.bridge.Arguments;
-import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.GuardedAsyncTask;
import com.facebook.react.bridge.ReactApplicationContext;
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.WritableArray;
import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.modules.core.DeviceEventManagerModule;
+import com.squareup.okhttp.Callback;
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.MultipartBuilder;
@@ -31,6 +34,9 @@ import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
+import com.squareup.okhttp.ResponseBody;
+
+import static java.lang.Math.min;
/**
* Implements the XMLHttpRequest JavaScript interface.
@@ -44,6 +50,11 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
private static final String REQUEST_BODY_KEY_FORMDATA = "formData";
private static final String USER_AGENT_HEADER_NAME = "user-agent";
+ private static final int MIN_BUFFER_SIZE = 8 * 1024; // 8kb
+ private static final int MAX_BUFFER_SIZE = 512 * 1024; // 512kb
+
+ private static final int CHUNK_TIMEOUT_NS = 100 * 1000000; // 100ms
+
private final OkHttpClient mClient;
private final @Nullable String mDefaultUserAgent;
private boolean mShuttingDown;
@@ -93,15 +104,10 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
public void sendRequest(
String method,
String url,
- int requestId,
+ final int requestId,
ReadableArray headers,
ReadableMap data,
- final Callback callback) {
- // We need to call the callback to avoid leaking memory on JS even when input for sending
- // request is erroneous or insufficient. For non-http based failures we use code 0, which is
- // interpreted as a transport error.
- // Callback accepts following arguments: responseCode, headersString, responseBody
-
+ final boolean useIncrementalUpdates) {
Request.Builder requestBuilder = new Request.Builder().url(url);
if (requestId != 0) {
@@ -110,7 +116,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
Headers requestHeaders = extractHeaders(headers, data);
if (requestHeaders == null) {
- callback.invoke(0, null, "Unrecognized headers format");
+ onRequestError(requestId, "Unrecognized headers format");
return;
}
String contentType = requestHeaders.get(CONTENT_TYPE_HEADER_NAME);
@@ -121,7 +127,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
requestBuilder.method(method, null);
} else if (data.hasKey(REQUEST_BODY_KEY_STRING)) {
if (contentType == null) {
- callback.invoke(0, null, "Payload is set but no content-type header specified");
+ onRequestError(requestId, "Payload is set but no content-type header specified");
return;
}
String body = data.getString(REQUEST_BODY_KEY_STRING);
@@ -129,7 +135,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
if (RequestBodyUtil.isGzipEncoding(contentEncoding)) {
RequestBody requestBody = RequestBodyUtil.createGzip(contentMediaType, body);
if (requestBody == null) {
- callback.invoke(0, null, "Failed to gzip request body");
+ onRequestError(requestId, "Failed to gzip request body");
return;
}
requestBuilder.method(method, requestBody);
@@ -138,14 +144,14 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
}
} else if (data.hasKey(REQUEST_BODY_KEY_URI)) {
if (contentType == null) {
- callback.invoke(0, null, "Payload is set but no content-type header specified");
+ onRequestError(requestId, "Payload is set but no content-type header specified");
return;
}
String uri = data.getString(REQUEST_BODY_KEY_URI);
InputStream fileInputStream =
RequestBodyUtil.getFileInputStream(getReactApplicationContext(), uri);
if (fileInputStream == null) {
- callback.invoke(0, null, "Could not retrieve file for uri " + uri);
+ onRequestError(requestId, "Could not retrieve file for uri " + uri);
return;
}
requestBuilder.method(
@@ -156,7 +162,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
contentType = "multipart/form-data";
}
ReadableArray parts = data.getArray(REQUEST_BODY_KEY_FORMDATA);
- MultipartBuilder multipartBuilder = constructMultipartBody(parts, contentType, callback);
+ MultipartBuilder multipartBuilder = constructMultipartBody(parts, contentType, requestId);
if (multipartBuilder == null) {
return;
}
@@ -168,16 +174,13 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
}
mClient.newCall(requestBuilder.build()).enqueue(
- new com.squareup.okhttp.Callback() {
+ new Callback() {
@Override
public void onFailure(Request request, IOException e) {
if (mShuttingDown) {
return;
}
- // We need to call the callback to avoid leaking memory on JS even when input for
- // sending request is erronous or insufficient. For non-http based failures we use
- // code 0, which is interpreted as a transport error
- callback.invoke(0, null, e.getMessage());
+ onRequestError(requestId, e.getMessage());
}
@Override
@@ -185,34 +188,115 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
if (mShuttingDown) {
return;
}
- String responseBody;
+
+ // Before we touch the body send headers to JS
+ onResponseReceived(requestId, response);
+
+ ResponseBody responseBody = response.body();
try {
- responseBody = response.body().string();
- } catch (IOException e) {
- // The stream has been cancelled or closed, nothing we can do
- callback.invoke(0, null, e.getMessage());
- return;
- }
-
- WritableMap responseHeaders = Arguments.createMap();
- Headers headers = response.headers();
- for (int i = 0; i < headers.size(); i++) {
- String headerName = headers.name(i);
- // multiple values for the same header
- if (responseHeaders.hasKey(headerName)) {
- responseHeaders.putString(
- headerName,
- responseHeaders.getString(headerName) + ", " + headers.value(i));
+ if (useIncrementalUpdates) {
+ readWithProgress(requestId, responseBody);
+ onRequestSuccess(requestId);
} else {
- responseHeaders.putString(headerName, headers.value(i));
+ onDataReceived(requestId, responseBody.string());
+ onRequestSuccess(requestId);
}
+ } catch (IOException e) {
+ onRequestError(requestId, e.getMessage());
}
-
- callback.invoke(response.code(), responseHeaders, responseBody);
}
});
}
+ private void readWithProgress(int requestId, ResponseBody responseBody) throws IOException {
+ Reader reader = responseBody.charStream();
+ try {
+ StringBuilder sb = new StringBuilder(getBufferSize(responseBody));
+ char[] buffer = new char[MIN_BUFFER_SIZE];
+ int read;
+ long last = System.nanoTime();
+ while ((read = reader.read(buffer)) != -1) {
+ sb.append(buffer, 0, read);
+ long now = System.nanoTime();
+ if (shouldDispatch(now, last)) {
+ onDataReceived(requestId, sb.toString());
+ sb.setLength(0);
+ last = now;
+ }
+ }
+
+ if (sb.length() > 0) {
+ onDataReceived(requestId, sb.toString());
+ }
+ } finally {
+ reader.close();
+ }
+ }
+
+ private static boolean shouldDispatch(long now, long last) {
+ return last + CHUNK_TIMEOUT_NS < now;
+ }
+
+ private static int getBufferSize(ResponseBody responseBody) throws IOException {
+ long length = responseBody.contentLength();
+ if (length == -1) {
+ return MIN_BUFFER_SIZE;
+ } else {
+ return (int) min(length, MAX_BUFFER_SIZE);
+ }
+ }
+
+ private void onDataReceived(int requestId, String data) {
+ WritableArray args = Arguments.createArray();
+ args.pushInt(requestId);
+ args.pushString(data);
+
+ getEventEmitter().emit("didReceiveNetworkData", args);
+ }
+
+ private void onRequestError(int requestId, String error) {
+ WritableArray args = Arguments.createArray();
+ args.pushInt(requestId);
+ args.pushString(error);
+
+ getEventEmitter().emit("didCompleteNetworkResponse", args);
+ }
+
+ private void onRequestSuccess(int requestId) {
+ WritableArray args = Arguments.createArray();
+ args.pushInt(requestId);
+ args.pushNull();
+
+ getEventEmitter().emit("didCompleteNetworkResponse", args);
+ }
+
+ private void onResponseReceived(int requestId, Response response) {
+ WritableMap headers = translateHeaders(response.headers());
+
+ WritableArray args = Arguments.createArray();
+ args.pushInt(requestId);
+ args.pushInt(response.code());
+ args.pushMap(headers);
+
+ getEventEmitter().emit("didReceiveNetworkResponse", args);
+ }
+
+ private static WritableMap translateHeaders(Headers headers) {
+ WritableMap responseHeaders = Arguments.createMap();
+ for (int i = 0; i < headers.size(); i++) {
+ String headerName = headers.name(i);
+ // multiple values for the same header
+ if (responseHeaders.hasKey(headerName)) {
+ responseHeaders.putString(
+ headerName,
+ responseHeaders.getString(headerName) + ", " + headers.value(i));
+ } else {
+ responseHeaders.putString(headerName, headers.value(i));
+ }
+ }
+ return responseHeaders;
+ }
+
@ReactMethod
public void abortRequest(final int requestId) {
// We have to use AsyncTask since this might trigger a NetworkOnMainThreadException, this is an
@@ -228,7 +312,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
private @Nullable MultipartBuilder constructMultipartBody(
ReadableArray body,
String contentType,
- Callback callback) {
+ int requestId) {
MultipartBuilder multipartBuilder = new MultipartBuilder();
multipartBuilder.type(MediaType.parse(contentType));
@@ -239,7 +323,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
ReadableArray headersArray = bodyPart.getArray("headers");
Headers headers = extractHeaders(headersArray, null);
if (headers == null) {
- callback.invoke(0, null, "Missing or invalid header format for FormData part.");
+ onRequestError(requestId, "Missing or invalid header format for FormData part.");
return null;
}
MediaType partContentType = null;
@@ -256,19 +340,19 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
multipartBuilder.addPart(headers, RequestBody.create(partContentType, bodyValue));
} else if (bodyPart.hasKey(REQUEST_BODY_KEY_URI)) {
if (partContentType == null) {
- callback.invoke(0, null, "Binary FormData part needs a content-type header.");
+ onRequestError(requestId, "Binary FormData part needs a content-type header.");
return null;
}
String fileContentUriStr = bodyPart.getString(REQUEST_BODY_KEY_URI);
InputStream fileInputStream =
RequestBodyUtil.getFileInputStream(getReactApplicationContext(), fileContentUriStr);
if (fileInputStream == null) {
- callback.invoke(0, null, "Could not retrieve file for uri " + fileContentUriStr);
+ onRequestError(requestId, "Could not retrieve file for uri " + fileContentUriStr);
return null;
}
multipartBuilder.addPart(headers, RequestBodyUtil.create(partContentType, fileInputStream));
} else {
- callback.invoke(0, null, "Unrecognized FormData part.");
+ onRequestError(requestId, "Unrecognized FormData part.");
}
}
return multipartBuilder;
@@ -305,4 +389,9 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
return headersBuilder.build();
}
+
+ private DeviceEventManagerModule.RCTDeviceEventEmitter getEventEmitter() {
+ return getReactApplicationContext()
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
+ }
}