Skip to content

Commit bbc69b7

Browse files
authored
Merge pull request #341 from vincent-paing/rework/custom_tab
[Android] Add mayLaunchUrl and warmup methods
2 parents 0b942ba + 3db57a3 commit bbc69b7

File tree

8 files changed

+165
-15
lines changed

8 files changed

+165
-15
lines changed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,33 @@ import { InAppBrowser } from 'react-native-inappbrowser-reborn'
232232
...
233233
```
234234

235+
### Android Optimizations
236+
237+
On Android, you can warmup the in app browser client to make it launch siginificantly faster. To do so, add the following to your `MainActivity`
238+
239+
```java
240+
import com.proyecto26.inappbrowser.RNInAppBrowserModule;
241+
242+
public class MainActivity extends ReactActivity {
243+
244+
@Override
245+
protected void onStart() {
246+
super.onStart();
247+
RNInAppBrowserModule.onStart(this);
248+
}
249+
250+
}
251+
```
252+
253+
You can further optimize performance and pre-render pages [by providing the urls that the user is likely to open](https://developer.chrome.com/docs/android/custom-tabs/best-practices/#pre-render-content).
254+
255+
```javascript
256+
// Do not call this every time the component render
257+
useEffect(() => {
258+
InAppBrowser.mayLaunchUrl("Url user has high chance to open", ["Other urls that user might open ordered by priority"]);
259+
}, []);
260+
```
261+
235262
### Authentication Flow using Deep Linking
236263

237264
In order to redirect back to your application from a web browser, you must specify a unique URI to your app. To do this,

android/src/main/java/com/proyecto26/inappbrowser/RNInAppBrowser.java

Lines changed: 83 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,45 @@
11
package com.proyecto26.inappbrowser;
22

3-
import android.net.Uri;
4-
import android.os.Build;
5-
import android.os.Bundle;
6-
import android.text.TextUtils;
73
import android.app.Activity;
4+
import android.content.ComponentName;
85
import android.content.Context;
96
import android.content.Intent;
107
import android.content.pm.ResolveInfo;
11-
import android.graphics.Color;
128
import android.graphics.BitmapFactory;
9+
import android.graphics.Color;
10+
import android.net.Uri;
11+
import android.os.Bundle;
1312
import android.provider.Browser;
13+
import android.text.TextUtils;
14+
15+
import androidx.annotation.NonNull;
1416
import androidx.annotation.Nullable;
17+
import androidx.browser.customtabs.CustomTabsCallback;
1518
import androidx.browser.customtabs.CustomTabsClient;
1619
import androidx.browser.customtabs.CustomTabsIntent;
20+
import androidx.browser.customtabs.CustomTabsService;
21+
import androidx.browser.customtabs.CustomTabsServiceConnection;
22+
import androidx.browser.customtabs.CustomTabsSession;
1723
import androidx.core.graphics.ColorUtils;
1824

1925
import com.facebook.react.bridge.Arguments;
26+
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
2027
import com.facebook.react.bridge.Promise;
21-
import com.facebook.react.bridge.WritableMap;
28+
import com.facebook.react.bridge.ReadableArray;
2229
import com.facebook.react.bridge.ReadableMap;
23-
import com.facebook.react.bridge.ReadableType;
2430
import com.facebook.react.bridge.ReadableMapKeySetIterator;
25-
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
31+
import com.facebook.react.bridge.ReadableType;
32+
import com.facebook.react.bridge.WritableMap;
2633

2734
import org.greenrobot.eventbus.EventBus;
2835
import org.greenrobot.eventbus.Subscribe;
2936

3037
import java.lang.reflect.Method;
38+
import java.util.ArrayList;
3139
import java.util.Arrays;
32-
import java.util.regex.Pattern;
3340
import java.util.List;
41+
import java.util.regex.Pattern;
42+
3443
public class RNInAppBrowser {
3544
private final static String ERROR_CODE = "InAppBrowser";
3645
private static final String KEY_TOOLBAR_COLOR = "toolbarColor";
@@ -63,6 +72,18 @@ public class RNInAppBrowser {
6372
private Activity currentActivity;
6473
private static final Pattern animationIdentifierPattern = Pattern.compile("^.+:.+/");
6574

75+
@Nullable
76+
private CustomTabsClient customTabsClient;
77+
78+
private static RNInAppBrowser _inAppBrowser;
79+
80+
public static RNInAppBrowser getInstance() {
81+
if (_inAppBrowser == null) {
82+
_inAppBrowser = new RNInAppBrowser();
83+
}
84+
return _inAppBrowser;
85+
}
86+
6687
public Integer setColor(CustomTabsIntent.Builder builder, final ReadableMap options, String key, String method, String colorName) {
6788
String colorString = null;
6889
Integer color = null;
@@ -309,4 +330,57 @@ private String getDefaultBrowser(Context context) {
309330
}
310331
return packageName;
311332
}
333+
334+
public void onStart(Activity activity) {
335+
Context applicationContext = activity.getApplicationContext();
336+
CustomTabsServiceConnection connection = new CustomTabsServiceConnection() {
337+
@Override
338+
public void onCustomTabsServiceConnected(@NonNull ComponentName name, @NonNull CustomTabsClient client) {
339+
customTabsClient = client;
340+
if (!customTabsClient.warmup(0L)) {
341+
System.err.println("Couldn't warmup custom tabs client");
342+
}
343+
applicationContext.unbindService(this);
344+
}
345+
346+
@Override
347+
public void onServiceDisconnected(ComponentName name) {
348+
customTabsClient = null;
349+
}
350+
};
351+
352+
final String packageName = getDefaultBrowser(applicationContext);
353+
if (packageName != null) {
354+
CustomTabsClient.bindCustomTabsService(applicationContext, packageName, connection);
355+
} else {
356+
System.err.println("No browser supported to bind custom tab service");
357+
}
358+
}
359+
360+
public void warmup(final Promise promise) {
361+
if (customTabsClient != null) {
362+
promise.resolve(customTabsClient.warmup(0L));
363+
}
364+
promise.resolve(false);
365+
}
366+
367+
public void mayLaunchUrl(String mostLikelyUrl, ReadableArray otherUrls) {
368+
if (customTabsClient != null) {
369+
final CustomTabsSession customTabsSession = customTabsClient.newSession(new CustomTabsCallback());
370+
if (customTabsSession != null) {
371+
final ArrayList<Bundle> otherUrlBundles = new ArrayList<>(otherUrls.size());
372+
373+
for (int index = 0; index < otherUrls.size(); index++) {
374+
String link = otherUrls.getString(index);
375+
if (link != null) {
376+
final Bundle bundle = new Bundle();
377+
bundle.putParcelable(CustomTabsService.KEY_URL, Uri.parse(link));
378+
otherUrlBundles.add(bundle);
379+
}
380+
}
381+
382+
customTabsSession.mayLaunchUrl(Uri.parse(mostLikelyUrl), null, otherUrlBundles);
383+
}
384+
}
385+
}
312386
}

android/src/main/java/com/proyecto26/inappbrowser/RNInAppBrowserModule.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,19 @@
66
import com.facebook.react.bridge.ReactApplicationContext;
77
import com.facebook.react.bridge.ReactContextBaseJavaModule;
88
import com.facebook.react.bridge.ReactMethod;
9+
import com.facebook.react.bridge.ReadableArray;
910
import com.facebook.react.bridge.ReadableMap;
1011
import com.facebook.react.module.annotations.ReactModule;
1112

1213
@ReactModule(name = RNInAppBrowserModule.NAME)
1314
public class RNInAppBrowserModule extends ReactContextBaseJavaModule {
1415
public final static String NAME = "RNInAppBrowser";
1516

16-
private final RNInAppBrowser inAppBrowser;
1717
private final ReactApplicationContext reactContext;
1818

1919
public RNInAppBrowserModule(ReactApplicationContext reactContext) {
2020
super(reactContext);
2121
this.reactContext = reactContext;
22-
this.inAppBrowser = new RNInAppBrowser();
2322
}
2423

2524
@Override
@@ -30,16 +29,31 @@ public String getName() {
3029
@ReactMethod
3130
public void open(final ReadableMap options, final Promise promise) {
3231
final Activity activity = getCurrentActivity();
33-
inAppBrowser.open(this.reactContext, options, promise, activity);
32+
RNInAppBrowser.getInstance().open(this.reactContext, options, promise, activity);
3433
}
3534

3635
@ReactMethod
3736
public void close() {
38-
inAppBrowser.close();
37+
RNInAppBrowser.getInstance().close();
3938
}
4039

4140
@ReactMethod
4241
public void isAvailable(final Promise promise) {
43-
inAppBrowser.isAvailable(this.reactContext, promise);
42+
RNInAppBrowser.getInstance().isAvailable(this.reactContext, promise);
4443
}
44+
45+
public static void onStart(final Activity activity) {
46+
RNInAppBrowser.getInstance().onStart(activity);
47+
}
48+
49+
@ReactMethod
50+
public void warmup(final Promise promise) {
51+
RNInAppBrowser.getInstance().warmup(promise);
52+
}
53+
54+
@ReactMethod
55+
public void mayLaunchUrl(final String mostLikelyUrl, final ReadableArray otherUrls) {
56+
RNInAppBrowser.getInstance().mayLaunchUrl(mostLikelyUrl, otherUrls);
57+
}
58+
4559
}

example/App.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* @flow strict-local
77
*/
88

9-
import React, {useCallback, useState} from 'react';
9+
import React, {useEffect, useCallback, useState} from 'react';
1010
import {
1111
Platform,
1212
StatusBar,
@@ -17,6 +17,7 @@ import {
1717
Button,
1818
} from 'react-native';
1919
import {openLink, tryDeepLinking} from './utils';
20+
import {InAppBrowser} from 'react-native-inappbrowser-reborn';
2021

2122
const instructions = Platform.select({
2223
ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',
@@ -29,6 +30,10 @@ const App = () => {
2930
const [url, setUrl] = useState('https://reactnative.dev');
3031
const [statusBarStyle] = useState('dark-content');
3132

33+
useEffect(() => {
34+
InAppBrowser.mayLaunchUrl('https://reactnative.dev', []);
35+
}, []);
36+
3237
const onOpenLink = useCallback(async () => {
3338
await openLink(url, statusBarStyle);
3439
}, [url, statusBarStyle]);

example/android/app/src/main/java/com/example/MainActivity.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.example;
22

33
import com.facebook.react.ReactActivity;
4+
import com.proyecto26.inappbrowser.RNInAppBrowserModule;
45

56
public class MainActivity extends ReactActivity {
67

@@ -12,4 +13,10 @@ public class MainActivity extends ReactActivity {
1213
protected String getMainComponentName() {
1314
return "example";
1415
}
16+
17+
@Override
18+
protected void onStart() {
19+
super.onStart();
20+
RNInAppBrowserModule.onStart(this);
21+
}
1522
}

index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ declare module 'react-native-inappbrowser-reborn' {
7272
options?: InAppBrowserOptions,
7373
) => Promise<BrowserResult>;
7474
close: () => void;
75+
warmup: () => Promise<boolean>;
76+
mayLaunchUrl: (mostLikelyUrl: string, otherUrls: Array<string>) => void;
7577
openAuth: (
7678
url: string,
7779
redirectUrl: string,

index.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
InAppBrowserOptions,
1313
} from './types';
1414
import {
15+
isAndroid,
1516
RNInAppBrowser,
1617
openBrowserAsync,
1718
openAuthSessionAsync,
@@ -43,6 +44,22 @@ function close(): void {
4344
RNInAppBrowser.close();
4445
}
4546

47+
function warmup(): Promise<boolean> {
48+
if (isAndroid) {
49+
return RNInAppBrowser.warmup();
50+
}
51+
return Promise.resolve(false);
52+
}
53+
54+
function mayLaunchUrl(
55+
mostLikelyUrl: string,
56+
otherUrls: Array<string> = []
57+
): void {
58+
if (isAndroid) {
59+
RNInAppBrowser.mayLaunchUrl(mostLikelyUrl, otherUrls);
60+
}
61+
}
62+
4663
function closeAuth(): void {
4764
closeAuthSessionPolyfillAsync();
4865
if (authSessionIsNativelySupported()) {
@@ -62,6 +79,8 @@ export const InAppBrowser = {
6279
close,
6380
closeAuth,
6481
isAvailable,
82+
warmup,
83+
mayLaunchUrl,
6584
};
6685

6786
export default InAppBrowser;

utils.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,5 @@ export function authSessionIsNativelySupported(): boolean {
177177
const versionNumber = parseInt(Platform.Version, 10);
178178
return versionNumber >= 11;
179179
}
180+
181+
export const isAndroid = Platform.OS === 'android';

0 commit comments

Comments
 (0)