Chapter 4. Developing Client Apps

4.1. Developing An Ionic App Using RHMAP

Overview

This guide will introduce you to using the RHMAP Javascript SDK within a HTML5 Ionic Cordova App.

This guide includes the steps necessary to create a new Ionic Hello World Project and highlights the code necessary to interact with a Cloud Code App.

Requirements

  • RHMAP Account
  • Knowledge of HTML, JavaScript, Ionic and Node.js
  • GitHub User Account
  • FHC installed.

The Ionic Documentation contains all of the information needed to start developing Ionic Apps.

4.1.1. Sample Project Overview

The example project is a simple project containing one Client App and one Cloud App.

4.1.1.1. Client App

The Client App is a simple Ionic App that includes the RHMAP Javascript SDK. The Client App asks the user to enter their name into a text box and click a Say Hello From The Cloud button. The Client App then uses the $fh.cloud function to send the text entered to the Cloud Code App.

4.1.1.2. Cloud Code App

The Cloud Code App exposes a hello endpoint to recieve a request from the Client App, change the text sent to add Hello and return the response to the client.

4.1.2. Create A New Ionic Hello World Project

Use the following steps to create a new Ionic Hello World Project.

  1. Log into the Studio.
  2. Select the Projects tab located at top-left of the screen.
  3. Select the + New Project button located at the top of the screen. You will then see a list of Project Templates.
  4. Select the Hello World Project Template and give the new project a name.
  5. Click the Create button. The new project will now be created.
  6. When the project has been created, select the Finish button.

You now have a new Project containing an Ionic Client App and a Cloud Code App that it will communicate with.

To Preview the app:

  1. Select the Apps, Cloud Apps & Services tab.
  2. Select the Client App under the Apps section. This will take you to the Details Screen.
  3. The App Preview contains a working version of the Ionic Client App that will communicate with the Cloud Code App.

4.1.3. Build The Client App For An Android Device

Now that you have tested your app and are happy that it is working correctly, you can now build the Client App using the RHMAP Studio.

Use the following steps to build the Client App for an Android device:

  1. From the Client App interface selected the Build tab.
  2. Select Android, the master branch, a Debug build type, the Dev Cloud Code App and then click the Build button. An Android build of the Client App will now be built.
  3. When the Android build has completed, you will be presented with a QR Code, simply open a QR Code Scanner app on your Android device and install the build. Alternatively, type the short URL into your phone’s browser.
Note

You can build Android debug binaries without any certificates but you will need the requisite credentials to build any type of iOS Phone binaries.

Note

The branch selector allows you to select which branch of the Client App you wish to build. In this case, the default master branch is the correct branch.

Congratulations. You have just created an Ionic HTML5 Cordova App using RHMAP.

4.1.4. Development Overview

This section will highlight the code necessary for the example solution to work correctly.

4.1.4.1. Cloud Code App

First, let’s consider the Cloud Code App. In this example, we want the Cloud Code App to recieve a request from the Client App, change the hello parameter and respond to the Client App using a JSON object containing the following parameters:

{
    msg: "Hello <<hello parameter sent by the client app>>"
}

To implement this functionality in the Cloud Code App:

  1. In the application.js file, a new /hello route is added which requires a hello.js file located in the lib directory.
  2. The hello.js file creates two routes. Both routes perform the same operation of changing the hello parameter.

    • A GET request where the hello parameter is appended as a query string.
    • A POST request where the hello parameter is sent in the body of a POST request.
Note

This Cloud Code App is completely independent of the Ionic Client App. The Cloud Code App can be shared between any number of Client Apps within a project.

4.1.4.2. Ionic Client App

Having created the /hello endpoint in the Cloud Code App, we now proceed to examine the functionality added to the Ionic Client App to allow it to send requests to the /hello endpoint exposed in the Cloud Code App.

The Client App is a simple Ionic App with a single input that accepts some text and a single button that sends the input to the cloud and displays the result to the user.

4.1.4.2.1. fhconfig.json

The Client App contains a fhconfig.json file. This file contains the information needed for the RHMAP Javascript SDK to communicate with the Cloud App.

Note

All HTML5 Client Apps must contain a fhconfig.json file to use the $fh Client API functions. This file is automatically populated with the required information when the app is created in the Studio.

4.1.4.2.2. $fh.cloud

In this example, the $fh.cloud Client API function is used to send requests to the hello endpoint in the Cloud Code App.

The $fh.cloud function is located in the fhcloud.js file. Here, the $fh.cloud function is exposed as a reusable service for the MainCtrl Controller to use.

There is a single controller in the Ionic App called MainCtrl. This controller is responsible for

  1. Accepting the input from the user from the example.html view.
  2. Using the fhcloud service to call the hello endpoint in the Cloud Code App.
  3. Processing the response from the Cloud Code App using the success or error functions, depending on whether the $fh.cloud call was successful.
Note

In this case, the Client App is using a GET request type. As the Cloud Code App exposes both a GET and POST version of the hello endpoint, a POST request type will also work. This is especially useful when dealing with RESTful applications.

4.2. Using Cordova Plug-ins

Cordova plug-ins provide a platform-independent JavaScript interface to mobile device capabilities in hybrid mobile apps. There are several official Apache plug-ins for basic device functions such as storage, camera, or geolocation, and other third-party plug-ins.

The official registry and distribution channel for Cordova plug-ins is npm and the plug-in ID corresponds to the npm package ID. The Cordova Plugins page contains the official plug-in list and acts as a filter for npm packages with the keyword ecosystem:cordova.

4.2.1. Supported Platforms

You can use Cordova plug-ins with all platforms supported by RHMAP. However, not all Cordova plug-ins support all platforms. Platforms supported by a plug-in are specified in a plug-in’s package.json file in the cordova.platforms object, or on the Cordova Plug-ins search page.

4.2.2. Adding Plug-ins to Apps

When developing Cordova applications, plug-ins are added using cordova plugin add, which downloads the plug-in from the repository, creates the necessary folder structure, and adds an entry in the config.xml file. See Platforms and Plugins Version Management in the official Cordova documentation for more information.

In RHMAP, however, plug-ins can also be declared in an RHMAP-specific JSON file config.json and makes the apps future-proof in case the plug-in specification format changes. The necessary changes can be implemented once in the RHMAP Build Farm instead of forcing all developers to update their apps.

{
  "plugins": [
    {
      "id": "cordova-plugin-device",
      "version": "latest"
    },
    {
      "id": "cordova-plugin-geolocation",
      "version": "1.0.1"
    },
    {
      "id": "cordova-labs-local-webserver",
      "url": "https://github.com/apache/cordova-plugins#master:local-webserver",
      "version": "2.3.1",
      "preferences": {
          "CordovaLocalWebServerStartOnSimulator": "false"
      }
    }
  ]
}

Three plug-ins are specified in the above example: the Device plug-in and Geolocation plug-in, which are available through npm, and the Local WebServer plug-in which is available only from the indicated Github repository.

4.2.2.1. Specification

The config.json file must be located in the www folder of a Cordova app.

The file must contain a key called plugins, the value of which is an array of JSON objects, which can define the following properties:

  • id (Required)

    This is the globally unique ID of the Cordova plug-in, which corresponds to its npm package ID. It can also be found in the plug-in’s plugin.xml file, as described in the Cordova Plugin Specification.

  • version (Required)

    The version of the plug-in to use. Corresponds to the npm package version. For plug-ins distributed through Git only, you can find the version of a plug-in in its plugin.xml.

    For plug-ins distributed through npm, you can also use the latest value to always use the latest available version. We strongly advise you to use a specific version that is proved to work with your app, since a plug-in upgrade could break backward compatibility.

  • url

    The URL of a public Git repository, containing a valid Cordova plugin (contains a plugin.xml file).

    The provided value for this field is used to download the plug-in, regardless of the values of the id and version fields. If this field is not provided, the plug-in will be downloaded from npm with the values of id and version fields.

    You can also specify a particular Git ref and a path to the plug-in within the repository, as described in official Cordova CLI documentation.

    • A Git ref object can be specified by appending #<git-ref> in the URL. We strongly recommend using a tag or other stable ref that is tested as working with your app. Using master as the ref could result in the plug-in code changing on every build and potentially breaking your application.
    • Path to the directory containing the plug-in can be specified with :<path> in the URL.

    For example, if we want to get a plug-in that resides in the plugin subdirectory in the release branch of the repository, the URL should have the following format:

    https://example.com/example.git#release:plugin

    After the plug-in is downloaded, its plugin.xml file will be parsed to make sure the plug-in ID matches the id field specified in the config.json.

  • preferences

    To provide plug-in configuration, use key-value pairs in the preferences object, like CordovaLocalWebServerStartOnSimulator in the example above.

  • variables

    Some plug-ins use variables in plugin.xml to parameterize string values. You can provide values for variables as key-value pairs in this field. See official documentation for more information on variables.

4.2.2.2. Default Plugins

Official plug-ins

  • cordova-plugin-device (1.1.2)
  • cordova-plugin-network-information (1.2.1)
  • cordova-plugin-battery-status (1.1.2)
  • cordova-plugin-device-motion (1.2.1)
  • cordova-plugin-device-orientation (1.0.3)
  • cordova-plugin-geolocation (2.2.0)
  • cordova-plugin-file (4.2.0)
  • cordova-plugin-camera (2.2.0)
  • cordova-plugin-media (2.3.0)
  • cordova-plugin-media-capture (1.3.0)
  • cordova-plugin-file-transfer (1.5.1)
  • cordova-plugin-dialogs (1.2.1)
  • cordova-plugin-vibration (2.1.1)
  • cordova-plugin-contacts (2.1.0)
  • cordova-plugin-globalization (1.0.3)
  • cordova-plugin-inappbrowser (1.4.0)
  • cordova-plugin-console (1.0.3)
  • cordova-plugin-whitelist (1.2.2)
  • cordova-plugin-splashscreen (3.2.2)

Optional

  • cordova-sms-plugin (v0.1.9)
  • com.arnia.plugins.smsbuilder (0.1.1)
  • cordova-plugin-statusbar (2.1.3)

4.2.3. Testing Apps in the Browser

If a plug-in is specified in the config.json file, the JavaScript object of the plug-in won’t be available until it is built and installed on the device. So if you reference the plug-in’s JavaScript object in your app, and try to load your app in App Preview in the Studio, you may get object undefined errors.

To solve this, use defensive checking when calling the plug-in APIs. For example, the following call to the Cordova camera API would result in an "object undefined" error:

navigator.camera.getPicture(success, fail, opts);

However, checking whether the API is defined lets you handle the error gracefully:

if(typeof navigator.camera !== "undefined"){
  navigator.camera.getPicture(success, fail, opts);
} else {
  //fail gracefully
  fail();
}

4.3. Using Secure Keys in your App

$fh.sec APIs provides the functionality to generate keys and data encryption/decryption. However, after the keys are generated, you may need to save them somewhere for future usage. For example, you have some data that needs to be encrypted with a secret key and saved on the device. Next time, when the app starts again, you need to get the same secret key and decrypt the data.

The best practice to achieve this is to save the keys on the cloud side, and associate the keys with the client using the client unique id (CUID) and app id.

4.3.1. CUID and App Id

Both the CUID and app id are sent by the client SDK in every $fh.act request. They are accessible via a special JSON object called __fh. The CUID is unique for each device and remain unchanged even if the app is deleted and re-installed. The app id is generated when the app is created on the platform and remain unchanged. You can use the following code to access them in the cloud code:

getKeyId = function(params){
  var cuid = params.__fh.cuid;
  var appid = params.__fh.appid;
  var keyid = cuid + "_" + appid;
  return keyid;
}

exports.getKey = function(params, callback){
  var keyid = getKeyId(params);
  //get a key using this keyid
  ....
}

4.3.2. Key Persistence

You can use whatever persistent mechanism you like to save the keys in the cloud. One recommended approach is to use $fh.db. Here is some example code to show how to save and retrieve keys using $fh.db:

//read a key using $fh.db
var getKey = function(params, cb){
  if(typeof $fh !== "undefined" && $fh.db){
    $fh.db({
      act:'list',
      'type': 'securityKeys',
      eq: {
        "id": getKeyId(params),  //The id is generated using the above example code
        "keyType": params.type
      }
    }, function(err, data){
      if(err) return cb(err);
      if(data.count > 0){
        return cb(undefined, data.list[0].fields.keyValue);
      } else {
        return cb(undefined, undefined);
      }
    });
  } else {
    console.log("$fh.db not defined");
    cb("$fh.db not defined");
  }
}

//save a key using $fh.db
var saveKey = function(params, cb){
  if(typeof $fh !== "undefined" && $fh.db){
    //first check if a key with the same id and type already exsists
    $fh.db({
      act:'list',
      'type': 'securityKeys',
      eq: {
        "id": getKeyId(params),
        "keyType": params.type
      }
    }, function(err, data){
      if(err) return cb(err);
      //a key with the same id and type already exists, update it
      if(data.count > 0){
        $fh.db({
          'act':'update',
          'type': 'securityKeys',
          'guid': data.list[0].guid,
          'fields' : {
            'id': getKeyId(params),
            'keyType': params.type,
            'keyValue' : params.value
          }
        }, function(err, result){
          if(err) return cb(err);
          return cb(undefined, result);
        })
      } else {
        //a key with the same id and type is not found, create it
        $fh.db({
          'act': 'create',
          'type': 'securityKeys',
          'fields': {
            'id' : getKeyId(params),
            'keyType': params.type,
            'keyValue': params.value
          }
        }, function(err, result){
          if(err) return cb(err);
          return cb(undefined, result);
        });
      }
    });
  } else {
    console.log("$fh.db not defined");
    cb("$fh.db not defined");
  }
}

4.3.3. Sample Code

A reference application has been created which fully demonstrates how to use $fh.sec APIs. The code for this application is available on GitHub: https://github.com/feedhenry-training/fh-security-demo-app.

4.4. Debugging Apps

Debugging is an essential part of app development. With web applications this can be as simple as using the alert() function to show the value of a variable at a certain point in the code. For browsers that have a console, the console.log function can be used to log this kind of information passively. More advanced forms of debugging web applications include inspecting the state of the DOM, breakpointing JavaScript code and stepping through it, or updating the DOM and the associated styles on the fly.

This page gives an explanation of the debugging tools that can be used while developing cross platform apps. These tools can be used in the Studio or on device.

For more information about how to prepare an app for debugging and how to debug on iOS and Android, see this blog.

4.4.1. Studio console

Most browsers support the console logger. Various log levels can be called with a message for example,

console.log(message);
console.error(message);

To debug this console output, you will need to open the relevant web debugging tools in your browser.

For debugging cloud code, simply use console.log() and console.error(). The corresponding log files can be viewed in the Studio under the 'Logs' section, or using FHC.

console.log('this goes to stdout.log');
console.error('this goes to stderr.log');

4.4.2. Firebug

If your browser of choice for development is Firefox, the Firebug tool makes debugging very easy. Firebug is an add-on for Firefox that is quite active and has many updates to it. Here are some of the things that can be done using Firebug:

  • view console output
  • view resource requests
  • debug script execution
  • dynamically run code, even when breakpoint debugging
  • view/update the DOM
  • view/update styles
  • view/update local storage (DB)

Using these features, debugging an app is made very easy. Getting an app working without Firebug showing any errors or problems gives the app a good chance of working cross platform.

4.4.3. Web Inspector with Chrome

Web Inspector is very similar to Firebug. It offers more or less the same features as Firebug, but has the advantage of being included with WebKit browsers out of the box. This means the Web Inspector can be used with Google Chrome and Safari. The tool is enabled by default with Chrome and can be started by context clicking any object on a web page, and selecting Inspect Element.

4.4.4. Safari / iOS 6+

Note

Remote debugging on iOS with Safari can only be enabled for applications built with a Development provisioning profile. Ad Hoc and Distribution apps cannot be debugged this way.

  1. Go to the Settings app on your iOS device.
  2. Navigate to SafariAdvanced, and then toggle on the Web Inspector switch.

    Studio Screenshot

  3. In desktop Safari, go to SafariPreferences, select Advanced, then check the Show Develop menu in menu bar check box.

    Studio Screenshot

  4. Connect your iOS device to your development machine via USB.
  5. Open the app you want to debug, it will be available in Safari’s Develop menu.

    Studio Screenshot

  6. The Develop menu has additional tools such as the User Agent switcher. This allows the browser to pretend to be a different browser for example, Mobile Safari. This feature can be useful if developing a mobile Internet version of an app.

4.4.5. Chrome / Android 4.0+

Note

Remote debugging on Android with Chrome can only be enabled for applications that flagged debuggable. If you are using Cordova 3.3 or newer, add android:debuggable="true" inside the <application> element in the AndroidManifest.xml file. If you are using Cordova 3.2 or older version, you have to enable WebView debugging.

  1. Enable USB debugging on your Android device On Android 4.2 and newer versions, the developer option is hidden. To make it available, navigate to SettingsAbout phone, and tap the Build number seven times.

    Studio Screenshot

  2. Go back to the previous screen to find Developer options, then check the USB debugging check box. Choosing the Stay awake option is also recommended.

    Studio Screenshot

  3. In the desktop Chrome browser, in the menu on the upper-right corner select ToolsInspect Devices
  4. Check Discover USB devices, if it is not selected.
  5. Connect your Android device via USB
  6. If it is the first time you attached this device for developing, you may see an alert on device requesting permission for USB debugging from your development machine. To avoid appearing this alert every time you debug, check Always allow from this computer, then tap OK.
  7. To debug your app, you need to run it on device.
  8. In Chrome, click the Inspect link to open the DevTools.

    Studio Screenshot

4.4.6. On-Device

Debugging on device is still in an early stage compared to debugging in a desktop browser. Some platforms, such as iOS, make the console available and show any javascript errors when Developer mode is enabled, but it is nowhere near as fully featured as Firebug or Web Inspector.

4.4.6.1. Weinre

Weinre is probably the best on-device debugging tool available at the moment. It officially only supports webkit browsers. This means it will work for Android and iOS. The full list and version numbers are available on its site.

Weinre works by setting up a remote debugging session from the app on the device to the Weinre server. Weinre runs a web server which can be used to access the Web Inspector, and remotely debug the app. At the time of writing, the features of this Web Inspector session allow debugging the DOM and updating it. The remote console is also shown, and the developer can dynamically run code in the app from the console.

To enable Weinre debugging in an application, there are a few steps.

  1. Get the Weinre jar up and running on a machine that the device can connect to. There are 2 ways of doing this:

    • Run the jar file on a machine accessible over the Internet
    • Run the jar on a machine on the same network as the device, that is, device connected via WiFi to the same router/access point as the developers machine.

    Note that when running the jar from the command line, it is advised to use the value -all- for the boundHost so that it is listening on all interfaces.

  2. Add a script to the HTML of the app before building and deploying it to the device. According to the documentation, the script include will look something like this.

    <script src="http://some.server.xyz/target/target-script-min.js#anonymous"></script>

    The address used, some.server.xyz, must match the address (ip address should work too) of the machine that is running the Weinre server.

  3. Deploy the app to the device and launch it. Opening the web page of the Weinre server (on the developer machine) should present a link to the debug user interface. This link opens up the Web Inspector and allows remote debugging of the app.

    As these instructions are for a third party tool, it is best to check with the official site for any updates around this setup process.

4.5. Managing App Connections in the Studio Using Connection Tags

4.5.1. Overview

You can use connection tags in RHMAP Studio to associate a Client App with a Cloud App. This enables you to change which Cloud App is associated with your Client App without the need to distribute an updated Client App binary. Cloud connections are updated by Client Apps upon restart.

4.5.2. Prerequisites

Make sure that you have at least two deployed Cloud Apps associated with your project.

4.5.3. Configuring App Connections in the Studio

  1. Log in to RHMAP Studio.
  2. Navigate to the Connections section, and find the app whose connection you want to re-configure.
  3. Click Configure next to the connection.
  4. From the drop-down list, pick the Cloud App you want to be associated with your Client App.
  5. Click Save and Update Connection to exit the configuration window. A message is displayed stating that the re-configuration was successful.
Note

Client Apps on Android might cache the location of the Cloud App, which may prevent them from immediately recognizing changes after the Cloud App connection is reconfigured. To ensure that the Cloud App location in your Client App is updated, restart the Client App on your device after completing the steps above.