Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ReactNative - Webview not executing injectedJavaScript on Android

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+.

like image 480
FarligOpptreden Avatar asked Feb 28 '17 19:02

FarligOpptreden


2 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.

like image 133
David Alan Hjelle Avatar answered Nov 13 '22 20:11

David Alan Hjelle


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 />}
      />;
  }
}
like image 32
FarligOpptreden Avatar answered Nov 13 '22 19:11

FarligOpptreden