Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Normal open function returns incredibly slowly when canceling under certain circumstances on Android #155

Open
TheWirv opened this issue May 6, 2020 · 11 comments
Labels
help wanted Extra attention is needed

Comments

@TheWirv
Copy link
Contributor

TheWirv commented May 6, 2020

Hi, I have the strange problem that, after canceling on Android, the InAppBrowser.open function takes a very long time to return with the result ({type: "cancel"}) under certain circumstances. The worst thing is that I can't really pin down said circumstances. All I know for sure is that, in contrast, InAppBrowser.openAuth returns instantly after being canceled.

I made a video of it all and uploaded it to YouTube. What I did was the following:

  • InAppBrowser.open imprint link (dark theme); then close and wait
  • InAppBrowser.open privacy policy link (dark theme); then close and wait
  • Switch to different tab => theme changes
  • InAppBrowser.open (the same) imprint link (light theme); then close and wait (a long time)
  • Add some products to cart and select payment method
  • Check out => InAppBrowser.openAuth PayPal (light theme); then close

In App Browser anomaly showcase

@jdnichollsc Do you have an inkling of an idea what could be the cause of this delay? It's almost ten seconds. Additionally, it has the effect that during that duration any other call to open fails, or rather the open call from before finally returns with {type: "cancel"} which, I guess, directly cancels the new call.

The code is rather distributed and would therefore be difficult to share, but if that would be of substantial help I could pull out a few snippets, I guess.

Which platform(s) does your issue occur on?

  • iOS/Android/both: Only an issue on Android. On iOS, both open and openAuth return in an instant, no matter what.
  • iOS/Android versions: Android 10
  • emulator or device. What type of device? Emulator and Samsung Galaxy S10

Please, provide the following version numbers that your issue occurs with:

  • CLI: 4.8.0 (I ran npx react-native --version to fetch it, instead of react-native --version)
  • Plugin(s):
"dependencies": {
  "@react-native-community/async-storage": "^1.9.0",
  "@react-native-community/blur": "^3.4.1",
  "@react-native-community/masked-view": "^0.1.7",
  "@react-native-community/netinfo": "^5.5.1",
  "@react-native-community/viewpager": "^3.3.0",
  "@react-navigation/bottom-tabs": "^5.2.4",
  "@react-navigation/drawer": "^5.3.4",
  "@react-navigation/native": "^5.1.3",
  "@react-navigation/stack": "^5.2.6",
  "color": "^3.1.2",
  "react": "^16.13.1",
  "react-native": "^0.62.2",
  "react-native-gesture-handler": "^1.6.0",
  "react-native-inappbrowser-reborn": "^3.4.0",
  "react-native-iphone-x-helper": "^1.2.1",
  "react-native-linear-gradient": "^2.5.6",
  "react-native-localize": "^1.3.4",
  "react-native-reanimated": "1.8.0",
  "react-native-safe-area-context": "^0.7.3",
  "react-native-screens": "^2.0.0",
  "react-native-splash-screen": "^3.2.0",
  "react-native-svg": "^12.0.3",
  "react-native-vector-icons": "^6.6.0",
  "react-redux": "^7.2.0",
  "redux": "^4.0.5",
  "redux-thunk": "^2.3.0"
},
"devDependencies": {
  "@babel/cli": "^7.8.4",
  "@babel/core": "^7.8.6",
  "@babel/preset-env": "^7.8.6",
  "@babel/runtime": "^7.8.4",
  "@react-native-community/eslint-config": "^0.0.6",
  "@react-native-community/eslint-plugin": "^1.0.0",
  "babel-jest": "^25.1.0",
  "babel-plugin-lodash": "^3.3.4",
  "babel-plugin-module-resolver": "^4.0.0",
  "babel-plugin-transform-remove-console": "^6.9.4",
  "eslint": "^6.8.0",
  "eslint-config-prettier": "^6.10.0",
  "eslint-import-resolver-babel-module": "^5.1.2",
  "eslint-plugin-import": "^2.20.1",
  "eslint-plugin-prettier": "^3.1.2",
  "jest": "^25.1.0",
  "metro-react-native-babel-preset": "^0.58.0",
  "patch-package": "^6.2.2",
  "postinstall-postinstall": "^2.0.0",
  "prettier": "^1.19.1",
  "react-test-renderer": "^16.13.1"
}
@jdnichollsc
Copy link
Member

Can you share your code with the options you're using?

@TheWirv
Copy link
Contributor Author

TheWirv commented May 6, 2020

Can you share your code with the options you're using?

I'll try..

So, on the one hand, I've built a wrapper class around the InAppBrowser for ease-of-use.
Info: The Platform.setOnIosAndAndroid() is basically just an

if (Platform.OS === 'ios') {
   // return first param
} else if (Platform.OS === 'android') {
  // return second param
}

Here it goes:

import {Linking, Alert} from 'react-native';
import InAppBrowser from 'react-native-inappbrowser-reborn';
// Module registry and config
import {theme} from 'root/app-config';
// Utils
import {Platform} from '@cineorder/utils/helpers';
// Themes and styles
import {Colors} from '@cineorder/themes';

class Browser {
  constructor() {
    this.isAvailable = null;
  }

  checkForAvailability = async () => {
    this.isAvailable = await InAppBrowser.isAvailable();
  };

  openLink = async (url, auth = false, redirectUrl?, options?) => {
    try {
      let result;

      if (this.isAvailable) {
        console.log(
          `opening ${Platform.setOnIosAndAndroid(
            'Safari View Controller',
            'Chrome Custom Tab',
          )}...`,
        );

        let browserConfig = Platform.setOnIosAndAndroid(
          {
            dismissButtonStyle: auth ? 'cancel' : 'close',
            readerMode: false,
            animated: true,
            modalEnabled: true,
          },
          {
            showTitle: false,
            toolbarColor: Colors[options?.theme ?? theme].background.regular,
            secondaryToolbarColor: Colors.snow,
            enableUrlBarHiding: true,
            enableDefaultShare: false,
            forceCloseOnRedirection: false,
            animations: {
              startEnter: 'slide_in_down',
              startExit: 'slide_out_up',
              endEnter: 'slide_in_up',
              endExit: 'slide_out_down',
            },
          },
        );

        if (options) {
          browserConfig = {
            ...browserConfig,
            ...options,
          };
        }

        if (auth) {
          console.log('opening Link in auth window...');
          result = await InAppBrowser.openAuth(url, redirectUrl, browserConfig);
          console.log('result:', result);
        } else {
          console.log('opening Link in normal window...');
          result = await InAppBrowser.open(url, browserConfig);
          console.log('result:', result);
        }
      } else {
        console.log(
          `unfortunately, not able to open ${Platform.setOnIosAndAndroid(
            'Safari View Controller',
            'Chrome Custom Tab',
          )}...`,
        );

        Linking.openURL(url);
      }

      return result;
    } catch (e) {
      console.error('An error occured in InAppBrowser:', e.message);
      Alert.alert('Error occured in InAppBrowser', e.message);
    }
  };
}

export default new Browser();

Under normal circumstances, the code for the actual opening of the browser windows is written like this:

if (auth) {
  console.log('opening Link in auth window...');
  return await InAppBrowser.openAuth(url, redirectUrl, browserConfig);
}

console.log('opening Link in normal window...');
return await InAppBrowser.open(url, browserConfig);

But for debugging purposes I funneled the result into a variable and console.logged it.

Then, when calling it, I call it like this. Analogous to the aforementioned Platform.setOnIosAndAndroid(), Platform.setOnAndroid() is basically just

if (Platform.OS === 'android') {
  // return param
} else {
  return undefined
}

Here's the call:

Browser.openLink(
  props.children.url,
  false,
  null,
  Platform.setOnAndroid({
    toolbarColor: props.children.browserColor ?? Colors[theme].primary,
    theme,
  }),
);

I console.logged the calls and the options for the first three calls (which were all without auth) are as follows:

{
  animations: {
    startEnter: 'slide_in_down',
    startExit: 'slide_out_up',
    endEnter: 'slide_in_up',
    endExit: 'slide_out_down',
  },
  enableDefaultShare: false,
  enableUrlBarHiding: true,
  forceCloseOnRedirection: false,
  secondaryToolbarColor: 'white',
  showTitle: false,
  theme: /* 'light' or 'dark', depending on the calling screen */
  toolbarColor: '#28A3D9',
}

The options object for the auth call is almost identical:

{
  animations: {
    startEnter: 'slide_in_down',
    startExit: 'slide_out_up',
    endEnter: 'slide_in_up',
    endExit: 'slide_out_down',
  },
  enableDefaultShare: false,
  enableUrlBarHiding: true,
  forceCloseOnRedirection: false,
  secondaryToolbarColor: 'white',
  showTitle: false,
  theme: 'light',
  toolbarColor: '#0070BA', // PayPal blue; only difference
}

I hope this helps. If you need anything else, let me know!

@TheWirv
Copy link
Contributor Author

TheWirv commented May 6, 2020

This is not an issue on iOS. I have updated the initial post.

@TheWirv
Copy link
Contributor Author

TheWirv commented May 7, 2020

Did some more digging and it turns out that it doesn't have to do anything with whether open or openAuth is being called. Opening the browser only returns incredibly slowly on that one screen.
Is it possible that it takes so long because that screen's component is much more complex than the other screens'? It holds a Tab View (utilizing a Native ViewPager) with approx. 10 sub-views, each consisting of a vertically scrolling FlatList, which, in turn, each consists of a number of product cards in a 2-col-layout.

@jdnichollsc
Copy link
Member

@TheWirv I don't think because this plugin don't have React components, it's only a wrapper to call a native component of the device by just calling native code, also without using EventEmitters or something like that

@TheWirv
Copy link
Contributor Author

TheWirv commented May 7, 2020

@TheWirv I don't think because this plugin don't have React components, it's only a wrapper to call a native component of the device by just calling native code, also without using EventEmitters or something like that

I thought exactly the same. On the other hand, I suspected the open and openAuth functions, which are also JavaScript after all. Maybe they are taking so much time to return whereas the native component has already exited long before.

And if it's not that, what could be the reason? I could easily look a bit more into it, if you can give me some direction as to where exactly I should check.

@jdnichollsc
Copy link
Member

jdnichollsc commented May 7, 2020

Do you have this configuration? https://github.com/proyecto26/react-native-inappbrowser/blob/master/example/android/app/src/main/AndroidManifest.xml#L18
android:launchMode="singleTask"

@TheWirv
Copy link
Contributor Author

TheWirv commented May 7, 2020

Yes, this is my AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  package="com.compeso.cineorder">

  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

  <application
    android:name=".MainApplication"
    android:label="@string/app_name"
    android:icon="@mipmap/ic_launcher"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:allowBackup="false"
    android:theme="@style/AppTheme">
    <activity
      android:name=".SplashActivity"
      android:theme="@style/SplashTheme"
      android:label="@string/app_name">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
    <activity
      android:name=".MainActivity"
      android:launchMode="singleTask"
      android:label="@string/app_name"
      android:screenOrientation="portrait"
      android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
      android:windowSoftInputMode="stateHidden|adjustNothing"
      android:exported="true">
      <intent-filter>
        <action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
      </intent-filter>
      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="cineorder" />
      </intent-filter>
    </activity>
    <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
  <provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}"
    android:grantUriPermissions="true"
    android:exported="false"
    tools:replace="android:authorities">
    <meta-data
      android:name="android.support.FILE_PROVIDER_PATHS"
      android:resource="@xml/passkit_file_paths"
      tools:replace="android:resource" />
  </provider>
  </application>

</manifest>

@jdnichollsc
Copy link
Member

mmm very odd, and without a demo project I can't reproduce that issue here :(

@TheWirv
Copy link
Contributor Author

TheWirv commented May 7, 2020

I'll see if there's any possibility for you to reproduce this issue.
This is not an open source project so unfortunately, I can't just give you the code base to play with. :/

@jdnichollsc jdnichollsc added the help wanted Extra attention is needed label May 8, 2020
@akshy78695
Copy link

Can you share your code with the options you're using?

I'll try..

So, on the one hand, I've built a wrapper class around the InAppBrowser for ease-of-use. Info: The Platform.setOnIosAndAndroid() is basically just an

if (Platform.OS === 'ios') {
   // return first param
} else if (Platform.OS === 'android') {
  // return second param
}

Here it goes:

import {Linking, Alert} from 'react-native';
import InAppBrowser from 'react-native-inappbrowser-reborn';
// Module registry and config
import {theme} from 'root/app-config';
// Utils
import {Platform} from '@cineorder/utils/helpers';
// Themes and styles
import {Colors} from '@cineorder/themes';

class Browser {
  constructor() {
    this.isAvailable = null;
  }

  checkForAvailability = async () => {
    this.isAvailable = await InAppBrowser.isAvailable();
  };

  openLink = async (url, auth = false, redirectUrl?, options?) => {
    try {
      let result;

      if (this.isAvailable) {
        console.log(
          `opening ${Platform.setOnIosAndAndroid(
            'Safari View Controller',
            'Chrome Custom Tab',
          )}...`,
        );

        let browserConfig = Platform.setOnIosAndAndroid(
          {
            dismissButtonStyle: auth ? 'cancel' : 'close',
            readerMode: false,
            animated: true,
            modalEnabled: true,
          },
          {
            showTitle: false,
            toolbarColor: Colors[options?.theme ?? theme].background.regular,
            secondaryToolbarColor: Colors.snow,
            enableUrlBarHiding: true,
            enableDefaultShare: false,
            forceCloseOnRedirection: false,
            animations: {
              startEnter: 'slide_in_down',
              startExit: 'slide_out_up',
              endEnter: 'slide_in_up',
              endExit: 'slide_out_down',
            },
          },
        );

        if (options) {
          browserConfig = {
            ...browserConfig,
            ...options,
          };
        }

        if (auth) {
          console.log('opening Link in auth window...');
          result = await InAppBrowser.openAuth(url, redirectUrl, browserConfig);
          console.log('result:', result);
        } else {
          console.log('opening Link in normal window...');
          result = await InAppBrowser.open(url, browserConfig);
          console.log('result:', result);
        }
      } else {
        console.log(
          `unfortunately, not able to open ${Platform.setOnIosAndAndroid(
            'Safari View Controller',
            'Chrome Custom Tab',
          )}...`,
        );

        Linking.openURL(url);
      }

      return result;
    } catch (e) {
      console.error('An error occured in InAppBrowser:', e.message);
      Alert.alert('Error occured in InAppBrowser', e.message);
    }
  };
}

export default new Browser();

Under normal circumstances, the code for the actual opening of the browser windows is written like this:

if (auth) {
  console.log('opening Link in auth window...');
  return await InAppBrowser.openAuth(url, redirectUrl, browserConfig);
}

console.log('opening Link in normal window...');
return await InAppBrowser.open(url, browserConfig);

But for debugging purposes I funneled the result into a variable and console.logged it.

Then, when calling it, I call it like this. Analogous to the aforementioned Platform.setOnIosAndAndroid(), Platform.setOnAndroid() is basically just

if (Platform.OS === 'android') {
  // return param
} else {
  return undefined
}

Here's the call:

Browser.openLink(
  props.children.url,
  false,
  null,
  Platform.setOnAndroid({
    toolbarColor: props.children.browserColor ?? Colors[theme].primary,
    theme,
  }),
);

I console.logged the calls and the options for the first three calls (which were all without auth) are as follows:

{
  animations: {
    startEnter: 'slide_in_down',
    startExit: 'slide_out_up',
    endEnter: 'slide_in_up',
    endExit: 'slide_out_down',
  },
  enableDefaultShare: false,
  enableUrlBarHiding: true,
  forceCloseOnRedirection: false,
  secondaryToolbarColor: 'white',
  showTitle: false,
  theme: /* 'light' or 'dark', depending on the calling screen */
  toolbarColor: '#28A3D9',
}

The options object for the auth call is almost identical:

{
  animations: {
    startEnter: 'slide_in_down',
    startExit: 'slide_out_up',
    endEnter: 'slide_in_up',
    endExit: 'slide_out_down',
  },
  enableDefaultShare: false,
  enableUrlBarHiding: true,
  forceCloseOnRedirection: false,
  secondaryToolbarColor: 'white',
  showTitle: false,
  theme: 'light',
  toolbarColor: '#0070BA', // PayPal blue; only difference
}

I hope this helps. If you need anything else, let me know!

I think
if (this.isAvailable)
should be this
if (await this.isAvailable)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants