Integration in a React Native app

This guide describes how to setup a React Native application that integrates with Seamly Web UI using a React Native Webview and how to retain data across initialisations.

Important This guide assumes that there is a hosted app.html which uses the built-in appStorageProvider. Usually this will be provided in your implementation. For this demo we use https://developers.seamly.ai/clients/web-ui/demos/app/index.html.

A demo app for this example can be found at https://gitlab.com/seamly-app/examples/react-native-demo-app.

Components

React Native's own Webview component has been removed from Core since 0.63. For this implementation we will use the most common alternative, React Native Webview. If you use a different Webview implementation please refer to its documentation for details on how to run JavaScript. We also use React Native Async Storage to retain our data between launches.

Connecting the web page

To connect the page, we only have to load a Webview with the right source URI:

render() => (
  WebView
    source={{uri: 'https://developers.seamly.ai/clients/web-ui/demos/app/index.html'}}
  />
)

On the JavaScript side, the default appStorageProvider should be in place. At this point you can run the app and should be able to load a chat client, but it will not store anything yet. In the next two paragraphs we'll store and retrieve the data.

Storing data

React Native Webview comes with a message handler built in. All we have to do is connect this message handler to a specific function and store the data in our chosen data store. We use AsyncStorage for this example, but you can substitute your own version.

onMessageReceived = event => {
  if (event.nativeEvent?.data) {
    AsyncStorage.setItem('@SeamlyBridge', event.nativeEvent.data);
  }
};

Be sure to connect this function to the WebView inside the component:

WebView
  source={{uri: 'https://developers.seamly.ai/clients/web-ui/demos/app/index.html'}}
  onMessage={this.onMessageReceived}
/>

In the Webview, the appStorageProvider will post a message to the onMessage handler. will With the React Native part in place, all that's left is to actually post a message from our storageProvider. Again we will simply use the mechanism already built into the WebView:

set: function(newData) {
  if (window.ReactNativeWebView) {
    window.ReactNativeWebView.postMessage(JSON.stringify(newData))
  }
}

You should be able to build and run the app again, but nothing will change. The data will be stored inside your app, but it isn't being retrieved yet. Let's tackle that next.

Note This solution will simply store all data posted by the loaded webpage. If you want to use the WebView for other purposes, or in general want to have a bit more control, you need to implement your own storageProvider to change the message to include for instance a specific key. Make sure, however, to always send back the same object you receive.

Retrieving data

We will need the stored data on initialisation. The appStorageProvider needs to retrieve the data directly when it is created. Since we use AsyncStorage the data might not be available directly when the component is created. For this example, we will simply render the WebView when we are sure data is loaded. Under normal circumstances this should be almost instantaneous, but your storage provider or other needs might require a more sophisticated approach here. WebView again helps us out by providing a prop for preloading JavaScript: injectedJavaScriptBeforeContentLoaded. All we need to do is retrieve the data from storage, so let's start with a function for that:

preloadData = async () => {
  let value;
  try {
    value = await AsyncStorage.getItem('@SeamlyBridge');
  } catch (e) {
    console.error("Couldn't retrieve data from storage: ", e);
  }
  this.setState({
    loaded: true,
    preloadedData: value ? 'window.seamlyBridgeData=' + value : null,
  });
};

Next, we call this function when we load our component:

componentDidMount() {
  this.preloadData();
}

And connect it to the WebView (as mentioned, only rendering it when loading is finished):

render() {
  if (!this.state.loaded) {
    return null;
  }
  return (
    <WebView
      source={{uri: 'https://developers.seamly.ai/clients/web-ui/demos/app/index.html'}}
      injectedJavaScriptBeforeContentLoaded={this.state.preloadedData}
      onMessage={this.onMessageReceived}
    />
  );
}

That's it for the React Native side. The appStorageProvider uses the data left by our initialisation process

Our app will now build and run and when you start a chat you can leave it and come back and the chat will continue where you left of. Our storage solution is working! But there is one small detail we overlooked...

Avoiding synchronisation issues

We only transmit the stored data on initialisation. This should be fine, since normally the storageProvider get function will only be called when the client starts. But if it ever gets called again, we would be providing it with older data as we never update the internal variable after initialisation. Fortunately this is an easy fix, we can simply return our data to the Javascript directly after receiving it. For this, we need to add a reference to our WebView:

<Webview
  ref={r => this.webView = r}
  // ...
/>

And expand the onMessageReceived handler:

onMessageReceived = event => {
  if (event.nativeEvent?.data) {
    AsyncStorage.setItem('@SeamlyBridge', event.nativeEvent.data);
    this.webView.injectJavaScript(`window.seamlyBridgeData=${event.nativeEvent.data}`)
  }
};