Using Oracle Mobile Cloud Service with React JS

Introduction

Oracle’s Mobile Cloud Service (MCS) can be used with any client development tool. Whether it is a mobile framework or web based framework. To speed up development for the most popular development tools, MCS comes with a Software Development Kit (SDK) for native Android, iOS and Windows development, as well as a JavaScript SDK for hybrid JavaScript-based development. Oracle’s A-Team has gained experience with the MCS JavaScript SDK in combination with various JavaScript toolkits. In a previous post, we discussed how to use the SDK when building Oracle JET applications. In this post we will share code samples, tips and best practices when building a web application using React JS. We assume you have a basic knowledge of React JS and JQuery. Check out their websites for tutorials if you are new to these technologies.
In a different post we explain how to use the MCS JS SDK in a hybrid mobile application using Ionic and Angular.

Main Article

In this article, we will explain how to connect to your custom REST API’s defined in MCS and how to leverage the various MCS platform services like storage and analytics. Note that all platform services can be accessed through the Oracle MCS REST API.
This article explains how to connect to MCS using the JavaScript SDK for Mobile Cloud Service which abstracts authentication and platform API’s.

 

Downloading and Configuring the JavaScript SDK

The Javascript SDK for MCS comes in two flavours, one for JavaScript applications and one for Cordova applications. The MCS Cordova SDK is a super set of the MCS JS SDK as it provides a few more more capabilities that depend on Cordova, such as methods for registering a device for push notifications.  Since we are creating a web app, we choose the JavaScript SDK.

To download an SDK for MCS, login to your MCS instance and open the hamburger menu. Click on “Applications”. Click on the “SDK Download” link on the right hand side. This takes you to a page where you can select your target platform, and download the SDK.

mcsJS

After downloading the file, we unzip it and copy over the following files:

  • Copy mcs.js and mcs.min.js into a new mcs subfolder of your project
  • Copy the oracle_mobile_cloud_config.js.js file to the scripts folder.

We add the mcs.js file to the index.html file, and add it above the existing script tags that include the React libraries.

 

We will create a seperate JS class: McsService.js in the scripts folder that will contain all the MCS related code:

function McsService(){
  var mcs_config = {
    "logLevel": mcs.logLevelInfo,
    "mobileBackends": {
      "HR": {
        "default": true,
        "baseUrl": "https://mobileportalsetrial.yourdomain.mobileenv.us2.oraclecloud.com:443",
        "applicationKey": "a4a8af19-38f8-4306-9ac6-adcf7a53deff",
        "authorization": {
          "basicAuth": {
            "backendId": "e045cc30-a347-4f7d-a05f-4d285b6a9abb",
            "anonymousToken": "QVRFQU1ERVZfTUVTREVWMV9NT0JJTEVfQU5PTllNT1VTX0FQUElEOnByczcuYXduOXRlUmhp"
          }
        }
      }
    }
  };

  // initialize MCS mobile backend
  mcs.MobileBackendManager.setConfig(mcs_config);
  var mbe = mcs.MobileBackendManager.getMobileBackend('HR');
  mbe.setAuthenticationType("basicAuth");
}

The structure of the mcs_config variable can be copied from the oracle_mobile_cloud_service_config.js file. This file includes all the declarative SDK configuration settings. Since we are using basic authentication in our app, we left out the configurations required when using OAuth, Facebook or Single-Sign-On (SSO) authentication. The configuration is defined for a backend called HR. This name doesn’t have to match the name of the backend that is actually in MCS, but can if you wish to match it. A description of all possible configuration settings can be found in the chapter JavaScript Applications in the MCS Developer’s Guide.

You can find the values of the mcs_config settings baseUrl, backendId and anonymousToken on the overview of your mobile backend in MCS:

MBE-Settings

 

 

We could have done the SDK configuration directly in oracle_mobile_cloud_service.js as well, and add a reference to this file in index.html.The downside of that approach is that we “pollute” the global space with another global variable mcs_config.  In addition, we prefer to make the McsService self-contained including the required configuration settings. If you prefer to have the configuration in a separate file, then it is better to create a json file that holds the config object and read the content of this file into your mcs_config variable within the McsService.js file.
After you copied the mcs_config variable, you can remove file oracle_mobile_cloud_service.js again from your project.

 

Authenticating Against MCS

MCS basic authentication provides two ways to authenticate, a “named” authentication using a username and password and a so-called anonymous authentication which uses the anonymousToken that we specified in the mcs_config variable. Anonymous authentication might be convenient during initial development when you did not set up a user realm with your mobile backend yet, or when you want to use another authentication mechanism for your app that is unrelated to MCS.

We first add functions to our mcsService to support both ways of authentication and to be able to logout:

McsService.prototype.authenticate = function(username,password,success,failure) {
  mbe.Authorization.authenticate(username, password
    , function(statusCode,data) {success(data)}
    , failure);
};

McsService.prototype.authenticateAnonymous = function(success,failure) {
  mbe.Authorization.authenticateAnonymous(
    function (statusCode, data) {
      success(data)
    }
    , failure);
};

McsService.prototype.logout = function(success,failure) {
  mbe.Authorization.logout(
    function(statusCode,data) {success(data)}
    , failure);
};

Now we can create a login component in React.JS:

var LoginButton = React.createClass({
  doLogin: function(){
    mcsService.authenticate(this.state.username,this.state.password,function(data){
        console.log("Success");
    },
    function(status,data){
      console.log("Error authenticating: " + data);
    })
  },
  render:function(){
    return (
      <button role="button" onClick="doLogin">Login</button>
    )
  }
});

Also make sure you initialize the mcsService at the top of the app.js so the service is initialized only once:

var mcsService = new McsService();

Invoking the HR Custom API

For this demo, we have created a basic HR REST API to manage departments. The API looks like this:

Endpoint Method Description
/departments GET List of departments with id, name attributes
/departments POST Add new department
/department/{id} GET All department attributes and list of employees in department
/department/{id} PUT Update department
/department/{id} DELETE Delete department

 

To access our custom API endpoints through the SDK, we add the following function to our McsService object:

McsService.prototype.invokeCustomAPI = function(uri,method,payload,success,failure) {
  this.mbe.CustomCode.invokeCustomCodeJSONRequest(uri , method , payload
    , function(statusCode,data) {success(data)}
    , failure);
};

Next step is to bind an endpoint to a component:

Binding endpoints to components

If we want to create a component that lists all the departments, we want to bind the component to the corresponding endpoint.

We first define the component that will contain the data and render the global list:

var DepartmentBox = React.createClass({
  getInitialState:function(){
    return {data:[]}
  },
  componentDidMount:function(){
    mcsService.invokeCustomAPI(this.props.url,"GET",null,function(data){
      this.setState({data:data});
    }.bind(this))
  },
  render: function(){
    return (
      <div className="departmentBox">
        <h1>Departments</h1>
        <DepartmentList data={this.state.data}/>
        <DepartmentDetails/>
      </div>
    );
  }
});

The componentDidMount function is a method from React that is called once per component, when the component is attached to the DOM. This ensures us that the data will be fetched in an asynchronous way and at the earliest possible.

Once the data is retrieved, we assign it to the component state (line 7) which will trigger a re-render of the component.
Notice that the actual URL is not hard coded put provided in the properties of the component. We pass this on during the initial render:

ReactDOM.render(
  <DepartmentBox data={departments} url="hr/departments" />,
  document.getElementById("content")
);

This technique allows you to easily see what data will be presented in which components.
By making it dynamic and allowing to pass the URL to the data, you also increase the re-usability of the component.

The next component we need is the one that will actually display the department list:

var DepartmentList = React.createClass({
  render: function(){
    var depNodes = this.props.data.map(function(dep){
      return (
        <Department key={dep.id} dep={dep}/>
      )
    })
    return (
      <div className="departmentList">
        {depNodes}
      </div>
    );
  }
});

And the department component:

 

var Department = React.createClass({
  render:function(){
    return (
      <div className="department">
        <h4>{this.props.dep.name}</h4>
      </div>
    )
  }
});

Using the MCS Storage Service

MCS includes a file storage service where you can store or cache mobile application objects, such as text, JSON, or images. You can define a storage collection and then add files to such a collection. For our sample application, we store the employee images in an MCS collection named HR. The storage ID of each image includes a reference to the employee ID, so we can easily link each employee with his/her photo in the HR collection:

HRCollection

You can upload files to a storage collection using the MCS UI as shown above. When you use the MCS UI, the ID of the storage object is system-generated. This is inconvenient if you want to associate MCS storage objects with the data of your systems of record that you expose through MCS. Fortunately, if you use the PUT method of the storage REST API, you can add new files and determine the storage ID yourself. For example, you can use a CURL command to upload an image to a storage collection like this:

curl -i -X PUT  -u steven.king:AuThyRJL!  -H "Oracle-Mobile-Backend-ID:bcda8418-8c23-4d92-b656-9299d691e120" -H "Content-Type:image/png"  --data-binary @FayWood.png https://mobileportalsetrial1165yourdomain.mobileenv.us2.oraclecloud.com:443/mobile/platform/storage/collections/HR/objects/EmpImg119

To use the storage service in our app, we first add functions to read storage objects in our McsService:

McsService.prototype.getCollection = function(collectionName,success,failure) {
  this.mbe.Storage.getCollection(collectionName, null
    , function(collection) {success(collection)}
    , failure);
};

McsService.prototype.getStorageObjectFromCollection = function(collection,storageId,success,failure) {
  collection.getObject(storageId
    , function(storageObject) {success(storageObject)}
    , failure
    ,'blob');
};

McsService.prototype.getStorageObject = function(collectionName,storageId) {
  //  This is the officially documented way, but fires redundant REST call:
  //  return getCollection(collectionName).then( function (collection) {
  //      return getStorageObjectFromCollection(collection,storageId)
  //  })
  var collection = new mcs._StorageCollection({id:collectionName,userIsolated:false},null,this.mbe.Storage);
  return this.getStorageObjectFromCollection(collection,storageId);
};

The “official” way for accessing a storage object is through its collection. This can be done by calling the getCollection method on the Storage object of the SDK. This returns the collection object which holds the metadata of all the objects inside the collection. On this collection object we can then call methods like getObject, postObject and putObject.  In our app, we want to prevent this additional REST call since we are not interested in the collection as a whole. This is why in line 19 we programmatically instantiate the collection object without making a REST call. As indicated  by the underscore, the _StorageCollection constructor function was intended to be “private” but this use case has been identified as valid and it will be made public in the next version of the SDK.

To be able to show the images with the employees, we extend the code in the editDepartment controller as follows:

var Employee = React.createClass({
  render:function(){
    return (
      <h5>{this.props.firstName} {this.props.lastName}</h5>
      <img src={this.state.empImg}/>
    )
  }
});

Just as we did with the departments, we can use the componentDidMount on that component to load the employee image in an asynchronous way:

var Employee = React.createClass({
  getInitialState:function(){
    return {data:[]}
  },
  componentDidMount:function(){
     mcsService.getStorageObject("HR","EmpImg"+this.props.empId,function(data){
       var url = URL.createObjectURL(data);
      this.setState({empImg:url});
    }.bind(this))
  },
  render:function(){
    return (
      <h5>{this.props.firstName} {this.props.lastName}</h5>
      <img src={this.state.empImg}/>
    )
  }
});

Using MCS Analytics Service

The MCS Analytics platform service is a very powerful tool to get detailed insight in how your mobile app is used. From your mobile app you can send so-called system events like startSession and endSession to get insight in session duration, location, etc.  Even better, you can send custom events to get very specific information about how the app is used, for example which pages are accessed for how long, which data is viewed the most, etc.

To support the MCS analytics events, we add the following functions to our McsService:

McsService.prototype.logStartSessionEvent = function() {
  this.mbe.Analytics.startSession();
}

McsService.prototype.logEndSessionEvent = function() {
  this.mbe.Analytics.endSession();
}

McsService.prototype.logCustomEvent = function(eventName, properties) {
  var event = new mcs.AnalyticsEvent(eventName);
  event.properties = properties;
  this.mbe.Analytics.logEvent(event);
}

McsService.prototype.flushAnalyticsEvents = function() {
  this.mbe.Analytics.flush();
}

When you log an event, the event is not yet sent to the MCS server. You can batch up multiple events and then flush them to the server, which is more efficient because all events are then sent in one REST call. If you log a custom event and you didn’t log a startSession event before, the SDK will automatically create a startSession event first.

In our app, we are going to log a custom event when the user selects a department to display its details. Therefore we add an onClick event on the Department component:

var Department = React.createClass({
  clicked: function(){
    mcsService.logCustomEvent('ViewDepartment',{user:this.state.userName,department:this.props.dep.name});
    mcsService.flushAnalyticsEvents();
  },
  render:function(){
    return (
      <div className="department">
        <h4><a onClick={this.clicked}>{this.props.dep.name}</a></h4>
      </div>
    )
  }
});

The name of the event is ViewDepartment and we send the user name and department name as properties with the event. If you check the REST request payload that is sent to MCS, you can see how the SDK is easing your life, the required context object with device information, the startSession event and the custom event itself are all included in the payload:

AnalyticsPayloadIn the MCS user interface, we can navigate to the custom events analytics page, and get some nice graphs that represent our ViewDepartment event data:

AnaGraphs

Making Direct REST Calls to MCS

At the end of the day, every interaction between a client app using the MCS SDK and MCS results in a REST call being made. The MCS SDK provides a nice abstraction layer which makes your life easier and can save you a lot of time as we have seen with the payload required to send an MCS analytics event. However, there might be situations where you want to make a direct REST call to MCS, for example:

  • to call your custom API with some custom request headers
  • to get access to the raw response object returned by the REST call
  • to call a brand new REST API not yet supported by the JavaScript SDK, like the Locations API.

In such a case, the SDK can still help you by providing the base URL and the Authorization and oracle-mobile-backend-id HTTP headers. Here are two functions you can add to your mcsService to expose this data:

McsService.prototype.getHttpHeaders = function() {
  return this.mbe.getHttpHeaders();
}

McsService.prototype.getCustomApiUrl = function(customUri) {
  return this.mbe.getCustomCodeUrl(customUri);
}

Going mobile with React

So far, this artice has provided a way of using React.JS in a web based application however with some minor tweaks, we can easily build hybrid applications using Cordova and React.JS.

By using a framework like Reapp.io it is very easy to combine Cordova and React.

When using Cordova to build a hybrid application with React, you just have to make sure that you are using the Cordova based JavaScript SDK. This will give you even more functonality than the JavaScript SDK used in this article. It will also provide functionality for push notification and so on.

Conclusion

The MCS Javascript SDK provides an easy and fast way to connect to your custom API’s defined in MCS as well as the various MCS platform services. In this article we provided tips and guidelines for using the MCS authorization, storage and analytics services in the context of a React app. If you are using another JavaScript toolkit, most of the guidelines and code samples (with some minor tweaks) should still be useful. If you are using Oracle JET, we provide some Oracle JET specific guidelines.

Keep an eye on Oracle A-Team Chronicles home page, or if you are specifically interested in articles around Oracle MCS, there is also a list of MCS-related articles.

 

 

Add Your Comment