Home » Android » ReactNative – Webview not executing injectedJavaScript on Android

ReactNative – Webview not executing injectedJavaScript on Android

Posted by: admin June 15, 2020 Leave a comment

Questions:

I’m having trouble getting the Webview on ReactNative to execute the injected JavaScript on a physical Android device. I’ve scoured the web as far as I could over the past 2 days and still haven’t found a solution. Results for testing are as follows:

  1. iOS simulator – All good
  2. iPhone – All good
  3. Android simulator – All good
  4. Physical devices, Sony Xperia Z2, Sony Xperia Z5 Compact and LG G4 – NOTHING

My Webview is defined as follows:

<WebView
  style={styles.webView}
  source={{
    html: html,
    baseUrl: 'web/'
  }}
  injectedJavaScript={'render(' + JSON.stringify(this.state.data) + ');'}
  javaScriptEnabledAndroid={true}
  scrollEnabled={false}
  bounces={false}
  renderLoading={() => <LoadingIndicator />}
/>

I’ve tried specifying javaScriptEnabled as well, to no avail. I also tried smaller scripts to just colour elements on the page or post a message back to the app using window.postMessage, but nothing happens. I need to inject the data to the HTML, which will render graphs for me based on the supplied data. My last resort is to manually construct the HTML with the data appended as part of the markup being supplied to the Webview, but I’d really like to keep it simple and just get it to work the way it should.

I’m using the latest version of ReactNative (0.41) and the phones are running Android 6+.

How to&Answers:

I just discovered that the Android WebView appears to inject any JS as a single line, even if it includes line breaks. That means that missing semicolons can definitely cause issues, or, in my case, comments delimited by //. Using /* and */ for comments got my injected JavaScript working again.

Answer:

Well, after leaving this question open for some time and finding a (not so elegant) solution to the problem, I decided I’d share what I ended up doing:

  1. I declared the HTML to be passed to the WebView in a constant string along these lines: const html = '<html>...<script>...[INJECTEDSCRIPT]</script></html>';
  2. I retrieved the data in the componentDidMount() event of the React component’s lifecycle and set a state variable for it using this.setState({ data: retrievedData });
  3. This, of course, forced the component to re-render itself, after which I now have the data available to “pass” to the WebView
  4. Seeing as I couldn’t find any elegant or usable way of using the injectedJavaScript property to work the way I want it to (as stated in the question), I resorted to replacing the [INJECTEDSCRIPT] value in the HTML constant with the serialized data.

Not the most elegant solution, I know, but it’s the only one I could get working reliably across a multitude of devices and emulator configurations. Sample, edited for brevity, as below:

const html = `
<!DOCTYPE html>
<html>
  <head>...</head>
  <body>...</body>
  <script>
    var render = function (data) {
      ...
    };
    [INJECTEDSCRIPT]
  </script>
</html>`;

export class GraphComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  componentDidMount = () => {
    SERVICE.getData().done((data) => {
      this.setState({ data: data });
    });
  }
  render = () => {
    if (!this.state.data)
      return <LoadingIndicator />;
    let serializedData = JSON.stringify(this.state.data);
    return
      <WebView
        style={styles.webView}
        source={{
          html: html.replace('[INJECTEDSCRIPT]', 'render(' + serializedData + ');'),
          baseUrl: 'web/'
        }}
        scrollEnabled={false}
        bounces={false}
        renderLoading={() => <LoadingIndicator />}
      />;
  }
}

Answer:

I have been struggling to get Javascript (pre-embed and injected) to be executed as well. So far, a simple and better solution is to use WebViewBridge. After, everything worked as expected. Here a link to the package: cnpmjs.org/package/react-native-webview-bridge.

Here a demo:

import React, {
    Component
} from 'react';

 import PropTypes from 'prop-types';

 import {
     Platform,
     WebView,
     ActivityIndicator,
} from 'react-native'; 

import WevViewBridge from 'react-native-webview-bridge';

// TODO: Keep in mind that you should ALWAYS edit
//       two separate file and keep them matched.
const IOS_WEB_PAGE_SOURCE = './webPage/wordBody.html';
const ANDROID_WEB_PAGE_SOURCE = 'file:///android_asset/webPage/wordBody.html';

export default class WordBody extends Component {

    static propTypes = {
        data: PropTypes.object.isRequired,
    }

    _injectedScript = `
        (function(){
            let data = ${JSON.stringify(this.props.data)};
            refresh(data);
        })();
    `;

    render() {
        /*
         * We are using Platform.select here for two reasons:
         * 0. Everythig work fine for IOS using `WebView` from RN
         * 1. Android doesn't load web ressource with `require`
         * 2. On Android, `WebView` from react-native doesn't
         *    execute JavaScript so we use `WebViewBridge`. 
         */
        return Platform.select({
            ios: (<WebView
                    style = {{backgroundColor: 'rgba(0, 0, 0, 0)', flex:1,}}
                    scalesPageToFit
                    source = {require(IOS_WEB_PAGE_SOURCE)}
                    javaScriptEnabled={true}
                    originWhitelist={['*']}
                    injectedJavaScript = {this._injectedScript}
                    renderLoading={() => <ActivityIndicator/>}
                />),
            android: (<WevViewBridge
                        style = {{backgroundColor: 'rgba(0, 0, 0, 0)', flex:1,}}
                        scalesPageToFit
                        source = {{uri:ANDROID_WEB_PAGE_SOURCE}}
                        javaScriptEnabled={true}
                        // originWhitelist={['*']}
                        injectedJavaScript = {this._injectedScript}
                        renderLoading={() => <ActivityIndicator/>}
                    />)
        });
    };
}