• Home
  • Nativescript
  • Bi-directional communication between WebView and Android/iOS in Nativescript

Bi-directional communication between WebView and Android/iOS in Nativescript


Nativescript provides a cross-platform web-view ui element. It serves the purpose of showing web-view content in our page.
But if you want to send some data to web-view or get some data from web-view, you need to write platform specific code for that. So rather than writing all such code in every application, it is better to have a plugin for that. Right?

I have created nativescript-webview-interface plugin to serve this purpose. You can clone the demo application to quickly get started with this plugin.

Output of the demo appliaction is as shown below:

Android

nativescript-webivew-interface-android-demo

iOS

nativescript-webview-interface-ios-demo

In this demo application, we have languages dropdown in web-view and we can add new language and query currently selected language in dropdown, from native side. The dropdown is also emitting event to native, whenever the selection is changed.

Let’s understand step-by-step, how this is working.

Step 1: Initialization of plugin

First we need to install the plugin by following the instruction at https://www.npmjs.com/package/nativescript-webview-interface#installation).
Once the plugin is installed, let’s add web-view element to our page.

main-page.xml

<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="pageLoaded">
....
<web-view id="webView" src="~/www/index.html"></web-view>
....
</Page>

Now, initialize the plugin, in our main-page.ts file. (Here, I am using typescript, but you can check the related code in javascript at https://github.com/shripalsoni04/nativescript-webview-interface-demo)

main-page.ts

import {webViewInterfaceDemoVM} from './main-view-model';
var webViewInterfaceModule = require('nativescript-webview-interface');
var oLangWebViewInterface;

export function pageLoaded(args){
    page = args.object;
    page.bindingContext = webViewInterfaceDemoVM;
    setupWebViewInterface(page) 
}

/**
 * Initializes webViewInterface for communication between webview and android/ios
 */
function setupWebViewInterface(page: Page){
    var webView = page.getViewById('webView');
    oLangWebViewInterface = new webViewInterfaceModule.WebViewInterface(webView);
}

Check the code of main-view-model for reference.

Assuming we have copied the plugin file for web-view by following installation instruction mentioned above, let’s import the plugin file in our index.html

www/index.html

<!doctype html>
<html>
    <body>
        <select id="knownLanguage">
            <option value="-1">----Select Language----</option>
        </select>
        <script src="./lib/nativescript-webview-interface.js"></script>
        <script src="./index.js"></script>
    </body>
</html>

Once the plugin file is imported in html file, we will have access to all the necessary API, to communicate to native app from web-view, in window.nsWebViewInterface global object.

www/index.js

(function(){
    // oWebViewInterface provides necessary APIs for communication to native app.
    var oWebViewInterface = window.nsWebViewInterface;
    var languageDD = document.getElementById('knownLanguage');

    function init(){

    }

    init();
})()

Step 2: Loading languages in dropdown from native app

Now, we want to load the dropdown with some default languages loaded in it. We can do it, by emitting an event to web-view with the default languages, once the web-view is loaded.

main-page.ts

function setupWebViewInterface(page: Page){
    ...
    // loading languages in dropdown, on load of webView.
    webView.on(WebView.loadFinishedEvent, (args: LoadEventData) => {
        if (!args.error) {
            loadLanguagesInWebView();
        }
    });
    ...       
}

/**
 * Sends intial list of languages to webView, once it is loaded 
 */
function loadLanguagesInWebView(){
    oLangWebViewInterface.emit('loadLanguages', webViewInterfaceDemoVM.lstLanguages);
}

Let’s handle loadLanguages event in web-view.

www/index.js

function init(){
    ...
    addNativeMsgListener();
    ...
}

/**
 * Registers handlers for native events.
 */
function addNativeMsgListener() {
    oWebViewInterface.on('loadLanguages', function (arrLanguages) {
        for (var i = 0; i < arrLanguages.length; i++) {
            addLanguageOption(arrLanguages[i]);
        }
    });
}

/**
 *  Adds language to dropdown options 
 */
function addLanguageOption(language){
    var option = document.createElement('Option');
    option.text = language;
    option.value = language;
    languageDD.appendChild(option);
}

Step 3: Notify native app when language is changed in dropdown

To let the native app know, whenever the language is changed in dropdown, we need to emit an event from web-view on selection change.

www/index.js

function init(){
    ...
    languageDD.onchange = function(){
        sendSelectedValue(languageDD.value);
    }
    ...

    /**
     * Sends selected value to native app by emitting an event
     */
    function sendSelectedValue(selectedLanguage){
        oWebViewInterface.emit('languageSelection', selectedLanguage);
    }
}

We need to handle this event at native side, to do any operation on language change.

main-page.ts

function setupWebViewInterface(page: Page){
    ...    
    listenLangWebViewEvents();
    ...
}

/**
 * Handles any event/command emitted by language webview.
 */
function listenLangWebViewEvents(){  
    // handles language selectionChange event.
    oLangWebViewInterface.on('languageSelection', (selectedLanguage) => {
        webViewInterfaceDemoVM.selectedLanguage = selectedLanguage;
    });
}

Step 4: Add new language in dropdown from native

To add a new language into dropdown options from native app, we can directly call a JS function in web-view which handles this operation.

main-page.xml

<GridLayout rows="50" columns="*, 70">
    <TextField id="txtLanguage" col="0" hint="Language" />
    <Button col="1" text="Add" tap="addLanguage" />
</GridLayout>

main-page.ts

/**
 * Adds language to webView dropdown
 */
export function addLanguage(){
    var txtField = <TextField>page.getViewById('txtLanguage');
    oLangWebViewInterface.callJSFunction('addNewLanguage', [txtField.text]);
}

Here, we are calling addNewLanguage function of web-view, directly from native app. Note that the arguments that we want to pass to addNewLanguage funciton must be in array format.
As we don’t expect any return value on language add operation in web-view, we don’t need to assign any callback function to callJSFunction api.

Let’s create addNewLanguage function in web-view to handle this operation.

www/index.js

function init(){
    ...
    defineNativeInterfaceFunctions();
    ...
}

/**
 * Defines global functions which will be called from andorid/ios
 */
function defineNativeInterfaceFunctions(){

    /**
     *  Handles new language addition initiated by native app
     */
    window.addNewLanguage = function(language){
        addLanguageOption(language);
        languageDD.value = language;
        languageDD.onchange();
    };
}

Step 5: Query data from web-view

Now, we want to get the value of currently selected language from web-view. We can do it by calling a JS function into the web-view.

main-page.ts

/**
 * Fetches currently selected language of dropdown in webView.
 */
export function getSelectedLanguage(){
   oLangWebViewInterface.callJSFunction('getSelectedLanguage', null, (oSelectedLang) => {
        alert(`Selected Language is ${oSelectedLang.text}`);
    });
}

www/index.js

function defineNativeInterfaceFunctions(){
    ...
    window.getSelectedLanguage = function(){
        var selectedLangOpt = languageDD.options[languageDD.selectedIndex];
        return {id: selectedLangOpt.value, text: selectedLangOpt.text};
    }; 
    ...  
}

Step 6: Return deferred result from web-view on call of JS function

As per the async nature of javscript, sometimes we cannot have immediate result to return on call of a JS function in web-view, so in such cases, we can return a promise on a JS function call.
So whenever the promise is resolved, the callback function registered in native app side will be called with the resolved value of the promise.

main-page.ts

/**
 * Fetches currently selected language of dropdown in webview.
 * The result will come after 2s. This function is written to show the support of deferred result.
 */
export function getSelectedLanguageDeferred(){
   oLangWebViewInterface.callJSFunction('getSelectedLanguageDeferred', null, (oSelectedLang) => {
        alert(`Deferred Selected Language is ${oSelectedLang.text}`);
    });     
}

www/index.js

function defineNativeInterfaceFunctions(){
    ...
    window.getSelectedLanguageDeferred = function(){
        var selectedLangOpt = languageDD.options[languageDD.selectedIndex];
        return new Promise(function(resolve) {
            setTimeout(function(){
                resolve({id: selectedLangOpt.value, text: selectedLangOpt.text});        
            }, 2000);
        });
    };
    ...  
}

I know this is very trivial application, but I found this application proper to explain all the scenarios, that can be handled by nativescript-webivew-interface plugin.
I have also created one more application which serves a valid use case. I will post it about in future.

Hope you have found this tutorial useful.

  • It would be nice if both TypeScript and JavaScript codes were shown. It’s confusing to have TS and JS all scrambled up through your tutorial. At least for a rookie like me.

  • SMS 1980

    Can i send some http posts via http request and have a callback in my webview?

Menu