mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-03-26 23:24:06 +08:00
Add cross-platform Linking module
Summary: A promise based API for handling Link for Android and iOS. Refer #4971 The iOS part doesn't handle errors. Will need someone with iOS knowledge to do that. cc skevy ide brentvatne mkonicek vjeux nicklockwood Closes https://github.com/facebook/react-native/pull/5336 Reviewed By: svcscm Differential Revision: D2866664 Pulled By: androidtrunkagent fb-gh-sync-id: 67e68a827e6b85886bfa84e79b897f079e78b1b5
This commit is contained in:
committed by
facebook-github-bot-5
parent
affd6230fe
commit
e33e6ab1f0
@@ -7,13 +7,16 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule IntentAndroid
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var IntentAndroidModule = require('NativeModules').IntentAndroid;
|
||||
var Linking = require('Linking');
|
||||
var invariant = require('invariant');
|
||||
|
||||
/**
|
||||
* NOTE: `IntentAndroid` is being deprecated. Use `Linking` instead.
|
||||
*
|
||||
* `IntentAndroid` gives you a general interface to handle external links.
|
||||
*
|
||||
* ### Basic Usage
|
||||
@@ -89,10 +92,12 @@ class IntentAndroid {
|
||||
* 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!
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
static openURL(url: string) {
|
||||
this._validateURL(url);
|
||||
IntentAndroidModule.openURL(url);
|
||||
console.warn('"IntentAndroid.openURL" is deprecated. Use the promise based "Linking.openURL" instead.');
|
||||
Linking.openURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,14 +109,16 @@ class IntentAndroid {
|
||||
* NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly!
|
||||
*
|
||||
* @param URL the URL to open
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
static canOpenURL(url: string, callback: Function) {
|
||||
this._validateURL(url);
|
||||
console.warn('"IntentAndroid.canOpenURL" is deprecated. Use the promise based "Linking.canOpenURL" instead.');
|
||||
invariant(
|
||||
typeof callback === 'function',
|
||||
'A valid callback function is required'
|
||||
);
|
||||
IntentAndroidModule.canOpenURL(url, callback);
|
||||
Linking.canOpenURL(url).then(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,24 +126,16 @@ class IntentAndroid {
|
||||
* it will give the link url, otherwise it will give `null`
|
||||
*
|
||||
* Refer http://developer.android.com/training/app-indexing/deep-linking.html#handling-intents
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
static getInitialURL(callback: Function) {
|
||||
console.warn('"IntentAndroid.getInitialURL" is deprecated. Use the promise based "Linking.getInitialURL" instead.');
|
||||
invariant(
|
||||
typeof callback === 'function',
|
||||
'A valid callback function is required'
|
||||
);
|
||||
IntentAndroidModule.getInitialURL(callback);
|
||||
}
|
||||
|
||||
static _validateURL(url: string) {
|
||||
invariant(
|
||||
typeof url === 'string',
|
||||
'Invalid URL: should be a string. Was: ' + url
|
||||
);
|
||||
invariant(
|
||||
url,
|
||||
'Invalid URL: cannot be empty'
|
||||
);
|
||||
Linking.getInitialURL().then(callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
211
Libraries/Linking/Linking.js
Normal file
211
Libraries/Linking/Linking.js
Normal file
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* 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 Linking
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Platform = require('Platform');
|
||||
const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
|
||||
const {
|
||||
IntentAndroid,
|
||||
LinkingManager: LinkingManagerIOS
|
||||
} = require('NativeModules');
|
||||
const LinkingManager = Platform.OS === 'android' ? IntentAndroid : LinkingManagerIOS;
|
||||
const invariant = require('invariant');
|
||||
const Map = require('Map');
|
||||
|
||||
const _notifHandlers = new Map();
|
||||
|
||||
const DEVICE_NOTIF_EVENT = 'openURL';
|
||||
|
||||
/**
|
||||
* `Linking` gives you a general interface to interact with both incoming
|
||||
* and outgoing app links.
|
||||
*
|
||||
* ### Basic Usage
|
||||
*
|
||||
* #### Handling deep links
|
||||
*
|
||||
* If your app was launched from an external url registered to your app you can
|
||||
* access and handle it from any component you want with
|
||||
*
|
||||
* ```
|
||||
* componentDidMount() {
|
||||
* var url = Linking.getInitialURL().then(url) => {
|
||||
* if (url) {
|
||||
* console.log('Initial url is: ' + url);
|
||||
* }
|
||||
* }).catch(err => console.error('An error occurred', err));
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* NOTE: For instructions on how to add support for deep linking on Android,
|
||||
* refer [Enabling Deep Links for App Content - Add Intent Filters for Your Deep Links](http://developer.android.com/training/app-indexing/deep-linking.html#adding-filters).
|
||||
*
|
||||
* NOTE: For iOS, in case you also want to listen to incoming app links during your app's
|
||||
* execution you'll need to add the following lines to you `*AppDelegate.m`:
|
||||
*
|
||||
* ```
|
||||
* - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
|
||||
* sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
|
||||
* {
|
||||
* return [LinkingManager application:application openURL:url
|
||||
* sourceApplication:sourceApplication annotation:annotation];
|
||||
* }
|
||||
*
|
||||
* // Only if your app is using [Universal Links](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html).
|
||||
* - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity
|
||||
* restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
|
||||
* {
|
||||
* return [LinkingManager application:application
|
||||
* continueUserActivity:userActivity
|
||||
* restorationHandler:restorationHandler];
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* And then on your React component you'll be able to listen to the events on
|
||||
* `Linking` as follows
|
||||
*
|
||||
* ```
|
||||
* componentDidMount() {
|
||||
* Linking.addEventListener('url', this._handleOpenURL);
|
||||
* },
|
||||
* componentWillUnmount() {
|
||||
* Linking.removeEventListener('url', this._handleOpenURL);
|
||||
* },
|
||||
* _handleOpenURL(event) {
|
||||
* console.log(event.url);
|
||||
* }
|
||||
* ```
|
||||
* Note that this is only supported on iOS.
|
||||
*
|
||||
* #### Opening external links
|
||||
*
|
||||
* To start the corresponding activity for a link (web URL, email, contact etc.), call
|
||||
*
|
||||
* ```
|
||||
* Linking.openURL(url).catch(err => console.error('An error occurred', err));
|
||||
* ```
|
||||
*
|
||||
* If you want to check if any installed app can handle a given URL beforehand you can call
|
||||
* ```
|
||||
* Linking.canOpenURL(url).then(supported => {
|
||||
* if (!supported) {
|
||||
* console.log('Can\'t handle url: ' + url);
|
||||
* } else {
|
||||
* return Linking.openURL(url);
|
||||
* }
|
||||
* }).catch(err => console.error('An error occurred', err));
|
||||
* ```
|
||||
*/
|
||||
class Linking {
|
||||
/**
|
||||
* Add a handler to Linking changes by listening to the `url` event type
|
||||
* and providing the handler
|
||||
*
|
||||
* @platform ios
|
||||
*/
|
||||
static addEventListener(type: string, handler: Function) {
|
||||
if (Platform.OS === 'android') {
|
||||
console.warn('Linking.addEventListener is not supported on Android');
|
||||
} else {
|
||||
invariant(
|
||||
type === 'url',
|
||||
'Linking only supports `url` events'
|
||||
);
|
||||
var listener = RCTDeviceEventEmitter.addListener(
|
||||
DEVICE_NOTIF_EVENT,
|
||||
handler
|
||||
);
|
||||
_notifHandlers.set(handler, listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a handler by passing the `url` event type and the handler
|
||||
*
|
||||
* @platform ios
|
||||
*/
|
||||
static removeEventListener(type: string, handler: Function ) {
|
||||
if (Platform.OS === 'android') {
|
||||
console.warn('Linking.removeEventListener is not supported on Android');
|
||||
} else {
|
||||
invariant(
|
||||
type === 'url',
|
||||
'Linking only supports `url` events'
|
||||
);
|
||||
var listener = _notifHandlers.get(handler);
|
||||
if (!listener) {
|
||||
return;
|
||||
}
|
||||
listener.remove();
|
||||
_notifHandlers.delete(handler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to open the given `url` with any of the installed apps.
|
||||
*
|
||||
* 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 the installed apps.
|
||||
*
|
||||
* 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): Promise<boolean> {
|
||||
this._validateURL(url);
|
||||
return LinkingManager.openURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not an installed app can handle a given URL.
|
||||
*
|
||||
* NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly!
|
||||
*
|
||||
* NOTE: As of iOS 9, your app needs to provide the `LSApplicationQueriesSchemes` key
|
||||
* inside `Info.plist`.
|
||||
*
|
||||
* @param URL the URL to open
|
||||
*/
|
||||
static canOpenURL(url: string): Promise<boolean> {
|
||||
this._validateURL(url);
|
||||
return LinkingManager.canOpenURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the app launch was triggered by an app link with,
|
||||
* it will give the link url, otherwise it will give `null`
|
||||
*
|
||||
* NOTE: To support deep linking on Android, refer http://developer.android.com/training/app-indexing/deep-linking.html#handling-intents
|
||||
*/
|
||||
static getInitialURL(): Promise<?string> {
|
||||
if (Platform.OS === 'android') {
|
||||
return IntentAndroid.getInitialURL();
|
||||
} else {
|
||||
return Promise.resolve(LinkingManagerIOS.initialURL);
|
||||
}
|
||||
}
|
||||
|
||||
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 = Linking;
|
||||
@@ -11,17 +11,15 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
|
||||
var Linking = require('Linking');
|
||||
var RCTLinkingManager = require('NativeModules').LinkingManager;
|
||||
var invariant = require('invariant');
|
||||
|
||||
var _notifHandlers = new Map();
|
||||
var _initialURL = RCTLinkingManager &&
|
||||
RCTLinkingManager.initialURL;
|
||||
|
||||
var DEVICE_NOTIF_EVENT = 'openURL';
|
||||
var _initialURL = RCTLinkingManager.initialURL;
|
||||
|
||||
/**
|
||||
* NOTE: `LinkingIOS` is being deprecated. Use `Linking` instead.
|
||||
*
|
||||
* `LinkingIOS` gives you a general interface to interact with both incoming
|
||||
* and outgoing app links.
|
||||
*
|
||||
@@ -98,44 +96,32 @@ class LinkingIOS {
|
||||
/**
|
||||
* Add a handler to LinkingIOS changes by listening to the `url` event type
|
||||
* and providing the handler
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
static addEventListener(type: string, handler: Function) {
|
||||
invariant(
|
||||
type === 'url',
|
||||
'LinkingIOS only supports `url` events'
|
||||
);
|
||||
var listener = RCTDeviceEventEmitter.addListener(
|
||||
DEVICE_NOTIF_EVENT,
|
||||
handler
|
||||
);
|
||||
_notifHandlers.set(handler, listener);
|
||||
console.warn('"LinkingIOS.addEventListener" is deprecated. Use "Linking.addEventListener" instead.');
|
||||
Linking.addEventListener(type, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a handler by passing the `url` event type and the handler
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
static removeEventListener(type: string, handler: Function ) {
|
||||
invariant(
|
||||
type === 'url',
|
||||
'LinkingIOS only supports `url` events'
|
||||
);
|
||||
var listener = _notifHandlers.get(handler);
|
||||
if (!listener) {
|
||||
return;
|
||||
}
|
||||
listener.remove();
|
||||
_notifHandlers.delete(handler);
|
||||
console.warn('"LinkingIOS.removeEventListener" is deprecated. Use "Linking.removeEventListener" instead.');
|
||||
Linking.removeEventListener(type, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to open the given `url` with any of the installed apps.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
static openURL(url: string) {
|
||||
invariant(
|
||||
typeof url === 'string',
|
||||
'Invalid url: should be a string'
|
||||
);
|
||||
RCTLinkingManager.openURL(url);
|
||||
console.warn('"LinkingIOS.openURL" is deprecated. Use the promise based "Linking.openURL" instead.');
|
||||
Linking.openURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,24 +130,26 @@ class LinkingIOS {
|
||||
*
|
||||
* NOTE: As of iOS 9, your app needs to provide the `LSApplicationQueriesSchemes` key
|
||||
* inside `Info.plist`.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
static canOpenURL(url: string, callback: Function) {
|
||||
invariant(
|
||||
typeof url === 'string',
|
||||
'Invalid url: should be a string'
|
||||
);
|
||||
console.warn('"LinkingIOS.canOpenURL" is deprecated. Use the promise based "Linking.canOpenURL" instead.');
|
||||
invariant(
|
||||
typeof callback === 'function',
|
||||
'A valid callback function is required'
|
||||
);
|
||||
RCTLinkingManager.canOpenURL(url, callback);
|
||||
Linking.canOpenURL(url).then(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the app launch was triggered by an app link, it will pop the link url,
|
||||
* otherwise it will return `null`
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
static popInitialURL(): ?string {
|
||||
console.warn('"LinkingIOS.popInitialURL" is deprecated. Use the promise based "Linking.getInitialURL" instead.');
|
||||
var initialURL = _initialURL;
|
||||
_initialURL = null;
|
||||
return initialURL;
|
||||
|
||||
@@ -73,26 +73,30 @@ continueUserActivity:(NSUserActivity *)userActivity
|
||||
body:notification.userInfo];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(openURL:(NSURL *)URL)
|
||||
RCT_EXPORT_METHOD(openURL:(NSURL *)URL
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(__unused RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
// TODO: we should really return success/failure via a callback here
|
||||
// TODO: we should really report success/failure via the promise here
|
||||
// Doesn't really matter what thread we call this on since it exits the app
|
||||
[RCTSharedApplication() openURL:URL];
|
||||
resolve(@[@YES]);
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(canOpenURL:(NSURL *)URL
|
||||
callback:(RCTResponseSenderBlock)callback)
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(__unused RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
if (RCTRunningInAppExtension()) {
|
||||
// Technically Today widgets can open urls, but supporting that would require
|
||||
// a reference to the NSExtensionContext
|
||||
callback(@[@NO]);
|
||||
resolve(@[@NO]);
|
||||
return;
|
||||
}
|
||||
|
||||
// This can be expensive, so we deliberately don't call on main thread
|
||||
BOOL canOpen = [RCTSharedApplication() canOpenURL:URL];
|
||||
callback(@[@(canOpen)]);
|
||||
resolve(@[@(canOpen)]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
1
Libraries/react-native/react-native.js
vendored
1
Libraries/react-native/react-native.js
vendored
@@ -73,6 +73,7 @@ var ReactNative = {
|
||||
get IntentAndroid() { return require('IntentAndroid'); },
|
||||
get InteractionManager() { return require('InteractionManager'); },
|
||||
get LayoutAnimation() { return require('LayoutAnimation'); },
|
||||
get Linking() { return require('Linking'); },
|
||||
get LinkingIOS() { return require('LinkingIOS'); },
|
||||
get NetInfo() { return require('NetInfo'); },
|
||||
get PanResponder() { return require('PanResponder'); },
|
||||
|
||||
@@ -85,6 +85,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
|
||||
IntentAndroid: require('IntentAndroid'),
|
||||
InteractionManager: require('InteractionManager'),
|
||||
LayoutAnimation: require('LayoutAnimation'),
|
||||
Linking: require('Linking'),
|
||||
LinkingIOS: require('LinkingIOS'),
|
||||
NetInfo: require('NetInfo'),
|
||||
PanResponder: require('PanResponder'),
|
||||
|
||||
@@ -13,8 +13,8 @@ import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
@@ -36,10 +36,10 @@ public class IntentModule extends ReactContextBaseJavaModule {
|
||||
/**
|
||||
* Return the URL the activity was started with
|
||||
*
|
||||
* @param callback a callback which is called with the initial URL
|
||||
* @param promise a promise which is resolved with the initial URL
|
||||
*/
|
||||
@ReactMethod
|
||||
public void getInitialURL(Callback callback) {
|
||||
public void getInitialURL(Promise promise) {
|
||||
try {
|
||||
Activity currentActivity = getCurrentActivity();
|
||||
String initialURL = null;
|
||||
@@ -54,10 +54,10 @@ public class IntentModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
}
|
||||
|
||||
callback.invoke(initialURL);
|
||||
promise.resolve(initialURL);
|
||||
} catch (Exception e) {
|
||||
throw new JSApplicationIllegalArgumentException(
|
||||
"Could not get the initial URL : " + e.getMessage());
|
||||
promise.reject(new JSApplicationIllegalArgumentException(
|
||||
"Could not get the initial URL : " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,9 +70,10 @@ public class IntentModule extends ReactContextBaseJavaModule {
|
||||
* @param url the URL to open
|
||||
*/
|
||||
@ReactMethod
|
||||
public void openURL(String url) {
|
||||
public void openURL(String url, Promise promise) {
|
||||
if (url == null || url.isEmpty()) {
|
||||
throw new JSApplicationIllegalArgumentException("Invalid URL: " + url);
|
||||
promise.reject(new JSApplicationIllegalArgumentException("Invalid URL: " + url));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -85,9 +86,11 @@ public class IntentModule extends ReactContextBaseJavaModule {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
getReactApplicationContext().startActivity(intent);
|
||||
}
|
||||
|
||||
promise.resolve(true);
|
||||
} catch (Exception e) {
|
||||
throw new JSApplicationIllegalArgumentException(
|
||||
"Could not open URL '" + url + "': " + e.getMessage());
|
||||
promise.reject(new JSApplicationIllegalArgumentException(
|
||||
"Could not open URL '" + url + "': " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,12 +98,13 @@ public class IntentModule extends ReactContextBaseJavaModule {
|
||||
* Determine whether or not an installed app can handle a given URL.
|
||||
*
|
||||
* @param url the URL to open
|
||||
* @param callback a callback that is always called with a boolean argument
|
||||
* @param promise a promise that is always resolved with a boolean argument
|
||||
*/
|
||||
@ReactMethod
|
||||
public void canOpenURL(String url, Callback callback) {
|
||||
public void canOpenURL(String url, Promise promise) {
|
||||
if (url == null || url.isEmpty()) {
|
||||
throw new JSApplicationIllegalArgumentException("Invalid URL: " + url);
|
||||
promise.reject(new JSApplicationIllegalArgumentException("Invalid URL: " + url));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -110,10 +114,10 @@ public class IntentModule extends ReactContextBaseJavaModule {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
boolean canOpen =
|
||||
intent.resolveActivity(getReactApplicationContext().getPackageManager()) != null;
|
||||
callback.invoke(canOpen);
|
||||
promise.resolve(canOpen);
|
||||
} catch (Exception e) {
|
||||
throw new JSApplicationIllegalArgumentException(
|
||||
"Could not check if URL '" + url + "' can be opened: " + e.getMessage());
|
||||
promise.reject(new JSApplicationIllegalArgumentException(
|
||||
"Could not check if URL '" + url + "' can be opened: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user