Search code examples
javascriptandroidreactjswebview

Interact with React app in Android WebView


Here is my simplified setup

//--- sample.com app ---//

async fetchFromServer() {
    // return promise..
}

function App() {
    const [sampleState, setSampleState] = React.useState(null)

    React.useEffect(() => {
        fetchFromServer().then((data) => {
            setSampleState(data)
        })
    }, [])

  return (
    <p>{JSON.stringify(sampleState)}</p>
  );
}

//--- android app ---//

public class SampleWebActivity extends Activity {
    SamleWebInterface mWebInterface;
    WebView mWebView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        mWebView = new WebView(this);
        setContentView(mWebView);
    
        mWebInterface = new SamleWebInterface();
        mWebView.addJavascriptInterface(mWebInterface, "Native");
        //...
        mWebView.loadUrl("https://sample.com");
    }
}

// Somewhere in another activity
private void showWebScreen() {
    Intent intent = new Intent(this, SampleWebActivity.class);
    // use existing activity if present
    intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
    startActivity(intent);
}

//--- Question ---//

How can I re-fetch sampleState from server every time SampleWebActivity is shown without reloading the whole page? I want to aviod reloading because the actual web app is much bigger than the sample. Also I don't know the exact state of the web app so it's not clear which url to load. I want to show whetever whas shown before the activity was switched but with updated data.

I'm aware of WebView.evaluateJavascript() but don't know how to interact with the react app after it's compiled into vanilla js.


Solution

  • Using the Webview (browser) itself:

    You could just use the webview's built in event system, i.e. When the webview returns from being backgrounded for instance you can leverage the document.visibilityState API (https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilityState):

    async fetchFromServer() {
      // return promise..
    }
    
    function App() {
      const [sampleState, setSampleState] = React.useState(null)
    
      React.useEffect(() => {
          const fetchData = () => {
            if (document.visibilityState !== "hidden") {
              fetchFromServer().then((data) => {
                  setSampleState(data)
              })
            }
          }
          fetchData();
          document.addEventListener("visibilitychange", fetchData);
          return () => document.removeEventListener("visibilitychange", fetchData);
      }, [])
    
    return (
      <p>{JSON.stringify(sampleState)}</p>
    );
    }
    

    Injecting/Calling from Java:

    async fetchFromServer() {
      // return promise..
    }
      
    function App() {
      const [sampleState, setSampleState] = React.useState(null)
    
      React.useEffect(() => {
          const fetchData = () => {
            fetchFromServer().then((data) => {
                setSampleState(data)
            })
          }
          /**
           * Make some globals
           */
          window.__triggerRefetch__ = fetchData;
          window.__setState__ = setSampleState;
          fetchData();
          return () => {
            /**
             * Remove the globals
             */
            delete window.__triggerRefetch__;
            delete window.__setState__;
          }
      }, [])
    
      return (
        <p>{JSON.stringify(sampleState)}</p>
      );
    }
    

    You trigger a refetch with WebView.evaluateJavascript("window.__triggerRefetch__()") or you can inject data with something like WebView.evaluateJavascript("window.__setState__({foo: "bar"})")