Compare commits

...
20 Commits
Author SHA1 Message Date
Satyajit Sahoo 913d89f4a5 Merge pull request #5063 from facebook/revert-4059-patch-2
Revert "remove Precomputing Style"
2015-12-31 16:24:08 +05:30
Satyajit Sahoo d9e14f674c Revert "remove Precomputing Style" 2015-12-31 16:23:51 +05:30
Satyajit Sahoo 62dd4aca59 Merge pull request #4059 from sunnylqm/patch-2
remove Precomputing Style
2015-12-14 17:20:51 +05:30
sunnylqm 7cb64e1655 revert TextInput example 2015-12-14 19:48:35 +08:00
Martin Konicek c6ae1a7702 [0.15.0] Bump Maven version number 2015-11-23 17:30:49 +00:00
Alexander BlomandMartin Konicek 0165ae1e55 Support cookies on Android
Summary: This adds a persistent cookie store that shares cookies with WebView.

Add a `ForwardingCookieHandler` to OkHttp that uses the underlying Android webkit `CookieManager`.
Use a `LazyCookieHandler` to defer initialization of `CookieManager` as this will in turn trigger initialization of the Chromium stack in KitKat+ which takes some time. This was we will incur this cost on a background network thread instead of during startup.
Also add a `clearCookies()` method to the network module.

Add a cookies example to the XHR example. This example should also work for iOS (except for the clear cookies part). They are for now just scoped to Android.

Closes #2792.

public

Reviewed By: andreicoman11

Differential Revision: D2615550

fb-gh-sync-id: ff726a35f0fc3c7124d2f755448fe24c9d1caf21

Conflicts:
	ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java
2015-11-23 16:29:19 +00:00
Alexander BlomandMartin Konicek dc971a3cb9 Add GuardedResultAsyncTask
Reviewed By: andreicoman11

Differential Revision: D2679459

fb-gh-sync-id: 8a9ec170ce76bbc3340c9e8872e19b78ae5a5c2d
2015-11-23 16:24:55 +00:00
Martin Konicek ed6f4a5bd6 [0.15.0] Bump version numbers 2015-11-21 20:44:45 +00:00
Martin Konicek 678dfbb5e7 Handle permission errors in the Android location module 2015-11-21 20:44:24 +00:00
Martin Konicek bb75d9e81e Open source the Android Location module
Reviewed By: foghina

Differential Revision: D2658581

fb-gh-sync-id: e95b21c5c7c06f3332d2a7c9fab8be9a2e6441cb
2015-11-20 16:47:35 +00:00
Martin Konicek a534d01e78 Open source IntentAndroid
Summary: Move the code to the github folder, add more docs and improve the example.

We might want to merge this with `LinkingIOS` later (it has the same functionality
plus support for deep links) but want to see how people use the `IntentAndroid`
API first (and what other methods we should add) to have more data points.

public

Reviewed By: lexs

Differential Revision: D2646936

fb-gh-sync-id: 751f35784d387efcd031f9b458821cdfde048a54
2015-11-20 16:47:28 +00:00
Martin Konicek e01f8a2406 Merge pull request #4061 from sunnylqm/patch-4
remove precomputeStyle
2015-11-13 21:20:14 +00:00
sunnylqm f4756d95ee Update DirectManipulation.md
and there is no more controlled / bufferdelay prop for TextInput
2015-11-11 16:41:58 +08:00
sunnylqm 65ff8a4455 remove precomputeStyle
remove precomputeStyle
2015-11-11 11:15:11 +08:00
sunnylqm b00a61d4ee remove Precompute Style
Since there are no precomputeStyle module now.
2015-11-11 11:01:00 +08:00
James Ide 88783d94f1 Source nvm.sh (best effort) when running react-native-xcode.sh
Summary: When Node is installed with nvm, scripts invoked from Xcode need to set up nvm. This can be done either by sourcing .profile/.bash_profile/.bashrc/etc... or by sourcing nvm.sh directly -- this diff does the latter and handles the case where nvm may have been installed the official way (under ~/.nvm/nvm.sh AFAIK) or via homebrew.
Closes https://github.com/facebook/react-native/pull/4015

Reviewed By: svcscm

Differential Revision: D2633301

Pulled By: frantic

fb-gh-sync-id: 3c2b9b0d21887ba21d6f85f5d279314d50c1db28
2015-11-10 00:26:05 -08:00
Martin Konicek 94420109a1 Update breaking-changes.md 2015-11-09 15:50:33 +00:00
Felix Oghină 4ba09895dc [releng] bump maven artifact version to 0.15.1 2015-11-06 17:05:51 -08:00
Andy StreetandFelix Oghină 40b74e49f2 Add Systrace support for API 18+ in OSS
Differential Revision: D2627757

fb-gh-sync-id: a01347800d8e9ffda8759cc17df04f7cd139b17c
2015-11-06 17:00:09 -08:00
Felix Oghină f7af7d267e [releng] bump versions to 0.15.0-rc 2015-11-06 12:13:44 -08:00
26 changed files with 1072 additions and 34 deletions
@@ -0,0 +1,90 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
'use strict';
var React = require('react-native');
var {
IntentAndroid,
StyleSheet,
Text,
TouchableNativeFeedback,
View,
} = React;
var UIExplorerBlock = require('./UIExplorerBlock');
var OpenURLButton = React.createClass({
propTypes: {
url: React.PropTypes.string,
},
handleClick: function() {
IntentAndroid.canOpenURL(this.props.url, (supported) => {
if (supported) {
IntentAndroid.openURL(this.props.url);
} else {
console.log('Don\'t know how to open URI: ' + this.props.url);
}
});
},
render: function() {
return (
<TouchableNativeFeedback
onPress={this.handleClick}>
<View style={styles.button}>
<Text style={styles.text}>Open {this.props.url}</Text>
</View>
</TouchableNativeFeedback>
);
}
});
var IntentAndroidExample = React.createClass({
statics: {
title: 'IntentAndroid',
description: 'Shows how to use Android Intents to open URLs.',
},
render: function() {
return (
<UIExplorerBlock title="Open external URLs">
<OpenURLButton url={'https://www.facebook.com'} />
<OpenURLButton url={'http://www.facebook.com'} />
<OpenURLButton url={'http://facebook.com'} />
<OpenURLButton url={'geo:37.484847,-122.148386'} />
</UIExplorerBlock>
);
},
});
var styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
padding: 10,
paddingTop: 30,
},
button: {
padding: 10,
backgroundColor: '#3B5998',
marginBottom: 10,
},
text: {
color: 'white',
},
});
module.exports = IntentAndroidExample;
@@ -38,6 +38,8 @@ var COMPONENTS = [
var APIS = [
require('./AccessibilityAndroidExample.android'),
require('./BorderExample'),
require('./GeolocationExample'),
require('./IntentAndroidExample.android'),
require('./LayoutEventsExample'),
require('./LayoutExample'),
require('./PanResponderExample'),
@@ -26,6 +26,8 @@ var {
} = React;
var XHRExampleHeaders = require('./XHRExampleHeaders');
var XHRExampleCookies = require('./XHRExampleCookies');
// TODO t7093728 This is a simlified XHRExample.ios.js.
// Once we have Camera roll, Toast, Intent (for opening URLs)
@@ -280,6 +282,11 @@ exports.examples = [{
render() {
return <XHRExampleHeaders/>;
}
}, {
title: 'Cookies',
render() {
return <XHRExampleCookies/>;
}
}];
var styles = StyleSheet.create({
+128
View File
@@ -0,0 +1,128 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
*/
'use strict';
var React = require('react-native');
var {
StyleSheet,
Text,
TouchableHighlight,
View,
} = React;
var RCTNetworking = require('RCTNetworking');
class XHRExampleCookies extends React.Component {
constructor(props: any) {
super(props);
this.cancelled = false;
this.state = {
status: '',
a: 1,
b: 2,
};
}
setCookie(domain: string) {
var {a, b} = this.state;
var url = `https://${domain}/cookies/set?a=${a}&b=${b}`;
fetch(url).then((response) => {
this.setStatus(`Cookies a=${a}, b=${b} set`);
});
this.setState({
status: 'Setting cookies...',
a: a + 1,
b: b + 2,
});
}
getCookies(domain: string) {
fetch(`https://${domain}/cookies`).then((response) => {
return response.json();
}).then((data) => {
this.setStatus(`Got cookies ${JSON.stringify(data.cookies)} from server`);
});
this.setStatus('Getting cookies...');
}
clearCookies() {
RCTNetworking.clearCookies((cleared) => {
this.setStatus('Cookies cleared, had cookies=' + cleared);
});
}
setStatus(status: string) {
this.setState({status});
}
render() {
return (
<View>
<TouchableHighlight
style={styles.wrapper}
onPress={this.setCookie.bind(this, 'httpbin.org')}>
<View style={styles.button}>
<Text>Set cookie</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
style={styles.wrapper}
onPress={this.setCookie.bind(this, 'eu.httpbin.org')}>
<View style={styles.button}>
<Text>Set cookie (EU)</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
style={styles.wrapper}
onPress={this.getCookies.bind(this, 'httpbin.org')}>
<View style={styles.button}>
<Text>Get cookies</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
style={styles.wrapper}
onPress={this.getCookies.bind(this, 'eu.httpbin.org')}>
<View style={styles.button}>
<Text>Get cookies (EU)</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
style={styles.wrapper}
onPress={this.clearCookies.bind(this)}>
<View style={styles.button}>
<Text>Clear cookies</Text>
</View>
</TouchableHighlight>
<Text>{this.state.status}</Text>
</View>
);
}
}
var styles = StyleSheet.create({
wrapper: {
borderRadius: 5,
marginBottom: 5,
},
button: {
backgroundColor: '#eeeeee',
padding: 8,
},
});
module.exports = XHRExampleCookies;
@@ -0,0 +1,90 @@
/**
* 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 IntentAndroid
*/
'use strict';
var IntentAndroidModule = require('NativeModules').IntentAndroid;
var invariant = require('invariant');
/**
* `IntentAndroid` gives you a general interface to handle external links.
*
* #### Opening external links
*
* To start the corresponding activity for a link (web URL, email, contact etc.), call
*
* ```
* IntentAndroid.openURL(url)
* ```
*
* If you want to check if any installed app can handle a given URL beforehand you can call
* ```
* IntentAndroid.canOpenURL(url, (supported) => {
* if (!supported) {
* console.log('Can\'t handle url: ' + url);
* } else {
* IntentAndroid.openURL(url);
* }
* });
* ```
*/
class IntentAndroid {
/**
* Starts a corresponding external activity for the given URL.
*
* For example, if the URL is "https://www.facebook.com", the system browser will be opened,
* or the "choose application" dialog will be shown.
*
* You can use other URLs, like a location (e.g. "geo:37.484847,-122.148386"), a contact,
* or any other URL that can be opened with {@code Intent.ACTION_VIEW}.
*
* NOTE: This method will fail if the system doesn't know how to open the specified URL.
* If you're passing in a non-http(s) URL, it's best to check {@code canOpenURL} first.
*
* NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly!
*/
static openURL(url: string) {
this._validateURL(url);
IntentAndroidModule.openURL(url);
}
/**
* Determine whether or not an installed app can handle a given URL.
*
* You can use other URLs, like a location (e.g. "geo:37.484847,-122.148386"), a contact,
* or any other URL that can be opened with {@code Intent.ACTION_VIEW}.
*
* NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly!
*
* @param URL the URL to open
*/
static canOpenURL(url: string, callback: Function) {
this._validateURL(url);
invariant(
typeof callback === 'function',
'A valid callback function is required'
);
IntentAndroidModule.canOpenURL(url, callback);
}
static _validateURL(url: string) {
invariant(
typeof url === 'string',
'Invalid URL: should be a string. Was: ' + url
);
invariant(
url,
'Invalid URL: cannot be empty'
);
}
}
module.exports = IntentAndroid;
@@ -0,0 +1,17 @@
/**
* 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 IntentAndroid
*/
'use strict';
module.exports = {
openURI: function(url) {
console.error('IntentAndroid is not supported on iOS');
},
};
+9 -2
View File
@@ -29,12 +29,19 @@ type GeoOptions = {
}
/**
* The Geolocation API follows the web spec:
* https://developer.mozilla.org/en-US/docs/Web/API/Geolocation
*
* ### iOS
* You need to include the `NSLocationWhenInUseUsageDescription` key
* in Info.plist to enable geolocation. Geolocation is enabled by default
* when you create a project with `react-native init`.
*
* Geolocation follows the MDN specification:
* https://developer.mozilla.org/en-US/docs/Web/API/Geolocation
* ### Android
* To request access to location, you need to add the following line to your
* app's `AndroidManifest.xml`:
*
* `<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />`
*/
var Geolocation = {
+4 -4
View File
@@ -23,7 +23,7 @@ var _initialURL = RCTLinkingManager &&
var DEVICE_NOTIF_EVENT = 'openURL';
/**
* `LinkingIOS` gives you a general interface to interact with both, incoming
* `LinkingIOS` gives you a general interface to interact with both incoming
* and outgoing app links.
*
* ### Basic Usage
@@ -65,13 +65,13 @@ var DEVICE_NOTIF_EVENT = 'openURL';
*
* #### Triggering App links
*
* To trigger an app link (browser, email or custom schemas) you call
* To trigger an app link (browser, email or custom schemas), call
*
* ```
* LinkingIOS.openURL(url)
* ```
*
* If you want to check if any installed app can handle a given url beforehand you can call
* If you want to check if any installed app can handle a given URL beforehand you can call
* ```
* LinkingIOS.canOpenURL(url, (supported) => {
* if (!supported) {
@@ -127,7 +127,7 @@ class LinkingIOS {
}
/**
* Determine whether or not an installed app can handle a given `url`
* Determine whether or not an installed app can handle a given URL.
* The callback function will be called with `bool supported` as the only argument
*
* NOTE: As of iOS 9, your app needs to provide a `LSApplicationQueriesSchemes` key
@@ -40,6 +40,10 @@ class RCTNetworking {
static abortRequest(requestId) {
RCTNetworkingNative.abortRequest(requestId);
}
static clearCookies(callback) {
RCTNetworkingNative.clearCookies(callback);
}
}
module.exports = RCTNetworking;
+1
View File
@@ -66,6 +66,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
Dimensions: require('Dimensions'),
Easing: require('Easing'),
ImagePickerIOS: require('ImagePickerIOS'),
IntentAndroid: require('IntentAndroid'),
InteractionManager: require('InteractionManager'),
LayoutAnimation: require('LayoutAnimation'),
LinkingIOS: require('LinkingIOS'),
+1 -1
View File
@@ -1,4 +1,4 @@
VERSION_NAME=0.12.0-SNAPSHOT
VERSION_NAME=0.15.3
GROUP=com.facebook.react
POM_NAME=ReactNative
@@ -16,9 +16,8 @@ import android.os.AsyncTask;
* handled by the {@link com.facebook.react.bridge.NativeModuleCallExceptionHandler} registered if
* the app is in dev mode.
*
* This class doesn't allow doInBackground to return a results. This is mostly because when this
* class was written that functionality wasn't used and it would require some extra code to make
* work correctly with caught exceptions. Don't let that stop you from adding it if you need it :)
* This class doesn't allow doInBackground to return a results. If you need this
* use GuardedResultAsyncTask instead.
*/
public abstract class GuardedAsyncTask<Params, Progress>
extends AsyncTask<Params, Progress, Void> {
@@ -0,0 +1,43 @@
// Copyright 2004-present Facebook. All Rights Reserved.
package com.facebook.react.bridge;
import android.os.AsyncTask;
/**
* Abstract base for a AsyncTask with result support that should have any RuntimeExceptions it
* throws handled by the {@link com.facebook.react.bridge.NativeModuleCallExceptionHandler}
* registered if the app is in dev mode.
*/
public abstract class GuardedResultAsyncTask<Result>
extends AsyncTask<Void, Void, Result> {
private final ReactContext mReactContext;
protected GuardedResultAsyncTask(ReactContext reactContext) {
mReactContext = reactContext;
}
@Override
protected final Result doInBackground(Void... params) {
try {
return doInBackgroundGuarded();
} catch (RuntimeException e) {
mReactContext.handleException(e);
throw e;
}
}
@Override
protected final void onPostExecute(Result result) {
try {
onPostExecuteGuarded(result);
} catch (RuntimeException e) {
mReactContext.handleException(e);
}
}
protected abstract Result doInBackgroundGuarded();
protected abstract void onPostExecuteGuarded(Result result);
}
@@ -80,7 +80,8 @@ public class FrescoModule extends ReactContextBaseJavaModule implements
}
Context context = this.getReactApplicationContext().getApplicationContext();
OkHttpClient okHttpClient = OkHttpClientProvider.getOkHttpClient();
OkHttpClient okHttpClient =
OkHttpClientProvider.getCookieAwareOkHttpClient(getReactApplicationContext());
ImagePipelineConfig.Builder builder =
OkHttpImagePipelineConfigFactory.newBuilder(context, okHttpClient);
@@ -0,0 +1,85 @@
/**
* 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.intent;
import android.content.Intent;
import android.net.Uri;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
/**
* Intent module. Launch other activities or open URLs.
*/
public class IntentModule extends ReactContextBaseJavaModule {
public IntentModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "IntentAndroid";
}
/**
* Starts a corresponding external activity for the given URL.
*
* For example, if the URL is "https://www.facebook.com", the system browser will be opened,
* or the "choose application" dialog will be shown.
*
* @param URL the URL to open
*/
@ReactMethod
public void openURL(String url) {
if (url == null || url.isEmpty()) {
throw new JSApplicationIllegalArgumentException("Invalid URL: " + url);
}
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
// We need Intent.FLAG_ACTIVITY_NEW_TASK since getReactApplicationContext() returns
// the ApplicationContext instead of the Activity context.
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getReactApplicationContext().startActivity(intent);
} catch (Exception e) {
throw new JSApplicationIllegalArgumentException(
"Could not open URL '" + url + "': " + e.getMessage());
}
}
/**
* Determine whether or not an installed app can handle a given URL.
*
* @param URL the URL to open
* @param promise a promise that is always resolved with a boolean argument
*/
@ReactMethod
public void canOpenURL(String url, Callback callback) {
if (url == null || url.isEmpty()) {
throw new JSApplicationIllegalArgumentException("Invalid URL: " + url);
}
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
// We need Intent.FLAG_ACTIVITY_NEW_TASK since getReactApplicationContext() returns
// the ApplicationContext instead of the Activity context.
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
boolean canOpen =
intent.resolveActivity(this.getReactApplicationContext().getPackageManager()) != null;
callback.invoke(canOpen);
} catch (Exception e) {
throw new JSApplicationIllegalArgumentException(
"Could not check if URL '" + url + "' can be opened: " + e.getMessage());
}
}
}
@@ -0,0 +1,281 @@
/**
* 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.location;
import javax.annotation.Nullable;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.os.Handler;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.SystemClock;
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
/**
* Native module that exposes Geolocation to JS.
*/
public class LocationModule extends ReactContextBaseJavaModule {
private @Nullable String mWatchedProvider;
private final LocationListener mLocationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
getReactApplicationContext().getJSModule(RCTDeviceEventEmitter.class)
.emit("geolocationDidChange", locationToMap(location));
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
if (status == LocationProvider.OUT_OF_SERVICE) {
emitError("Provider " + provider + " is out of service.");
} else if (status == LocationProvider.TEMPORARILY_UNAVAILABLE) {
emitError("Provider " + provider + " is temporarily unavailable.");
}
}
@Override
public void onProviderEnabled(String provider) { }
@Override
public void onProviderDisabled(String provider) { }
};
public LocationModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "LocationObserver";
}
private static class LocationOptions {
private final long timeout;
private final double maximumAge;
private final boolean highAccuracy;
private LocationOptions(long timeout, double maximumAge, boolean highAccuracy) {
this.timeout = timeout;
this.maximumAge = maximumAge;
this.highAccuracy = highAccuracy;
}
private static LocationOptions fromReactMap(ReadableMap map) {
// precision might be dropped on timeout (double -> int conversion), but that's OK
long timeout =
map.hasKey("timeout") ? (long) map.getDouble("timeout") : Long.MAX_VALUE;
double maximumAge =
map.hasKey("maximumAge") ? map.getDouble("maximumAge") : Double.POSITIVE_INFINITY;
boolean highAccuracy =
map.hasKey("enableHighAccuracy") && map.getBoolean("enableHighAccuracy");
return new LocationOptions(timeout, maximumAge, highAccuracy);
}
}
/**
* Get the current position. This can return almost immediately if the location is cached or
* request an update, which might take a while.
*
* @param options map containing optional arguments: timeout (millis), maximumAge (millis) and
* highAccuracy (boolean)
*/
@ReactMethod
public void getCurrentPosition(
ReadableMap options,
final Callback success,
Callback error) {
LocationOptions locationOptions = LocationOptions.fromReactMap(options);
try {
LocationManager locationManager =
(LocationManager) getReactApplicationContext().getSystemService(Context.LOCATION_SERVICE);
String provider = getValidProvider(locationManager, locationOptions.highAccuracy);
if (provider == null) {
error.invoke("No available location provider.");
return;
}
Location location = locationManager.getLastKnownLocation(provider);
if (location != null &&
SystemClock.currentTimeMillis() - location.getTime() < locationOptions.maximumAge) {
success.invoke(locationToMap(location));
return;
}
new SingleUpdateRequest(locationManager, provider, locationOptions.timeout, success, error)
.invoke();
} catch (SecurityException e) {
throwLocationPermissionMissing(e);
}
}
/**
* Start listening for location updates. These will be emitted via the
* {@link RCTDeviceEventEmitter} as {@code geolocationDidChange} events.
*
* @param options map containing optional arguments: highAccuracy (boolean)
*/
@ReactMethod
public void startObserving(ReadableMap options) {
if (LocationManager.GPS_PROVIDER.equals(mWatchedProvider)) {
return;
}
LocationOptions locationOptions = LocationOptions.fromReactMap(options);
try {
LocationManager locationManager =
(LocationManager) getReactApplicationContext().getSystemService(Context.LOCATION_SERVICE);
String provider = getValidProvider(locationManager, locationOptions.highAccuracy);
if (provider == null) {
emitError("No location provider available.");
return;
}
if (!provider.equals(mWatchedProvider)) {
locationManager.removeUpdates(mLocationListener);
locationManager.requestLocationUpdates(provider, 1000, 0, mLocationListener);
}
mWatchedProvider = provider;
} catch (SecurityException e) {
throwLocationPermissionMissing(e);
}
}
/**
* Stop listening for location updates.
*
* NB: this is not balanced with {@link #startObserving}: any number of calls to that method will
* be canceled by just one call to this one.
*/
@ReactMethod
public void stopObserving() {
LocationManager locationManager =
(LocationManager) getReactApplicationContext().getSystemService(Context.LOCATION_SERVICE);
locationManager.removeUpdates(mLocationListener);
mWatchedProvider = null;
}
@Nullable
private static String getValidProvider(LocationManager locationManager, boolean highAccuracy) {
String provider =
highAccuracy ? LocationManager.GPS_PROVIDER : LocationManager.NETWORK_PROVIDER;
if (!locationManager.isProviderEnabled(provider)) {
provider = provider.equals(LocationManager.GPS_PROVIDER)
? LocationManager.NETWORK_PROVIDER
: LocationManager.GPS_PROVIDER;
if (!locationManager.isProviderEnabled(provider)) {
return null;
}
}
return provider;
}
private static WritableMap locationToMap(Location location) {
WritableMap map = Arguments.createMap();
WritableMap coords = Arguments.createMap();
coords.putDouble("latitude", location.getLatitude());
coords.putDouble("longitude", location.getLongitude());
coords.putDouble("altitude", location.getAltitude());
coords.putDouble("accuracy", location.getAccuracy());
coords.putDouble("heading", location.getBearing());
coords.putDouble("speed", location.getSpeed());
map.putMap("coords", coords);
map.putDouble("timestamp", location.getTime());
return map;
}
private void emitError(String error) {
getReactApplicationContext().getJSModule(RCTDeviceEventEmitter.class)
.emit("geolocationError", error);
}
/**
* Provides a clearer exception message than the default one.
*/
private static void throwLocationPermissionMissing(SecurityException e) {
throw new SecurityException(
"Looks like the app doesn't have the permission to access location.\n" +
"Add the following line to your app's AndroidManifest.xml:\n" +
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />", e);
}
private static class SingleUpdateRequest {
private final Callback mSuccess;
private final Callback mError;
private final LocationManager mLocationManager;
private final String mProvider;
private final long mTimeout;
private final Handler mHandler = new Handler();
private final Runnable mTimeoutRunnable = new Runnable() {
@Override
public void run() {
synchronized (SingleUpdateRequest.this) {
if (!mTriggered) {
mError.invoke("Location request timed out");
mLocationManager.removeUpdates(mLocationListener);
mTriggered = true;
}
}
}
};
private final LocationListener mLocationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
synchronized (SingleUpdateRequest.this) {
if (!mTriggered) {
mSuccess.invoke(locationToMap(location));
mHandler.removeCallbacks(mTimeoutRunnable);
mTriggered = true;
}
}
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {}
@Override
public void onProviderEnabled(String provider) {}
@Override
public void onProviderDisabled(String provider) {}
};
private boolean mTriggered;
private SingleUpdateRequest(
LocationManager locationManager,
String provider,
long timeout,
Callback success,
Callback error) {
mLocationManager = locationManager;
mProvider = provider;
mTimeout = timeout;
mSuccess = success;
mError = error;
}
public void invoke() {
mLocationManager.requestSingleUpdate(mProvider, mLocationListener, null);
mHandler.postDelayed(mTimeoutRunnable, SystemClock.currentTimeMillis() + mTimeout);
}
}
}
@@ -0,0 +1,230 @@
// Copyright 2004-present Facebook. All Rights Reserved.
package com.facebook.react.modules.network;
import javax.annotation.Nullable;
import java.io.IOException;
import java.net.CookieHandler;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.ValueCallback;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.GuardedAsyncTask;
import com.facebook.react.bridge.GuardedResultAsyncTask;
import com.facebook.react.bridge.ReactContext;
/**
* Cookie handler that forwards all cookies to the WebView CookieManager.
*
* This class relies on CookieManager to persist cookies to disk so cookies may be lost if the
* application is terminated before it syncs.
*/
public class ForwardingCookieHandler extends CookieHandler {
private static final String VERSION_ZERO_HEADER = "Set-cookie";
private static final String VERSION_ONE_HEADER = "Set-cookie2";
private static final String COOKIE_HEADER = "Cookie";
// As CookieManager was synchronous before API 21 this class emulates the async behavior on <21.
private static final boolean USES_LEGACY_STORE = Build.VERSION.SDK_INT < 21;
private final CookieSaver mCookieSaver;
private final ReactContext mContext;
private @Nullable CookieManager mCookieManager;
public ForwardingCookieHandler(ReactContext context) {
mContext = context;
mCookieSaver = new CookieSaver();
}
@Override
public Map<String, List<String>> get(URI uri, Map<String, List<String>> headers)
throws IOException {
String cookies = getCookieManager().getCookie(uri.toString());
if (TextUtils.isEmpty(cookies)) {
return Collections.emptyMap();
}
return Collections.singletonMap(COOKIE_HEADER, Collections.singletonList(cookies));
}
@Override
public void put(URI uri, Map<String, List<String>> headers) throws IOException {
String url = uri.toString();
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
String key = entry.getKey();
if (key != null && isCookieHeader(key)) {
addCookies(url, entry.getValue());
}
}
}
public void clearCookies(final Callback callback) {
if (USES_LEGACY_STORE) {
new GuardedResultAsyncTask<Boolean>(mContext) {
@Override
protected Boolean doInBackgroundGuarded() {
getCookieManager().removeAllCookie();
mCookieSaver.onCookiesModified();
return true;
}
@Override
protected void onPostExecuteGuarded(Boolean result) {
callback.invoke(result);
}
}.execute();
} else {
clearCookiesAsync(callback);
}
}
private void clearCookiesAsync(final Callback callback) {
getCookieManager().removeAllCookies(
new ValueCallback<Boolean>() {
@Override
public void onReceiveValue(Boolean value) {
mCookieSaver.onCookiesModified();
callback.invoke(value);
}
});
}
public void destroy() {
if (USES_LEGACY_STORE) {
getCookieManager().removeExpiredCookie();
mCookieSaver.persistCookies();
}
}
private void addCookies(final String url, final List<String> cookies) {
if (USES_LEGACY_STORE) {
runInBackground(
new Runnable() {
@Override
public void run() {
for (String cookie : cookies) {
getCookieManager().setCookie(url, cookie);
}
mCookieSaver.onCookiesModified();
}
});
} else {
for (String cookie : cookies) {
addCookieAsync(url, cookie);
}
mCookieSaver.onCookiesModified();
}
}
@TargetApi(21)
private void addCookieAsync(String url, String cookie) {
getCookieManager().setCookie(url, cookie, null);
}
private static boolean isCookieHeader(String name) {
return name.equalsIgnoreCase(VERSION_ZERO_HEADER) || name.equalsIgnoreCase(VERSION_ONE_HEADER);
}
private void runInBackground(final Runnable runnable) {
new GuardedAsyncTask<Void, Void>(mContext) {
@Override
protected void doInBackgroundGuarded(Void... params) {
runnable.run();
}
}.execute();
}
/**
* Instantiating CookieManager in KitKat+ will load the Chromium task taking a 100ish ms so we
* do it lazily to make sure it's done on a background thread as needed.
*/
private CookieManager getCookieManager() {
if (mCookieManager == null) {
possiblyWorkaroundSyncManager(mContext);
mCookieManager = CookieManager.getInstance();
if (USES_LEGACY_STORE) {
mCookieManager.removeExpiredCookie();
}
}
return mCookieManager;
}
private static void possiblyWorkaroundSyncManager(Context context) {
if (USES_LEGACY_STORE) {
// This is to work around a bug where CookieManager may fail to instantiate if
// CookieSyncManager has never been created. Note that the sync() may not be required but is
// here of legacy reasons.
CookieSyncManager syncManager = CookieSyncManager.createInstance(context);
syncManager.sync();
}
}
/**
* Responsible for flushing cookies to disk. Flushes to disk with a maximum delay of 30 seconds.
* This class is only active if we are on API < 21.
*/
private class CookieSaver {
private static final int MSG_PERSIST_COOKIES = 1;
private static final int TIMEOUT = 30 * 1000; // 30 seconds
private final Handler mHandler;
public CookieSaver() {
mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == MSG_PERSIST_COOKIES) {
persistCookies();
return true;
} else {
return false;
}
}
});
}
public void onCookiesModified() {
if (USES_LEGACY_STORE) {
mHandler.sendEmptyMessageDelayed(MSG_PERSIST_COOKIES, TIMEOUT);
}
}
public void persistCookies() {
mHandler.removeMessages(MSG_PERSIST_COOKIES);
runInBackground(
new Runnable() {
@Override
public void run() {
if (USES_LEGACY_STORE) {
CookieSyncManager syncManager = CookieSyncManager.getInstance();
syncManager.sync();
} else {
flush();
}
}
});
}
@TargetApi(21)
private void flush() {
getCookieManager().flush();
}
}
}
@@ -13,6 +13,8 @@ import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.CookieHandler;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
@@ -59,19 +61,19 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
}
/**
* @param reactContext the ReactContext of the application
* @param context the ReactContext of the application
*/
public NetworkingModule(ReactApplicationContext reactContext) {
this(reactContext, null, OkHttpClientProvider.getOkHttpClient());
public NetworkingModule(final ReactApplicationContext context) {
this(context, null, OkHttpClientProvider.getCookieAwareOkHttpClient(context));
}
/**
* @param reactContext the ReactContext of the application
* @param context the ReactContext of the application
* @param defaultUserAgent the User-Agent header that will be set for all requests where the
* caller does not provide one explicitly
*/
public NetworkingModule(ReactApplicationContext reactContext, String defaultUserAgent) {
this(reactContext, defaultUserAgent, OkHttpClientProvider.getOkHttpClient());
public NetworkingModule(ReactApplicationContext context, String defaultUserAgent) {
this(context, defaultUserAgent, OkHttpClientProvider.getCookieAwareOkHttpClient(context));
}
public NetworkingModule(ReactApplicationContext reactContext, OkHttpClient client) {
@@ -87,6 +89,11 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
public void onCatalystInstanceDestroy() {
mShuttingDown = true;
mClient.cancel(null);
CookieHandler cookieHandler = mClient.getCookieHandler();
if (cookieHandler instanceof ForwardingCookieHandler) {
((ForwardingCookieHandler) cookieHandler).destroy();
}
}
@ReactMethod
@@ -225,6 +232,14 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
}.execute();
}
@ReactMethod
public void clearCookies(com.facebook.react.bridge.Callback callback) {
CookieHandler cookieHandler = mClient.getCookieHandler();
if (cookieHandler instanceof ForwardingCookieHandler) {
((ForwardingCookieHandler) cookieHandler).clearCookies(callback);
}
}
private @Nullable MultipartBuilder constructMultipartBody(
ReadableArray body,
String contentType,
@@ -9,7 +9,12 @@
package com.facebook.react.modules.network;
import javax.annotation.Nullable;
import java.util.concurrent.TimeUnit;
import com.facebook.react.bridge.ReactContext;
import com.squareup.okhttp.OkHttpClient;
/**
@@ -19,18 +24,33 @@ import com.squareup.okhttp.OkHttpClient;
public class OkHttpClientProvider {
// Centralized OkHttpClient for all networking requests.
private static OkHttpClient sClient;
private static @Nullable OkHttpClient sClient;
private static ForwardingCookieHandler sCookieHandler;
public static OkHttpClient getOkHttpClient() {
if (sClient == null) {
// TODO: #7108751 plug in stetho
sClient = new OkHttpClient();
// No timeouts by default
sClient.setConnectTimeout(0, TimeUnit.MILLISECONDS);
sClient.setReadTimeout(0, TimeUnit.MILLISECONDS);
sClient.setWriteTimeout(0, TimeUnit.MILLISECONDS);
sClient = createClient();
}
return sClient;
}
public static OkHttpClient getCookieAwareOkHttpClient(ReactContext context) {
if (sCookieHandler == null) {
sCookieHandler = new ForwardingCookieHandler(context);
getOkHttpClient().setCookieHandler(sCookieHandler);
}
return getOkHttpClient();
}
private static OkHttpClient createClient() {
// TODO: #7108751 plug in stetho
OkHttpClient client = new OkHttpClient();
// No timeouts by default
client.setConnectTimeout(0, TimeUnit.MILLISECONDS);
client.setReadTimeout(0, TimeUnit.MILLISECONDS);
client.setWriteTimeout(0, TimeUnit.MILLISECONDS);
return client;
}
}
@@ -18,6 +18,8 @@ import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.modules.fresco.FrescoModule;
import com.facebook.react.modules.intent.IntentModule;
import com.facebook.react.modules.location.LocationModule;
import com.facebook.react.modules.network.NetworkingModule;
import com.facebook.react.modules.storage.AsyncStorageModule;
import com.facebook.react.modules.toast.ToastModule;
@@ -47,6 +49,8 @@ public class MainReactPackage implements ReactPackage {
return Arrays.<NativeModule>asList(
new AsyncStorageModule(reactContext),
new FrescoModule(reactContext),
new IntentModule(reactContext),
new LocationModule(reactContext),
new NetworkingModule(reactContext),
new WebSocketModule(reactContext),
new ToastModule(reactContext));
@@ -9,8 +9,12 @@
package com.facebook.systrace;
import android.os.Build;
import android.os.Trace;
/**
* Systrace stub.
* Systrace stub that mostly does nothing but delegates to Trace for beginning/ending sections.
* The internal version of this file has not been opensourced yet.
*/
public class Systrace {
@@ -50,9 +54,15 @@ public class Systrace {
}
public static void beginSection(long tag, final String sectionName) {
if (Build.VERSION.SDK_INT >= 18) {
Trace.beginSection(sectionName);
}
}
public static void endSection(long tag) {
if (Build.VERSION.SDK_INT >= 18) {
Trace.endSection();
}
}
public static void beginAsyncSection(
-2
View File
@@ -2,8 +2,6 @@
## 0.15
None so far.
## 0.14
- D2533877: `react-native bundle` API changes:
+1 -4
View File
@@ -473,16 +473,13 @@ might be helpful if the component that we are updating is deeply nested
and hasn't been optimized with `shouldComponentUpdate`.
```javascript
// Outside of our React component
var precomputeStyle = require('precomputeStyle');
// Back inside of the App component, replace the scrollSpring listener
// in componentWillMount with this:
this._scrollSpring.addListener({
onSpringUpdate: () => {
if (!this._photo) { return }
var v = this._scrollSpring.getCurrentValue();
var newProps = precomputeStyle({transform: [{scaleX: v}, {scaleY: v}]});
var newProps = {style: {transform: [{scaleX: v}, {scaleY: v}]}};
this._photo.setNativeProps(newProps);
},
});
@@ -74,5 +74,5 @@ android {
dependencies {
compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.android.support:appcompat-v7:23.0.1"
compile "com.facebook.react:react-native:0.13.0"
compile "com.facebook.react:react-native:0.15.+"
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "react-native",
"version": "0.12.0",
"version": "0.15.0",
"description": "A framework for building native apps using React",
"license": "BSD-3-Clause",
"repository": {
+9
View File
@@ -33,6 +33,15 @@ cd ..
set -x
DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH
# Define NVM_DIR and source the nvm.sh setup script
[ -z "$NVM_DIR" ] && export NVM_DIR="$HOME/.nvm"
if [[ -s "$HOME/.nvm/nvm.sh" ]]; then
. "$HOME/.nvm/nvm.sh"
elif [[ -x "$(command -v brew)" && -s "$(brew --prefix nvm)/nvm.sh" ]]; then
. "$(brew --prefix nvm)/nvm.sh"
fi
react-native bundle \
--entry-file index.ios.js \
--platform ios \