From deb6c236bc9ee3d34e32ab65c61f5dbefab29cae Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Thu, 16 Feb 2017 10:42:10 -0600 Subject: [PATCH 1/8] Support versions of React Native >= 0.29. --- README.md | 19 ++++++++++++++ android/build.gradle | 3 +-- .../ChromeCustomTabsModule.java | 26 +++++++++---------- .../ChromeCustomTabsPackage.java | 10 ++----- .../CustomTabActivityHelper.java | 6 ++--- .../MainActivity.java | 2 +- 6 files changed, 38 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 0cc4c63..d3bd581 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,26 @@ project(':ReactNativeChromeCustomTabs').projectDir = new File(rootProject.projec 4. Import and register the module in your `MainActivity.java` file: ```java import com.dstaley.ReactNativeChromeCustomTabs.ChromeCustomTabsPackage; // <-- Import +``` + +### For React Native >= v0.29 +```java +public class MyReactNativeHost extends ReactNativeHost { + @Override + protected List getPackages() { + return Arrays.asList( + ... + new ChromeCustomTabsPackage(), // <-- Register + ... + ); + } +} +``` + +### For React Native v0.19 - v0.28 + +```java public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler { private ReactInstanceManager mReactInstanceManager; diff --git a/android/build.gradle b/android/build.gradle index 58535ae..d7fa3d4 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -23,7 +23,6 @@ android { dependencies { compile fileTree(dir: "libs", include: ["*.jar"]) - compile "com.android.support:appcompat-v7:23.0.1" - compile "com.android.support:customtabs:23.1.0" + compile "com.android.support:customtabs:25.1.0" compile "com.facebook.react:react-native:0.17.+" } \ No newline at end of file diff --git a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsModule.java b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsModule.java index e5d840b..0ba01a5 100644 --- a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsModule.java +++ b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsModule.java @@ -1,16 +1,11 @@ package com.dstaley.ReactNativeChromeCustomTabs; import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; +import android.graphics.Color; import android.net.Uri; -import android.widget.Toast; import android.support.customtabs.CustomTabsIntent; -import android.support.v7.app.AppCompatActivity; -import com.facebook.react.bridge.NativeModule; 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.modules.core.DeviceEventManagerModule; @@ -22,18 +17,15 @@ public class ChromeCustomTabsModule extends ReactContextBaseJavaModule implements CustomTabActivityHelper.ConnectionCallback { - Activity mActivity; private CustomTabActivityHelper mCustomTabActivityHelper; private ReactApplicationContext mContext; - public ChromeCustomTabsModule(ReactApplicationContext reactContext, Activity activity) { + public ChromeCustomTabsModule(ReactApplicationContext reactContext) { super(reactContext); - mActivity = activity; mContext = reactContext; - String packageName = CustomTabsHelper.getPackageNameToUse(mActivity); mCustomTabActivityHelper = new CustomTabActivityHelper(); mCustomTabActivityHelper.setConnectionCallback(this); - mCustomTabActivityHelper.bindCustomTabsService(mActivity, reactContext.getApplicationContext()); + mCustomTabActivityHelper.bindCustomTabsService(reactContext.getApplicationContext()); } private void sendEvent(String eventName) { @@ -71,7 +63,13 @@ public void mayLaunchUrl(String url) { @ReactMethod public void launchCustomTab(String url) { - CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder(mCustomTabActivityHelper.getSession()).build(); - mCustomTabActivityHelper.openCustomTab(mActivity, customTabsIntent, Uri.parse(url), null); + Activity activity = getCurrentActivity(); + if (activity == null) { + return; + } + + CustomTabsIntent customTabsIntent = + new CustomTabsIntent.Builder(mCustomTabActivityHelper.getSession()).build(); + CustomTabActivityHelper.openCustomTab(activity, customTabsIntent, Uri.parse(url), null); } -} \ No newline at end of file +} diff --git a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsPackage.java b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsPackage.java index da843f5..7b6165a 100644 --- a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsPackage.java +++ b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsPackage.java @@ -17,16 +17,10 @@ public class ChromeCustomTabsPackage implements ReactPackage { - Activity mActivity; - - public ChromeCustomTabsPackage(Activity activity) { - mActivity = activity; - } - @Override public List createNativeModules(ReactApplicationContext reactContext) { List modules = new ArrayList<>(); - modules.add(new ChromeCustomTabsModule(reactContext, mActivity)); + modules.add(new ChromeCustomTabsModule(reactContext)); return modules; } @@ -39,4 +33,4 @@ public List> createJSModules() { public List createViewManagers(ReactApplicationContext reactContext) { return Arrays.asList(); } -} \ No newline at end of file +} diff --git a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/CustomTabActivityHelper.java b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/CustomTabActivityHelper.java index 322aae4..b949d4a 100644 --- a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/CustomTabActivityHelper.java +++ b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/CustomTabActivityHelper.java @@ -105,10 +105,10 @@ public void setConnectionCallback(ConnectionCallback connectionCallback) { * Binds the Activity to the Custom Tabs Service. * @param activity the activity to be binded to the service. */ - public void bindCustomTabsService(Activity activity, Context context) { + public void bindCustomTabsService(Context context) { if (mClient != null) return; - String packageName = CustomTabsHelper.getPackageNameToUse(activity); + String packageName = CustomTabsHelper.getPackageNameToUse(context); if (packageName == null) return; mConnection = new ServiceConnection(this); @@ -170,4 +170,4 @@ public interface CustomTabFallback { void openUri(Activity activity, Uri uri); } -} \ No newline at end of file +} diff --git a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/MainActivity.java b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/MainActivity.java index dc3a5ca..0edcc18 100644 --- a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/MainActivity.java +++ b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/MainActivity.java @@ -28,7 +28,7 @@ protected void onCreate(Bundle savedInstanceState) { .setBundleAssetName("index.android.bundle") .setJSMainModuleName("index.android") .addPackage(new MainReactPackage()) - .addPackage(new ChromeCustomTabsPackage(this)) + .addPackage(new ChromeCustomTabsPackage()) .setUseDeveloperSupport(BuildConfig.DEBUG) .setInitialLifecycleState(LifecycleState.RESUMED) .build(); From cfd640446c0319136d4ae4b1c33bbb52f9343f85 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Thu, 16 Feb 2017 10:53:50 -0600 Subject: [PATCH 2/8] Add a hook to customize the created CustomTabsIntent. --- .../ChromeCustomTabsModule.java | 22 ++++++++++--------- .../ChromeCustomTabsPackage.java | 12 +++++++++- .../CustomTabsIntentEditor.java | 13 +++++++++++ 3 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/CustomTabsIntentEditor.java diff --git a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsModule.java b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsModule.java index 0ba01a5..9bbc43a 100644 --- a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsModule.java +++ b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsModule.java @@ -1,8 +1,8 @@ package com.dstaley.ReactNativeChromeCustomTabs; import android.app.Activity; -import android.graphics.Color; import android.net.Uri; +import android.support.annotation.Nullable; import android.support.customtabs.CustomTabsIntent; import com.facebook.react.bridge.ReactApplicationContext; @@ -10,19 +10,18 @@ import com.facebook.react.bridge.ReactMethod; import com.facebook.react.modules.core.DeviceEventManagerModule; -import java.util.Map; -import java.util.HashMap; - -import com.dstaley.ReactNativeChromeCustomTabs.CustomTabActivityHelper; - public class ChromeCustomTabsModule extends ReactContextBaseJavaModule implements CustomTabActivityHelper.ConnectionCallback { private CustomTabActivityHelper mCustomTabActivityHelper; private ReactApplicationContext mContext; + private CustomTabsIntentEditor mIntentEditor; - public ChromeCustomTabsModule(ReactApplicationContext reactContext) { + ChromeCustomTabsModule( + ReactApplicationContext reactContext, + @Nullable CustomTabsIntentEditor intentEditor) { super(reactContext); mContext = reactContext; + mIntentEditor = intentEditor; mCustomTabActivityHelper = new CustomTabActivityHelper(); mCustomTabActivityHelper.setConnectionCallback(this); mCustomTabActivityHelper.bindCustomTabsService(reactContext.getApplicationContext()); @@ -68,8 +67,11 @@ public void launchCustomTab(String url) { return; } - CustomTabsIntent customTabsIntent = - new CustomTabsIntent.Builder(mCustomTabActivityHelper.getSession()).build(); - CustomTabActivityHelper.openCustomTab(activity, customTabsIntent, Uri.parse(url), null); + CustomTabsIntent.Builder builder = + new CustomTabsIntent.Builder(mCustomTabActivityHelper.getSession()); + if (mIntentEditor != null) { + mIntentEditor.customize(activity, builder); + } + CustomTabActivityHelper.openCustomTab(activity, builder.build(), Uri.parse(url), null); } } diff --git a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsPackage.java b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsPackage.java index 7b6165a..489f257 100644 --- a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsPackage.java +++ b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsPackage.java @@ -17,10 +17,20 @@ public class ChromeCustomTabsPackage implements ReactPackage { + private final CustomTabsIntentEditor mIntentEditor; + + public ChromeCustomTabsPackage() { + this(null); + } + + public ChromeCustomTabsPackage(CustomTabsIntentEditor intentEditor) { + mIntentEditor = intentEditor; + } + @Override public List createNativeModules(ReactApplicationContext reactContext) { List modules = new ArrayList<>(); - modules.add(new ChromeCustomTabsModule(reactContext)); + modules.add(new ChromeCustomTabsModule(reactContext, mIntentEditor)); return modules; } diff --git a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/CustomTabsIntentEditor.java b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/CustomTabsIntentEditor.java new file mode 100644 index 0000000..55175df --- /dev/null +++ b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/CustomTabsIntentEditor.java @@ -0,0 +1,13 @@ +package com.dstaley.ReactNativeChromeCustomTabs; + +import android.content.Context; +import android.support.customtabs.CustomTabsIntent; + +/** + * Callback for customizing the creation of a CustomTabsIntent. + * This is called just before a custom tab is opened. + */ +public interface CustomTabsIntentEditor { + + void customize(Context context, CustomTabsIntent.Builder builder); +} From 5d34d8f22ea1e51579fdb8ff6e3a2d91bd7643e3 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Thu, 16 Feb 2017 11:00:21 -0600 Subject: [PATCH 3/8] Add a fallback to the default browser if Chrome is not installed. --- .../ChromeCustomTabsModule.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsModule.java b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsModule.java index 9bbc43a..dd31481 100644 --- a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsModule.java +++ b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsModule.java @@ -1,6 +1,7 @@ package com.dstaley.ReactNativeChromeCustomTabs; import android.app.Activity; +import android.content.Intent; import android.net.Uri; import android.support.annotation.Nullable; import android.support.customtabs.CustomTabsIntent; @@ -72,6 +73,12 @@ public void launchCustomTab(String url) { if (mIntentEditor != null) { mIntentEditor.customize(activity, builder); } - CustomTabActivityHelper.openCustomTab(activity, builder.build(), Uri.parse(url), null); + CustomTabActivityHelper.openCustomTab(activity, builder.build(), Uri.parse(url), + new CustomTabActivityHelper.CustomTabFallback() { + @Override + public void openUri(Activity activity, Uri uri) { + activity.startActivity(new Intent(Intent.ACTION_VIEW, uri)); + } + }); } } From 4753b756f4d89fceed6bb799feb4adfd251de7f2 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Thu, 16 Feb 2017 11:25:28 -0600 Subject: [PATCH 4/8] On iOS, fall back to Linking.openURL. --- ChromeCustomTabsClient.android.js | 4 ++++ ChromeCustomTabsClient.ios.js | 7 +++++++ 2 files changed, 11 insertions(+) create mode 100644 ChromeCustomTabsClient.android.js create mode 100644 ChromeCustomTabsClient.ios.js diff --git a/ChromeCustomTabsClient.android.js b/ChromeCustomTabsClient.android.js new file mode 100644 index 0000000..e5af6e8 --- /dev/null +++ b/ChromeCustomTabsClient.android.js @@ -0,0 +1,4 @@ +'use strict'; + +var { NativeModules } = require('react-native'); +module.exports = NativeModules.ChromeCustomTabsClient; diff --git a/ChromeCustomTabsClient.ios.js b/ChromeCustomTabsClient.ios.js new file mode 100644 index 0000000..6f8bf10 --- /dev/null +++ b/ChromeCustomTabsClient.ios.js @@ -0,0 +1,7 @@ +'use strict'; + +var { Linking } = require('react-native'); + +// Fall back to Linking.openURL on iOS. +module.exports.launchCustomTab = Linking.openURL; +module.exports.mayLaunchUrl = function(url) {}; From d165e7057b6692f2909fd469456aec3b43f75234 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Tue, 21 Feb 2017 10:02:22 -0600 Subject: [PATCH 5/8] Suppress exceptions caused when sending native events. This works around a race condition in RN < 0.38.0, wherein hasActiveCatalystInstance() might return true on an instance that is not in fact fully set up and ready to receive messages. --- .../ChromeCustomTabsModule.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsModule.java b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsModule.java index dd31481..371bdef 100644 --- a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsModule.java +++ b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsModule.java @@ -29,10 +29,16 @@ public class ChromeCustomTabsModule extends ReactContextBaseJavaModule implement } private void sendEvent(String eventName) { - if (mContext.hasActiveCatalystInstance()) { - mContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit(eventName, null); + try { + if (mContext.hasActiveCatalystInstance()) { + mContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(eventName, null); + } + } catch (RuntimeException e) { + // Work around a race condition in RN < 0.38.0, resulting in + // a RuntimeException of "Attempt to call JS function before JS bundle is loaded." + // Fixed in react-native#6a45f05. } } From 316c0b2ac56f4a56aaaefa6e332e8df12dcd985b Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Wed, 26 Apr 2017 15:09:53 -0500 Subject: [PATCH 6/8] Add a backward-compatible constructor to ChromeCustomTabsPackage. --- .../ChromeCustomTabsPackage.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsPackage.java b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsPackage.java index 489f257..77534b3 100644 --- a/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsPackage.java +++ b/android/src/main/java/com/dstaley/ReactNativeChromeCustomTabs/ChromeCustomTabsPackage.java @@ -20,13 +20,22 @@ public class ChromeCustomTabsPackage implements ReactPackage { private final CustomTabsIntentEditor mIntentEditor; public ChromeCustomTabsPackage() { - this(null); + this((CustomTabsIntentEditor) null); } public ChromeCustomTabsPackage(CustomTabsIntentEditor intentEditor) { mIntentEditor = intentEditor; } + /** + * @deprecated + * The activity parameter is no longer used. + */ + @Deprecated + public ChromeCustomTabsPackage(Activity activity) { + this((CustomTabsIntentEditor) null); + } + @Override public List createNativeModules(ReactApplicationContext reactContext) { List modules = new ArrayList<>(); From 3bec177a731c48b1c20ffcd9bef43c34e813551f Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Wed, 26 Apr 2017 15:13:04 -0500 Subject: [PATCH 7/8] Remove the platform-independent ChromeCustomTabsClient.js. It has been superceded by .android.js and .ios.js. --- ChromeCustomTabsClient.js | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 ChromeCustomTabsClient.js diff --git a/ChromeCustomTabsClient.js b/ChromeCustomTabsClient.js deleted file mode 100644 index 4e332dd..0000000 --- a/ChromeCustomTabsClient.js +++ /dev/null @@ -1,4 +0,0 @@ -'use strict'; - -var { NativeModules } = require('react-native'); -module.exports = NativeModules.ChromeCustomTabsClient; \ No newline at end of file From 59428e3dc6352dab9ceaad54d05905b59119752a Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Thu, 27 Apr 2017 10:52:58 -0500 Subject: [PATCH 8/8] Add documentation for CustomTabsIntentEditor. --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index d3bd581..4398bf6 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,27 @@ ChromeCustomTabsClient.mayLaunchUrl('http://i.imgur.com/6ogeF96.gif'); ChromeCustomTabsClient.launchCustomTab('http://i.imgur.com/xjdem.gif'); ``` +## Customization + +You can supply a `CustomTabsIntentEditor` to customize the CustomTabsIntent produced by +this package. + +```java + new ChromeCustomTabsPackage(new CustomTabsIntentEditor() { + @Override + public void customize(Context context, CustomTabsIntent.Builder builder) { + builder + .addDefaultShareMenuItem() + .setShowTitle(true) + .enableUrlBarHiding(); + } + }) +``` + +For a full description of the available options, see the official documentation for +[CustomTabsIntent.Builder](https://developer.android.com/reference/android/support/customtabs/CustomTabsIntent.Builder.html) +and the [custom tabs implementation guide](https://developer.chrome.com/multidevice/android/customtabs#implementationguide). + ## License MIT \ No newline at end of file