Skip to content

Commit

Permalink
Enables communication between the webview and RN
Browse files Browse the repository at this point in the history
Android devs have been relying for ages on intercepting custom HTTP
calls made by the webview, to map them to native actions, [like here](https://www.caphal.com/android/communication-between-application-and-webview/).

Say that we have a "close" button in our web interface. We first detect
if we're running on web or within a webview -- if on web, we simply call `window.close()`.
If we're running within the app things get a bit more complicated, but we can
simply call `app://exit` and the app will be able to intercept this call and
close the webview for us.

Now, this PR adds the ability of doing it on RN. You basically specify a
protocol for messages that should be forwarded to RN (ie. `rn`) and then,
when your webview loads a resource from `rn://xyz` the `onMessage` callback
will be called. You can access the URL etc etc through `event.nativeEvent.url`.

Example:

``` javascript
let onMessage = (event) => {
  let {url} = event.nativeEvent;

  if (url.endsWith('exit')) {
    BackAndroid.exitApp(); // just sayin'...
  }
}

return <Webview
  source={...}
  messageProtocol={'myApp'}
  onMessage={onMessage}
>
```

Or something of that sort. Communicating between the webview and RN / the ability
to intercept resources loaded within the webview has been already discussed but I
feel we need a very simple implementation rather than creating a full-blown
messaging pattern which will just hurt (maintainability, complexity) in the long run.

Also, my Java is pretty rusty ;-)
  • Loading branch information
odino committed Apr 17, 2016
1 parent 93b39b7 commit 0d74c6f
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 1 deletion.
17 changes: 17 additions & 0 deletions Libraries/Components/WebView/WebView.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,21 @@ var WebView = React.createClass({
*/
domStorageEnabled: PropTypes.bool,

/**
* A callback that will be executed once the webview
* sends a message to RN.
* @platform android
*/
onMessage: PropTypes.func,

/**
* A protocol that the webview can use to communicate with RN.
* When the WebView triggers requests to myProtocol://something
* the 'onMessage' callback passed to the webview will be called.
* @platform android
*/
messageProtocol: PropTypes.string,

/**
* Sets the JS to be injected when the webpage loads.
*/
Expand Down Expand Up @@ -226,6 +241,8 @@ var WebView = React.createClass({
contentInset={this.props.contentInset}
automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets}
onLoadingStart={this.onLoadingStart}
onMessage={this.props.onMessage}
messageProtocol={this.props.messageProtocol}
onLoadingFinish={this.onLoadingFinish}
onLoadingError={this.onLoadingError}
testID={this.props.testID}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;

import com.facebook.react.views.webview.events.MessageEvent;
import com.facebook.react.views.webview.events.TopLoadingErrorEvent;
import com.facebook.react.views.webview.events.TopLoadingFinishEvent;
import com.facebook.react.views.webview.events.TopLoadingStartEvent;
Expand Down Expand Up @@ -86,6 +89,21 @@ private static class ReactWebViewClient extends WebViewClient {

private boolean mLastLoadFailed = false;

@Override
public WebResourceResponse shouldInterceptRequest (final WebView webView, WebResourceRequest request) {
ReactWebView reactWebView = (ReactWebView) webView;

if (reactWebView.getMessageProtocol().equals(request.getUrl().getScheme())) {
WritableMap eventData = Arguments.createMap();
eventData.putString("url", request.getUrl().toString());
eventData.putString("method", request.getMethod().toString());

dispatchEvent(webView, new MessageEvent(webView.getId(), SystemClock.nanoTime(), eventData));
}

return super.shouldInterceptRequest(webView, request);
}

@Override
public void onPageFinished(WebView webView, String url) {
super.onPageFinished(webView, url);
Expand Down Expand Up @@ -180,6 +198,7 @@ private WritableMap createWebViewEvent(WebView webView, String url) {
*/
private static class ReactWebView extends WebView implements LifecycleEventListener {
private @Nullable String injectedJS;
private @Nullable String messageProtocol;

/**
* WebView must be created with an context of the current activity
Expand Down Expand Up @@ -211,6 +230,14 @@ public void setInjectedJavaScript(@Nullable String js) {
injectedJS = js;
}

public String getMessageProtocol() {
return messageProtocol;
}

public void setMessageProtocol(@Nullable String mp) {
messageProtocol = mp;
}

public void callInjectedJavaScript() {
if (getSettings().getJavaScriptEnabled() &&
injectedJS != null &&
Expand Down Expand Up @@ -270,7 +297,6 @@ public void setDomStorageEnabled(WebView view, boolean enabled) {
view.getSettings().setDomStorageEnabled(enabled);
}


@ReactProp(name = "userAgent")
public void setUserAgent(WebView view, @Nullable String userAgent) {
if (userAgent != null) {
Expand All @@ -289,6 +315,11 @@ public void setInjectedJavaScript(WebView view, @Nullable String injectedJavaScr
((ReactWebView) view).setInjectedJavaScript(injectedJavaScript);
}

@ReactProp(name = "messageProtocol")
public void setMessageProtocol(WebView view, @Nullable String messageProtocol) {
((ReactWebView) view).setMessageProtocol(messageProtocol);
}

@ReactProp(name = "source")
public void setSource(WebView view, @Nullable ReadableMap source) {
if (source != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

package com.facebook.react.views.webview.events;

import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;

/**
* Event emitted when there is an error in loading.
*/
public class MessageEvent extends Event<MessageEvent> {

public static final String EVENT_NAME = "message";
private WritableMap mEventData;

public MessageEvent(int viewId, long timestampMs, WritableMap eventData) {
super(viewId, timestampMs);
mEventData = eventData;
}

@Override
public String getEventName() {
return EVENT_NAME;
}

@Override
public boolean canCoalesce() {
return false;
}

@Override
public short getCoalescingKey() {
// All events for a given view can be coalesced.
return 0;
}

@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
}
}

0 comments on commit 0d74c6f

Please sign in to comment.