Telerik blogs

As we all know, mobile devices are exploding in popularity. Suddenly users want to do absolutely everything on their devices and not be tethered to traditional desktop computers.

Which is great, except if you're like most businesses, and have tons of legacy services that aren't ready for use in mobile apps.

The good news is you can absolutely expose these existing services so that they can be accessed from an iOS and Android applications.

In this article we'll do just that. We'll start with a legacy service, expose it over HTTP, and integrate it into our hybrid application built with Icenium. Let's get started by looking at our service.

Our Example

For the purposes of this example, suppose that we work at a large banking site. The banking site recently created mobile apps that lets users get up to date mortgage rates.

But the bank is having a problem - they only offer mortgages to customers that have a credit score of at least 500 - and they're getting a lot of inquires from people that are ineligible. The bank already has a service to order credit scores, and they want to expose it to users so they can check their eligibility directly in the app.

How can the bank accomplish this?

Exposing The Service Over HTTP

Simply put, the only way to access an existing backend system in a native or hybrid app is over HTTP. All native platforms - iOS, Android, etc - provide a means of invoking HTTP requests to send data back and forth.

The great thing about hybrid applications is you can perform these HTTP requests using a single familiar language: JavaScript. So how do we go about actually exposing our existing service?

For our bank, the existing API is a Java Class with a static method that takes the user's first and last name and passes back a Java bean with information about the order such as the score and the order date.

Order order = CreditScore.order("TJ", "VanToll");

Note: Clearly a real credit score order is more complex, but we're simplifying here for the sake of simplicity.

If you're familiar with Java web apps, you know that exposing services like this means creating a Servlet and configuring its URL in the application's deployment descriptor. As an example here's a Servlet that accepts POST requests, grabs "firstName" and "lastName" request parameters from the request, and invokes our existing API.

package com.bank;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CreditScoreServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String firstName = request.getParameter("firstName");
        String lastName = request.getParameter("lastName");
        CreditScore.order(firstName, lastName);
    }
}

Returning JSON Data

We now have a Servlet that orders a credit score, but there's one problem: it doesn't return anything. Since we're going to consume this service in JavaScript, it makes the most sense to return the data in a native JavaScript format - namely, JSON.

There are numerous libraries that can convert Java objects into JSON. We'll use a popular one from Google: Gson. Gson is extremely easy to use; instantiate a Gson object and pass its toJson() method a Java object. The code below shows our Servlet altered to use Gson to convert our Order object into JSON and output it.

package com.bank;

import com.google.gson.Gson;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CreditScoreServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String firstName = request.getParameter("firstName");
        String lastName = request.getParameter("lastName");
        Order order = CreditScore.order(firstName, lastName);

        response.setContentType("application/json");

        Gson gson = new Gson();
        try (PrintWriter out = response.getWriter()) {
            out.print(gson.toJson(order));
        }
    }
}

Now we have a Servlet that we can pass a first and last name, have a credit score ordered, and get back a JSON object with information about the order, including the score.

The last thing we need to do is configure a URL pattern that this Servlet can be accessed at. If Java web apps this means some configuration in the project's deployment descriptor (web.xml). The configuration to add is shown below.

<servlet>
    <servlet-name>CreditScore</servlet-name>
    <servlet-class>com.bank.CreditScoreServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>CreditScore</servlet-name>
    <url-pattern>/CreditScore</url-pattern>
</servlet-mapping>

Assuming this project is hosted at https://example.com, the Servlet can now be accessed at https://example.com/CreditScore. Now that we have a service, let's see how we can actually use it in an app.

Calling The Service

Let's start by building a quick web form to collect this data using Kendo UI Mobile. The code for our form is shown below. Don't worry too much about the details here. Essentially we're just creating a form with two required inputs to collect the user's first and last name.

<style>
    .km-widget.km-button {
        width: 95%;
        margin-left: 0.5em;
    }
    input {
        width: 66%;
        margin-left: 0.5em;
        padding: 0.5em;
    }
</style>
<div data-role="view">
    <header data-role="header">
        <div data-role="navbar">
            <div data-role="view-title">Check Eligibility</div>
        </div>
    </header>
    <form method="POST">
        <ul data-role="listview" data-style="inset">
            <li>
                <label>First Name:
                    <input name="firstName" required>
                </label>
            </li>
            <li>
                <label>Last Name:
                    <input name="lastName" required>
                </label>
            </li>
        </ul>
        <button data-role="button" type="submit" data-rel="external">
            Submit
        </button>
    </form>
</div>
<script>
    document.addEventListener( "deviceready", function () {
        new kendo.mobile.Application( document.body, {
            statusBarStyle: "black-translucent"
        });
    }, false );
</script>

We're using Kendo UI Mobile to get the nice mobile friendly display shown below.

Display of our eligibility form on an iPhone

Next, we have to listen for this form to be submitted by the user and invoke our service. The code below does just this.

$( "form" ).on( "submit", function( event ) {
    event.preventDefault();
    $.post( "https://example.com/Bank/CreditScore", {
        firstName: this.firstName.value,
        lastName: this.lastName.value
    });
});

This code invokes the request, but if you try it out you'll notice it doesn't quite work. Icenium Mist gives you the following warning.

Display of our eligibility form on an iPhone

What's this CORS business?

CORS

Historically, browsers adhere to a same-origin policy that blocks all AJAX requests that are not from the same domain for security reasons.

And that's exactly what's happening in our example. Since the simulator does not run in the same domain as the service, the browser blocks the request.

This is where CORS (Cross-Origin Resource Sharing) comes in. CORS is a W3C specification that allows certain cross domain access based on some configuration in HTTP headers.

CORS is primarily driven by the Access-Control-Allow-Origin header, which is used to provide a whitelist of origins that should be allowed to perform requests. For instance, if you want to allow all origins, you can use a Access-Control-Allow-Origin header set to *.

Therefore, we can add the following line to our Java Servlet to include this header in the response.

response.addHeader("Access-Control-Allow-Origin", "*");

After this change, our service call now works from the simulator. Although this works, there are some implications to adding this header. While our mobile app can now use the service, so can all other domains. Worse, we cannot explicitly whitelist our app's Origin because hybrid apps do note have one! A header of Origin: null is literally sent, which cannot be whitelisted.

What are we to do?

Luckily, PhoneGap/Cordova provides an elegant solution to this.

Accessing Services In Hybrid Apps

One of the great things about using PhoneGap/Cordova is that it takes care of accessing cross domain requests for you automatically. You just make the call and it takes care of the rest.

The only thing you need to do is configure which domains your application accesses in your project's config.xml files. If you've never done this before, my colleague Jim Cowart has an excellent guide that walks you through this.

So if PhoneGap/Cordova takes care of cross domain access, why are we receiving a CORS error in Mist?

As it turns out, this is one of the unavoidable consequences of using a web-based simulator rather than a desktop application or real device. In a simulator you are not running in a true PhoneGap/Cordova application environment.

Therefore to continue with this example you'll need to use Icenium Graphite, Icenium's Visual Studio Extensions, or on a real device using a true iOS or Android build.

If you do so, you'll see that we can now access the service safely from our hybrid application. Now that we have our service working, our final step is to do something with the data returned.

Handling The Results

Recall that this bank wanted to allow users that have a credit score of at least 500 in their app. How do we enforce this?

Let's return to our AJAX call, shown below:

$.post( "https://example.com/Bank/CreditScore", {
    firstName: this.firstName.value,
    lastName: this.lastName.value
});

Let's add a function to run after this call completes.

$.post( "https://example.com/Bank/CreditScore", {
    firstName: this.firstName.value,
    lastName: this.lastName.value
}).then(function( data ) {
    if ( data.score >= 500 ) {
        alert( "Congratulations, you are eligible!" );
    } else {
        alert( "Sorry, but you are not eligible." );
    }
});

Since we setup our service to return JSON, jQuery automatically turned the data into a JavaScript object we can use in a then() handler. We access this object's score property to check the user whether they are eligible or not, and show them an appropriate message. The image below shows each potential outcome.

Alert for an eligible user Alert for an ineligible user

In a real application you would want to do something more than a simple alert, but this example shows that the data from our service is available. This gives you the power to implement whatever UI you need on top of it.

Wrapping Up

And that's all there is to it. Just because your business logic is tied up in legacy backend services, it doesn't mean you can't leverage them from PhoneGap/Cordova applications.

In this article we took an existing backend Java service, exposed it over HTTP as a JSON API, and accessed it with a normal AJAX call from our hybrid application. While this example used a Java based backend service, the same process applies to any backend system.


TJ VanToll
About the Author

TJ VanToll

TJ VanToll is a frontend developer, author, and a former principal developer advocate for Progress.

Comments

Comments are disabled in preview mode.