Callbacks from webView to JavaScript: sharing data between native code and html

It is a very common technical problem with eDetailing (and other apps using html5 to display content), that you need to share data between your native iOS (or Android, Windows) code and your html presentation. And the code can and does get messy.

When you try to share data between your UIWebView and your JavaScript library, you’ll end up with something like this.

// Business Logic
function doStuff() {
// Get query from user
// Load data from DB
// Show results to user
}

function fetchDataFromDatabase(query) {
// Calling iOS method here
}

function didReceiveDataFromDatabase(data) {
// Receiving data here
}

 

You see the problem with that, you have your business logic defined, then you call the iOS method, and the application will at some point calls some other method as a callback. So you end up storing states all over the global scope and trying to guess, where exactly in the process are you at the moment.

Luckily there have been advences in JavaScript lately, which make this problem go away:

  • Promises
  • Observables
  • Generator functions

Our goal was to remove this break between the sender and the callback in the workflow and be able to focus on the business logic, without having to care about where and how the data is received: I just want a Promise returned when calling the native function.

Catching The Callback

First off, we need to solve the issue of the break and catch the callback without leaving the sender. For this, we can use the Observer pattern, namely ReactiveX. We observe when the callback gets called:

function fetchDataFromDatabase(query) {
  
  // Calling iOS method here
  
  Rx
    .Observable
    .create(function subscribe(observer) {
      // This will be called by the iOS client
      didReceiveDataFromDatabase = function(data) {
        // Parse, process your data if needed.
        observer.next(JSON.parse(data));
      }
    })
    .subscribe(result => {
      // Receiving data here
    });
}

 

Returning The Promise

Now we can simple wrap all this in a Promise and resolve it when the subscriber receives the data. I also added a timeout, so the code won’t hang in case of an error in the iOS code. Also, we use Bluebird as it is muchfaster then the native Promise.

function fetchDataFromDatabase(query) {
  
  // Calling iOS method here
  
  return new Promise((resolve, reject) => {
    
    // Timeout and reject.
    let timeoutId = setTimeout(() => reject(new Error('Timeout')), 3000);

    Rx
      .Observable
      .create(function subscribe(observer) {
        // This will be called by the iOS client
        didReceiveDataFromDatabase = function(data) {
          // Parse, process your data if needed.
          observer.next(JSON.parse(data));
        }
      })
      .subscribe(result => {
        // Receiving data here, resolving promise
        resolve(result);
      });
  });  
}

 

Clean Business Logic Code

Now back to the business logic. Thanks to the Promise, I do not have to leave the scope of my business logic function. Using co (or simple generator functions), will help to keep the code even cleaner.

function doStuff() {
  co(function *() {
    // Get query from user
    
    // Load data from DB
    let data = yield fetchDataFromDatabase(query);

    // Show results to user
    UI.update(data);
  });
}

 

And as you have a limited set of functions to call, you can create a nice, abstract and generic library for all your interactions with your native iOS code.

A full example is available here.