Hybrid Mobile Apps: Using the Mobile Cloud Service JavaScript SDK with Oracle JET

Introduction

Oracle’s Mobile Cloud Service has a Javascript SDK that makes connecting your hybrid mobile app to your mobile backend service a breeze.  The Javascript SDK for MCS comes in two flavors, one for web applications and one for Cordova applications. The MCS Javascript SDK for Cordova has a few more capabilities than the web version, such as methods for registering a device and notifications.  However, for the most part the two SDKs are quite similar. For creating hybrid mobile apps, choose the Cordova SDK.

To download the Javascript SDKs for MCS, login to your MCS instance and click on the “Get Started” page. This page has SDK downloads for native apps in Android, iOS, and MCS Javascript SDKs. You can download the SDK with a starter app or choose to download the SDK alone and add it to an existing project. For the example in this post, I downloaded the SDK by itself and added it to a project created using Oracle JET (Javascript Extension Toolkit). To get started with Oracle JET, follow the Get Started link on the JET home page.

The steps below include one way to connect the hybrid app to Mobile Cloud Service using the MCS JavaScript SDK. I will cover making calls to MCS for authentication and uploading a picture taken on the device to the MCS Storage repository.

NOTE: This example uses the camera plugin of Cordova. To test this on iOS the sample app will have to be run on an actual iOS device, since the iOS simulator does not have a camera. For Android, the emulator does have a camera, so on Android either a device or emulator will work.

Main Article

To get started, use the handy Oracle JET generator to stamp out a mobile app template. The generator can be installed used npm. Using Yeoman, the app template can be created for whatever platform you wish to use. The steps in this post will focus on Android primarily, but also work with iOS hybrid apps.

Install JET generator

To generate a hybrid mobile starter application, the Yeoman generator for Oracle JET must be installed. Use npm to install “generator-oraclejet”. Again, note that on Mac you may need to use sudo.

npm install -g generator-oraclejet

To verify the generator was installed, run the following command:

npm list -g generator-oraclejet

Scaffold a Mobile Application

Using Yeoman, the JET generator can scaffold three different types of starter applications. This example will use the “navBar” template. To see screens of the options, follow this link.

Open a command prompt and create a directory where you want your mobile application to reside. Change directory to that new folder and run the Yeoman command to create an Oracle JET application. The command below creates a hybrid app named “JETMobileDemo” using the “navBar” template. Note that if on Windows, the platforms option cannot include iOS. On Mac you can use both iOS and android.

yo oraclejet:hybrid --appName=JETMobileDemo --template=navBar --platforms=android

Once this command completes, the directory that you are in will have a JET mobile application created. The output should show this at the end:

  Done, without errors.
  Oracle JET: Your app is ready! Change to your new app directory and try grunt build and serve…

See this link to understand the folder structure that is created from the template. Below are the descriptions of the project folders from the documentation. Note that the “src” directory is where the app pages are defined and edited. At build time, the src files are copied to the hybrid directory. This is important to understand so you avoid developing in the hybrid directory which gets overwritten by the grunt build task (or more specifically the “grunt copy” task).

Build and Serve

If you have installed the Android or iOS tooling, then the template app is ready to be built and run on a device or emulator. To build the app, grunt is used. Set the platform as needed.

grunt build:dev --platform=android

Run the app using “grunt serve”. For more details on the grunt serve command, see the JET documentation on grunt commands. The command below runs the app on an Android device attached to your machine.

grunt serve --platform=android --destination=device

 

On the device or emulator, the starter template should look something like this. If running on iOS, the navigation bar will be on the bottom of the page instead of the top. The navbar can be styled to be on top or bottom for all devices if needed, but the default is top for Android, bottom for iOS. Hint: if you want the header on top for iOS as well, use the JET style “oj-hybrid-applayout-navbar-fixed-top” on the navigation div in index.html. For information on the built-in JET styles see this link.

 

scaffold

 

Open the JET Project for Editing

A grunt command can be used to start a local web server to run the project.

grunt serve --platform=android --web=true

For development, use the IDE or editor of your choice.
NOTE: During development of JET hybrid apps, edit the files under the “src” directory, not the “hybrid/www”. When grunt serve is run on the project, changes made in the src directory will be automatically copied over to the hybrid/www directory (this is part of the live reload feature). When grunt build executes, the files from the src directory are copied over to the hybrid directory. Note that if you don’t want to run a full build but just want to copy the files over from src to hybrid/www, run this command, which will delete everything under the hybrid/www and then copy the src files over again:

grunt clean copy

 

Main.js and AppController.js

JET uses Require.js for loading modules. In the index.html file, only two script tags are needed to run in the browser, one script tag for Require.js and one for main.js. The main.js file defines the Require configuration as well as the top level view model, named MainViewModel, which initializes the single-page application.

<script type="text/javascript" src="js/libs/require/require.js"></script>
<script type="text/javascript" src="js/main.js"></script>

This can be combined into a single line, if preferred. Require.js has a data-main attribute that can be used to reference main.js on the same line as Require.js.

<script type="text/javascript" data-main="js/main" src="js/libs/require/require.js"></script>

The MainViewModel also initializes the AppController.js view model object. The AppController defines the pages for the router and creates the navigation entries. Finally, the router is synchronized and then the bindings are applied to the body of the index.html file.

What about Cordova.js?

Cordova.js is referenced in the index.html file, but only in the hybrid/www directory. This script tag is added by the build process to the index.html file in the hybrid/www/index.html file. When the “grunt build:dev” is executed, watch for a step in the output that reads “includeCordovaJs”. This step insert the important cordova.js script tag into index.html.

<script src="cordova.js"></script>

The cordova.js is needed when running on a device or emulator. When running in the browser, a 404 error will occur because of the script tag for including cordova.js. Do not be concerned with the 404 issue on cordova.js when testing in the browser. But do be concerned if you see the same error on the device or emulator. The cordova.js file should be available when running on a device or emulator because the build process parks the cordova.js file in the platform directory. For Android the location is:

<project>\hybrid\platforms\android\platform_www\cordova.js

Because cordova.js is not available when running in the browser, testing any usage of Cordova plugins can only be done on a device or emulator/simulator. However, with the live reload capabilities using “grunt serve”, this makes it a less time consuming process to update the app on the device when making changes.

Connect to MCS using Javascript SDK

Ok, now let’s get to the MCS Javascript SDK! We’ll get started by creating a simple login page in the app to allow for user/password login and logout using the SDK. As a bonus we will use some JET components ojLabel, ojInputText, and ojInputPassword will be used with Knockout observables to set the username and password. If you haven’t checked out the plethora of JET UI components, run – don’t walk – to the JET cookbook page. This sample only uses very basic input components, but the JET UI components are extensive and powerful. See the Data Visualization Components for proof.

A JavaScript SDK is available for hybrid apps to connect to Oracle Mobile Cloud Service. This library is not available via bower, so this will be manually copied into the project.

At the end of this section the intial page of the app should look like this image.

loginpage

Get the SDK library

Download the MCS JavaScript SDK from your “Get Started” page in Mobile Cloud Service. From that page, links are provided to the different SDKs available in MCS.

Unzip the file to a temp directory and locate the mcs.js and mcs-min.js files.

 

Add SDK to Project and Require.js configuration

In your app project, under “src/js” folder create a new folder called “mcs”. Copy the mcs.js into this folder (or you can use the minified mcs-min.js file for these steps, if you prefer).

Update Require.js Configuration to include the MCS SDK

In the main.js file for the app, add a reference in the Require config file for the MCS JavaScript SDK. The mcs.js or minified mcs-min.js can be referenced. The configuration below use the non-minified version. One line is added below:

'mcs': 'mcs/mcs'

 

Config portion of main.js

requirejs.config(
{
  baseUrl: 'js',
  
  // Path mappings for the logical module names
  paths:
  //injector:mainReleasePaths
  {
    'knockout': 'libs/knockout/knockout-3.4.0.debug',
    'jquery': 'libs/jquery/jquery-2.1.3',
    'jqueryui-amd': 'libs/jquery/jqueryui-amd-1.11.4',
    'promise': 'libs/es6-promise/promise-1.0.0',
    'hammerjs': 'libs/hammer/hammer-2.0.4',
    'ojdnd': 'libs/dnd-polyfill/dnd-polyfill-1.0.0',
    'ojs': 'libs/oj/v2.0.0/debug',
    'ojL10n': 'libs/oj/v2.0.0/ojL10n',
    'ojtranslations': 'libs/oj/v2.0.0/resources',
    'text': 'libs/require/text',
    'signals': 'libs/js-signals/signals',
    'mcs': 'mcs/mcs'
  }
  //endinjector
  ,
  // Shim configurations for modules that do not expose AMD
  shim:
  {
    'jquery':
    {
      exports: ['jQuery', '$']
    }
  }
}
);

 

Get MCS URL and Keys

Login to MCS service console and go to the mobile backend that the app will use. On the Settings page, you can obtain the keys needed to wiring your mobile app to the MCS backend.  Click the “Show” links to see the URLs and keys for the mobile backend. These will be used to configure the MCS connection object in JavaScript.

settings

Create the view model

To interact with MCS from the app, an interface to MCS needs to be created. Since the MCS JavaScript SDK is already available to the app, a new JavaScript file can be created to call the MCS methods that the application needs to use.
Like the “mcs” folder that was created in the previous step, add another folder called “mbe”, an abbreviation for “Mobile Backend”. Create a JavaScript file called mbe.js. This should be at the same level as the mcs folder previously created.

 

Insert the code below into the file. Change the keys to use your MCS backend keys. This file is doing several things for configuring the application connection to the Mobile Backend in MCS:

 

  • The configuration is defined for a backend called “JETSample”. 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.
  • The “mcs” reference is included in the define array of dependencies. This reference is available because the Requires.js configuration in main.js was already updated in an earlier step.
  • The mcs_config object contains the URL and keys for accessing MCS. This object is defined in the official documentation for MCS. The basicAuth and OAuth portions of the object are defined, but only basicAuth is used in the demo.
  • The init() method in the file declares and initializes the MCS backend object, setting the authentication type to basic auth. A user named jetuser is defined in this particular MCS backend.
  • The methods defined in this file are for login and logout of the backend. A method for anonymous authentication is included but will not be used. The authenticate and logout methods will be used for a demo on the dashboard page to work in concert with JET components for a basic login page.
  • This file also contains methods for adding to a collection in MCS, which we will use later when interacting with the Cordova camera plugin. This example hardcodes a Storage collection name, “JETCollection”, that is assumed to be defined on the MCS backend.

mbe.js

define(['jquery', 'mcs'], function ($) {
    //define MCS mobile backend connection details
    var mcs_config = {
        "logLevel": mcs.logLevelInfo,
        "mobileBackends": {
            "JETSample": {
                "default": true,
                "baseUrl": "https://mobileportalsetrial-yourdomain.mobileenv.us2.oraclecloud.com:443",
                "applicationKey": "0fc655f4-0000-4876-0000-0000000000000",
                "authorization": {
                    "basicAuth": {
                        "backendId": "b00000cf-0000-4cda-a3e0-000000000000",
                        "anonymousToken": "redacted"
                    },
                    "oAuth": {
                        "clientId": "00000000-2c24-4667-b80e-000000000000000",
                        "clientSecret": "mysecretkey",
                        "tokenEndpoint": "https://mobileportalsetrial-yourdomain.mobileenv.us2.oraclecloud.com/oam/oauth2/tokens"
                    }
                }
            }
        }
    };

    function MobileBackend() {
        var self = this;
        self.mobileBackend;
        //Always using the same collection in this example, called JETCollection. Can be dynamic if using multiple collections, but for example using one collection.
        var COLLECTION_NAME = "JETCollection";
        function init() {
            mcs.MobileBackendManager.setConfig(mcs_config);
            //MCS backend name for example is JETSample. 
            self.mobileBackend = mcs.MobileBackendManager.getMobileBackend('JETSample');
            self.mobileBackend.setAuthenticationType("basicAuth");            
        }

        //Handles the success and failure callbacks defined here
        //Not using anonymous login for this example but including here. 
        self.authAnonymous = function () {
            console.log("Authenticating anonymously");
            self.mobileBackend.Authorization.authenticateAnonymous(
                    function (response, data) {                        
                        console.log("Success authenticating against mobile backend");
                    },
                    function (statusCode, data) {
                        console.log("Failure authenticating against mobile backend");
                    }
            );
        };

        //This handles success and failure callbacks using parameters (unlike the authAnonymous example)
        self.authenticate = function (username, password, successCallback, failureCallback) {
            self.mobileBackend.Authorization.authenticate(username, password, successCallback, failureCallback);
        };

        //this handles success and failure callbacks using parameters
        self.logout = function (successCallback, failureCallback) {
            self.mobileBackend.Authorization.logout();
        };

        self.isAuthorized = function () {
            return self.mobileBackend.Authorization.isAuthorized;
        };
       
        self.uploadFile = function (filename, payload, mimetype, callback) {            
            self.getCollection().then(success);                        
            
            function success(collection) {                
                //create new Storage object and set its name and payload
                var obj = new mcs.StorageObject(collection);
                obj.setDisplayName(filename);
                obj.loadPayload(payload, mimetype);                
                return self.postObject(collection, obj).then(function (object) {                                        
                    callback(object);
                });
            }
        }
        
        //getCollection taken from official documentation example at site https://docs.oracle.com/cloud/latest/mobilecs_gs/MCSUA/GUID-7DF6C234-8DFE-4143-B138-FA4EB1EC9958.htm#MCSUA-GUID-7A62C080-C2C4-4014-9590-382152E33B24
        //modified to use JQuery deferred instead of $q as shown in documentaion
        self.getCollection = function () {
            var deferred = $.Deferred();

            //return a storage collection with the name assigned to the collection_id variable.
            self.mobileBackend.Storage.getCollection(COLLECTION_NAME, self.mobileBackend.Authorization.authorizedUserName, onGetCollectionSuccess, onGetCollectionFailed);

            return deferred.promise();

            function onGetCollectionSuccess(status, collection) {
                console.log("Collection id: " + collection.id + ", description: " + collection.description);
                deferred.resolve(collection);
            }

            function onGetCollectionFailed(statusCode, headers, data) {
                console.log(mcs.logLevelInfo, "Failed to download storage collection: " + statusCode);
                deferred.reject(statusCode);
            }
        };

        //postObject taken from official documentation example at site https://docs.oracle.com/cloud/latest/mobilecs_gs/MCSUA/GUID-7DF6C234-8DFE-4143-B138-FA4EB1EC9958.htm#MCSUA-GUID-7A62C080-C2C4-4014-9590-382152E33B24
        //modified to use JQuery deferred instead of $q as shown in documentaion
        self.postObject = function (collection, obj) {
            var deferred = $.Deferred();

            //post an object to the collection
            collection.postObject(obj, onPostObjectSuccess, onPostObjectFailed);
            
            return deferred.promise();

            function onPostObjectSuccess(status, object) {            
                console.log("Posted storage object, id: " + object.id);
                deferred.resolve(object.id);
            }

            function onPostObjectFailed(statusCode, headers, data) {
                console.log("Failed to post storage object: " + statusCode);
                deferred.reject(statusCode);
            }
        };

        init();
    }

    return new MobileBackend();
});

 

Add JET Components to the dashboard page

For this example the dashboard page will have a login form added to it. The JET cookbook can be used for understanding how to add form elements and buttons into a view and viewModel. Knockout knowledge is important when using JET components. For beginning Knockout users the http://learn.knockoutjs.com/ tutorial provides a good hands-on lab.

Replace the scaffolded app’s dashboard.html page with the following html. What this html does is add a username and password entry field using JET components. The login form is only visible when the user is not authorized. The logout button is only visible when the user is authorized. The Knockout visible binding is used to hide and show elements based on the login status.

The username and password entry field values are bound to Knockout observables defined in dashboard.js. JET bindings for form inputs use the “value” attribute. The login button is bound on the click event to a function in the view model called login. Likewise, the logout button is bound on a click event to a logout function in the view model.

Additional elements that will be used are commented out for the time being.

dashboard.html

<div class="oj-hybrid-padding">
  <h3>Dashboard Content Area</h3>
  <div>
    <div data-bind="visible: !isLoggedIn()" class="oj-flex oj-sm-flex-direction-column oj-md-flex-direction-column"> 
      <div class="oj-flex-item">
        <label for="text-input">Username</label>
        <input id="text-input" type="text" data-bind="ojComponent: {component: 'ojInputText', value: username}"/>
      </div>
      <div class="oj-flex-item">
        <label for="password">Password</label>
        <input type="password" id="password" data-bind="ojComponent: {component: 'ojInputPassword', value: password}"/>
      </div>
      <div class="oj-flex-item">
        <input id="inputButton" type="button" data-bind="click: login, ojComponent: {component: 'ojButton', label: 'Login', chroming: 'full'}"/>
      </div>
    </div> 
  </div>
  <div data-bind="visible: isLoggedIn">
    <input id="inputButton" type="button" data-bind="click: logout, ojComponent: {component: 'ojButton', label: 'Logout', chroming: 'full'}"/> 

    <!--<input id="inputButton" type="button" data-bind="click: takePicture, ojComponent: {component: 'ojButton', label: 'Take a picture', chroming: 'none'}"/> 
    <br>
    <img id="cameraImage" src="" height="250" width="100%" data-bind="attr: { src: picture }">

    <input id="inputButton" type="button" data-bind="click: uploadPicture, ojComponent: {component: 'ojButton', disabled: (picture() === null), label: 'Upload to MCS', chroming: 'half'}"/> -->
  </div> 
</div>

 

Edit the dashboard view model

The dashboard.html page changes require related changes to the view model. Replace the dashboard.js file with the JavaScript below.

The first change is to include the proper dependencies in the define method (first line). The Mobile Backend helper class needs to be added here as a reference in order to makes calls to the MCS API that we have created in mbe.js.

Additional references are needed for JET components used on the page, in this case buttons and form inputs. Three Knockout observables are defined, one for the login status, one for the username, and one for the password. These are bound in the html to the JET input components. The login status is used to hide or show the login form or logout button based on the status (true or false). The username and password are initialized to working values so no typing is required in testing the login. An observable for picture is also defined for when we later add the Cordova camera plugin.

Notice the methods “authenticate” and “logout” call the Mobile Backend helper object’s methods to handle calls to MCS. In the case of the login method, callbacks are passed to the Mobile Backend helper so that upon success or failure the dashboard view model can react.

 

dashboard.js

define(['ojs/ojcore', 'knockout', 'jquery', 'mbe/mbe', 'ojs/ojknockout', 'ojs/ojselectcombobox', 'ojs/ojbutton', 'ojs/ojinputtext'],
        function (oj, ko, $, mbe) {
            function DashboardViewModel() {
                var self = this;
                self.isLoggedIn = ko.observable(false);

                //set user to a default value to quicken your login testing
                self.username = ko.observable("jetuser");

                self.picture = ko.observable(null);

                //pass callbacks to the login to trigger page behavior on success or failure
                self.login = function () {
                    mbe.authenticate(self.username(), self.password(), self.loginSuccess, self.loginFailure);
                };

                //pass callbacks to the login to trigger page behavior on success or failure
                self.logout = function () {
                    mbe.logout();
                    self.isLoggedIn(false);
                };

                self.loginSuccess = function (response) {
                    console.log(response);                    
                    self.isLoggedIn(true);
                };

                self.loginFailure = function (statusCode) {
                    self.isLoggedIn(false);
                    alert("Login failed! " + statusCode);
                };

            }
            return new DashboardViewModel;
        }
);

 

Test the login and logout

You may have to run “grunt clean copy” once to get all the updated files in place, and then run grunt serve. If a 401 error occurs on login, make sure that you are not hitting the CORS error (a Chrome workaround is defined in the next section).

After login, the Knockout visible binding on the isLoggedIn observable then hides the form and shows the logout button.

loginpagelogout

Cross Origin Error?

Calling backend services on MCS from your app while testing in a browser may cause a CORS error. This will show up in the browser console as a “HTTP Allow Access Origin Header” error. An example error showing this is:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://mymcshost.mobileenv.us2.oraclecloud.com/mobile/platform/users/login. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).

 

To avoid this error while developing and testing in the browser, refer to Appendix B of the MCS guide that covers Oracle Mobile Cloud Service Environment Policies.  Specifically, the Security_AllowOrigin policy can be changed from the default value of “disallow” to “allow” in order to workaround this error in your development environment.

Content Security Policy Header in index.html

The meta tag in Cordova apps is an important change in your hybrid app’s index.html file. If the tag is not set, warnings will appear in the browser console while the app is running on Android. The details of this meta tag can be found at the Cordova whitelist-plugin github page.

https://github.com/apache/cordova-plugin-whitelist

A default declaration for your app can be the following. Note that the “gap:” entry is needed for iOS when the Cordova Splash plugin is in use. Put this into the index.html file in the head. Specific needs may require alterations to this, and for production consider changing this to point only to the MCS host (and specific hosts that your app needs to communicate with).

<!--Allows connection to any host. Consider changing this to only allow calls to your MCS host: connect-src 'self' http://mobilecloudservicehost -->
<meta http-equiv="Content-Security-Policy" content="default-src * data: gap:; script-src 'self' 'unsafe-inline' 'unsafe-eval' 127.0.0.1:* localhost:*; style-src 'self' 'unsafe-inline'; media-src *"/>

Working with Cordova Plugins

This set of steps will briefly cover how to add Cordova plugins to the project, but for full details on Cordova plugins see the Apache Cordova website. The sample in this demo is to take a picture using the device and then upload it to a MCS Collection as a jpg file.
Cordova has a set of core plugins that can be added to the app. To add a plugin, in your project, change directory on the command line into the “hybrid” folder. This is where the config.xml file is located for Cordova, which defines the Cordova plugins, platforms, and hooks.
To add a plugin use the “cordova plugin add” command. To persist the change to the config.xml file, use the –save option on the command. Add the camera and file plugins using these commands.

cordova plugin add cordova-plugin-camera --save
cordova plugin add cordova-plugin-file --save

Once the plugin is installed, the config.xml will have two new lines added to it.

 

<plugin name="cordova-plugin-camera" spec="~2.1.1" />
<plugin name="cordova-plugin-file" spec="~4.1.1" />

These lines should be near the end of the config.xml file. For questions about the contents of the config.xml, Apache has a reference guide for the config.xml file that describes all of the entries in detail.

Add a Mobile Backend Method for Uploading to a MCS Collection

In the Mobile Backend helper file that uses the MCS JavaScript SDK, the methods required for uploading files to a collection were already added when you created mbe.js. In MCS, create a collection named “JETCollection” (or whatever you like). Set the collection name in the mbe.js file to a variable accessible by the view model methods. This was already done in the mbe.js sample code listed earlier in this post.

var COLLECTION_NAME = "JETCollection";

The getCollection and postObject are documented using the $q async/promise library at this MCS documentation link. However, since JET already has JQuery available, the $.Deferred object can be used for handling the async calls to MCS for getting a collection and posting a file to a storage collection. In the callbacks the deferred object can be resolved or rejected.

From the dashboard, the uploadFile method can be used by passing a filename, file blob (arrayBuffer), the MIME type of the file (image/jpeg), and lastly a callback to run on completion so that the user can be notified that the upload is done.
The uploadFile performs two tasks: first, get the collection from MCS, and second, POST the file object to the storage collection.

 

Using the Plugins in the Dashboard

The capabilities of the plugin “cordova-plugin-camera” are on the Apache documentation site where the core Cordova plugins are documented. For this example, we will add a button to the dashboard page that uses the getPicture method of the Camera plugin.
First, edit the view and view model. Add two buttons and an image tag to dashboard.html. (These were commented out in the earlier dashboard.html text that was used, so you can now uncomment these lines.) This html is placed after the “Logout” button but inside the same div so that th picture area is only visible when the user is logged in.

For illustration, this JET button shows the chroming option of “none”. This styles the button differently from the login and logout buttons previously created. A third option for chroming is “half”, which will give another look to the button. The “Upload to MCS” button uses the “half” option. Another JET component option on the button is to make it disabled based on certain criteria in the view model. If the picture variable in the view model is null, the disabled option is turned on. This only allows the user to click the button when there is a picture already taken.

 

Add tags on dashboard.html

<input id="inputButton" type="button" data-bind="click: takePicture, ojComponent: {component: 'ojButton', label: 'Take a picture', chroming: 'none'}"/> 
<br>
<img id="cameraImage" src="" height="250" width="100%" data-bind="attr: { src: picture }"> 
<input id="inputButton" type="button" data-bind="click: uploadPicture, ojComponent: {component: 'ojButton', disabled: (picture() === null), label: 'Upload to MCS', chroming: 'half'}"/>

 

The buttons rely on methods named “takePicture” and “uploadPicture” in the view model, so add these functions in the view model dashboard.js. The takePicture method will use the “navigator.camera” object which is available due to the Cordova plugin. Add a check for the existence of the camera object, because running in the browser this object will not exist. To take a picture, the camera plugin has a method called “getPicture”. The parameters are success and failure callback methods. A third parameter for camera options is available. The options parameter allows control over image quality, encoding, height, width, and more. Add the code in the block below to the dashboard.js.

Add this block to dashboard.js

//use the Cordova camera plugin to take a picture
                self.takePicture = function () {
                    if (navigator.camera && typeof navigator.camera !== "undefined") {       
                        //sample camera options, using defaults here but for illustration....
                        //Note that the destinationType can be a DATA_URL but cordova plugin warns of memory usage on that.
                        var cameraOptions = {
                            quality: 50,
                            destinationType: Camera.DestinationType.FILE_URL,
                            sourceType: Camera.PictureSourceType.CAMERA,
                            allowEdit: false,
                            encodingType: Camera.EncodingType.JPEG,                            
                            saveToPhotoAlbum: false,
                            correctOrientation: true
                        };
                        //use camera pluging method to take a picture, use callbacks for handling result
                        navigator.camera.getPicture(cameraSuccess, cameraError, cameraOptions);
                    } else {
                        //running on web, the navigator.camera object will not be available
                        console.log("The navigator.camera object is not available.")
                    }
                };

                function cameraSuccess(imageData) {
                    //returns a file path such as: file:///storage/emulated/0/Android/data/org.oraclejet.mcsexample/cache/1459277993352.jpg
                    //set observable to the path and img tag's src attributein view will be updated.
                    self.picture(imageData);

                }

                function cameraError(error) {
                    console.log(error);
                }

                self.uploadPicture = function () {
                    //load file as blob, then once loaded add to MCS collection. Use callback for when complete.                    
                    getBlobFromFile(self.picture())
                            .then(function (arrayBuffer) {
                                mbe.uploadFile("picture.jpg", arrayBuffer, "image/jpeg", self.pictureUploadSuccess);                                
                            });
                };

                self.pictureUploadSuccess = function (objectid) {
                    console.log(objectid);
                    //showing alert to notify user that upload completed, MCS object id is shown.
                    alert("Picture uploaded to MCS, id is: " + objectid);
                };

                function getBlobFromFile(filepath) {
                    //Use Cordova file plugin API to get the file:
                    //On success, load the file as array buffer
                    var deferred = $.Deferred();

                    if (window.resolveLocalFileSystemURL && typeof window.resolveLocalFileSystemURL !== "undefined") {
                        window.resolveLocalFileSystemURL(filepath,
                                function (fileEntry) {
                                    //on success use fileEntry handle to read the file, then run callback when onloadend event occurs 
                                    fileEntry.file(function (file) {
                                        var reader = new FileReader();
                                        reader.onloadend = function (e) {                                            
                                            deferred.resolve(this.result);
                                        };
                                        reader.onerror = function (e) {                                            
                                            deferred.reject(e);
                                        };
                                        reader.readAsArrayBuffer(file);
                                    });
                                },
                                function (error) {
                                    console.log("Error getting file. Message: ", error);
                                    deferred.reject(error);                                    
                                }
                        );
                    } else {
                        var msg = "The object window.resolveLocalFileSystemURL does not exist. Cannot get file."
                        console.log(msg);
                        //reject immediately
                        deferred.reject(msg);
                    }
                    return deferred.promise();
                }

Build and Serve to Device

To build and run on an emulator or device, be sure that you have followed the guides for your mobile operating system.

Android: Follow the steps on Cordova’s site for Android setup.
https://cordova.apache.org/docs/en/latest/guide/platforms/android/index.html

iOS: Follow the steps on Cordova’s site for iOS setup.
https://cordova.apache.org/docs/en/latest/guide/platforms/ios/index.html

For Android, if you have an emulator running or device plugged in via USB, you should be able to see them using the Android Debug Bridge (adb). Running adb devices will show all emulators and devices attached.

adb devices

Output:

List of devices attached
c143bx44 device

If your device or emulator doesn’t show up, check the documentation on the Android Debug Bridge page to see if you have setup the device or emulator properly. For devices that fail to appear in the list, USB debugging may not enabled on the device itself or a driver for your device isn’t installed on the machine (see the Google OEM drivers page).

 

Build and Serve for Android

The first step once you are ready to deploy is to build the .apk file for Android. This command is already familiar from when you first created the hybrid app and performed a build on the project. Keep in mind that this command should be run from the project root, where the Gruntfile.js is located. This does not run from the hybrid directory. Only cordova commands need to be run in the hybrid directory.
Note: These steps are covered in the official JET documentation.

grunt build:dev --platform=android

Once the .apk file is built, you can serve the app to the device or emulator. The following command installs the app onto the device.

grunt serve:dev --platform=android –destination=device

The serve command should show a successful build followed by the installation and launch of the .apk file on the device or emulator.

BUILD SUCCESSFUL
Total time: 8.941 secs
Built the following apk(s):
D:\jet\mcsexample\hybrid\platforms\android\build\outputs\apk\android-debug.apk
Using apk: D:\jet\mcsexample\hybrid\platforms\android\build\outputs\apk\android-debug.apk
Installing app on device...
Launching application...
LAUNCH SUCCESS
Done, without errors.

 

Take a Picture

Take a picture. Hopefully your picture will be more inspiring than the picture of my desk! The dashboard should show the image after a picture is taken. The image can then be uploaded to MCS by clicking the “Upload to MCS” link.

takepicture

 

If you are using the Android emulator, and the “Back Camera” is set to “Emulated”, the picture you can take is a green square on a background of checkered squares.

takepicture-emulator

 

Once the file is uploaded, a new “picture.jpg” will be visible in the Collection.

uploaded

Inspect in Chrome

To troubleshoot the app for Android, open Chrome and enter the address “chrome://inspect”. This should show a list of running applications on the device. In the image below, the “WebView in org.oraclejet.mcsexample” is the app that was served. Clicking the “inspect” link will bring up Chrome developer tools and allow for debugging the app while it runs on the device.

inspect

The inspect view allows the use of Chrome developer tools while the app runs on the device or emulator.

 inspect-dashboard

Conclusion

The MCS Javascript SDK provides the link between your Cordova based application and Oracle Mobile Cloud Service. Like this example using Oracle JET, this same SDK can be used with Ionic/Angular apps. The MCS Javascript SDK covers more than just authentication and file storage as well, including the ability to register the device, log analytics, and call custom APIs. The SDK can save time in developing hybrid apps since the interactions with the backend are already built for you, and all of the power of Oracle Mobile Cloud Service is at your command using a supported SDK.

 

Add Your Comment