Tuesday, April 28, 2015

Adding native functionality to hybrid apps

Today's topic is adding native iOS and Android functionality in hybrid apps using IBM MobileFirst (Worklight) as the development tool.  Instead of concentrating on any specific app, I will instead focus on the hybrid-native integration areas since that's the important part that can be applied to any app.  Thus you should have familiarity with building hybrid apps in IBM MobileFirst as a pre-req to this post.

As a simple example, let's say we have an app that has must call out to a native service to get a response to decide whether or not to allow the app to run for this particular user.  And let's say that native service is backed by a software solution that requires the username/password, as well as a specific developer license key and server location in order to use the software.

So in this case then, you would create a hybrid app using IBM MobileFirst and code the majority of it in Javascript.  We will pick up the example where you will need to call out to the native functionality as described above to make a yes/no decision, and a provide a reason why if 'no' is decided.

First, you will need to pass the required arguments as described above to the native service.  There are many ways to do this, but my preference is to use something very familiar to Javascript developers.  We can create a JSON string that will contain all the arguments.  This can be done simply by using the "stringify" method natively supported in the browser:

  var obj = new Object();
  obj.username = theUsername;
  obj.password = thePassword;
  obj.licenseKey = theLicenseKey;
  obj.serverURL = theServerURL;
  var jsonString = JSON.stringify(obj);

Because this is a hybrid app, this Javascript will be running in the mobile browser and it is possible that a back level device/browser will not have this support.  In which case creating a simple JSON string directly will work and support all devices:

  var jsonString = "{"
    + "\"username\": \" + theUsername + "\","
    + "\"password\": \" + thePassword + "\","
    + "\"licenseKey\": \" + theLicenseKey + "\","
    + "\"serverURL\": \" + theServerURL + "\"}";

Now, in your Javascript where you will need to call out to the native functionality, you will leverage libraries available in Apache Cordova which is the library to allow access to a set of device APIs that IBM MobileFirst uses.  The format for this call will be as follows:

  cordova.exec(
    function() { alert("Success!"); },
    function(data) { alert("Failed: " + data.message); },
    "MyNativePlugin",
    "isAppAvailable",
    [jsonString]);

In this Javascript snippet, we are using Cordova to call a native plugin that we will provide in a minute.  We pass this "exec" function 1) an anonymous function to alert us on success, 2) an anonymous function to alert us on failure and provide a message why from the native plugin, 3) the name of our native plugin, 4) the method to call on that native plugin, and 5) an array of arguments.  In this case we are using anonymous functions for simplicity, you of course would mostly likely use more sophisticated regular functions in your app.  Also, because we have captured all the configuration in one JSON object, we are only passing a single object array.  Alternatively, we could pass a Javascript array with each of the configuration parameters separately.  But that would require use to know something about the order of each argument.

Now that we have the Javascript complete, we need to let the app know about where to find the plugins.  This can be done in the config.xml file available for both the Android and iOS environments for our hybrid app in IBM MobileFirst.

  // Plugin for iOS
  <feature name="MyNativePlugin">
    <param name="ios-package" value="MyNativePlugin">
  </feature>

  // Plugin for Android
  <feature name="MyNativePlugin">
    <param name="android-package" value="com.mycompany.plugin.MyNativePlugin">
  </feature>
  
Now this Javascript code will work for both iOS and Android environments calling out to the right plugin for the platform as necessary.

The next step is to develop the plugins.  Unfortunately the API for plugins between iOS and Android is slightly different beyond just syntactical differences.  However the differences aren't that great and are easy to implement.

First let's start with iOS.  In this case we need to create a new MyNativePlugin class and place it in the "../native/Classes" directory for our iOS environment.  We can declare it as follows:

  #import <Foundation/Foundation.h>
  #import <Cordova/CDV.h>
  
  @interface MyNativePlugin : CDVPlugin
    - (void)isAppAvailable:(CDVInvokedUrlCommand *)command;
  @end

Now we must provide the implementation for this plugin class (Note, I'm not implementing all the error logic you would normally need to provide in a production app).  We need to first parse the JSON arguments that was passed to the plugin.  Then we need to call out to the native service with those arguments to allow it to make a yes/no decision.  In the case of success, we need to simply reply it was a success.  In the case of failure, we need to reply it was a failure, but also provide the message describing the reason why so it can be shown in the Javascript alert dialog shown above.

  #import "MyNativePlugin.h"

  @implementation MyNativePlugin

  -(void)isAppAvailable:(CDVInvokedUrlCommand *)command {
    
    BOOL canUseApp = NO;

    // Parse the JSON arguments.
    if (command.arguments > 0) {

      NSString *args = [command.arguments objectAtIndex:0];

      NSError *error = nil;
      NSDictionary *dict = [NSJSONSerialization
        JSONObjectWithData:[args dataUsingEncoding:NSURF8StringEncoding]
                   options:0
                     error:&error];

      if (dict != nil && error != nil) {

         // Example call to a service (insert your own code here).
         canUseApp = [myService canUseApp:"AppID"
                              serviceURL:[dict objectForKey:@"serverURL"]
                              licenseKey:[dict objectForKey:@"licenseKey"]
                                username:[dict objectForKey:@"username"]
                                password:[dict objectForKey:@"password"]];
      }  
    }

    if (canUseApp) {
      
      [self.commandDelegate
        sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK]
        callbackId:command.callbackId];
    }
    else {

      NSDictionary *d = @{@"message" : @"Unable to successfully enable the app."};
        
      CDVPluginResult *result = [resultWithStatus:CDVCommandStatus_ERROR
                              messageAsDictionary:d];

      [self.commandDelegate sendPluginResult:result callbackId:command:callbackId
  }

  @end

Now let's do the exact same thing for Android (notice the difference in syntax and API calls):

  public class MyNativePlugin extends CordovaPlugin {

    @Override 
    public boolean execute(final String action, 
      final JSONArray args, final CallbackContext callbackContext)
      throws JSONException {

      boolean canUseApp = false;

      // First match the action.
      if (action.equals("isAppAvailable")) {

        // Parse the JSON arguments.
        if (args.length() > 0) {

          JSONObject json = new JSONObject(args.getString(0));

          // Example call to a service (insert your own code here).
          canUseApp = myService.canUseApp("AppID",
            json.getString("serverURL"), 
            json.getString("licenseKey"),
            json.getString("username"),
            json.getString("password"));
        }
      
        if (answer) {

          callbackContext.success();  
        }
        else {

          JSONObject jsonResult = new JSONObject();
          jsonResult.put("message", "Unable to successfully enable the app.");

          callbackContext.error(jsonResult);
        }

        return true;
      }
      else {

        // Indicates action was not found.
        return false;
      }
  }
}

I did leave out some important things to keep this code example short.  For example, proper implementations should introduce more error handling.  And plugins should create a separate thread instead of doing it on the main thread.  On Android you can do this with the following:

  cordova.getThreadPool().execute( /* Wrap your code in a Runnable here */ );

And that's it, you should now be able to create your own Cordova plugins to implement your own native function in your Hybrid app!



Thursday, April 16, 2015

Docker, MacOS & Cisco AnyConnect VPN

If you are unsuccessfully trying to use Docker on your Mac, and you are using the Cisco AnyConnect VPN, you have come to the right place :-)

Docker, if you don't already know, is gaining more and more traction in the industry as the best open platform for distributed applications.  There are all sorts of advantages to using Docker over say a traditional Virtual Machine.  If you haven't already, you should definitely check out the many online resources explaining Docker.  There are many good videos on YouTube.

Now back to the point of this blog post :-)  After learning a bunch about Docker, I was excited and decided to give it a whirl on my own.  Docker uses features of the Linux OS, so it only works on Linux natively.  However, there is an install for Mac OS, which leverages VirtualBox to install a Virtual Linux Machine to host the Docker containers.  The tool you install on your Mac is called "boot2docker", and it's all documented right here: http://docs.docker.com/installation/mac/

Unfortunately, it didn't go as smoothly as advertised.  Networking between the Mac OS and the Linux Virtual Machine just wouldn't work.  But as it turns out that's not Dockers fault, after much Googling I figured out it's the Cisco VPN AnyConnect that was causing the issue.  So if you are remotely working via VPN, it won't work.  The details for the issue can be found online, but in short, AnyConnect captures all traffic from 192.168.59 which Docker uses to communicate with the Virtual Machine.

I tried all sorts of fixes and suggestions online, but nothing worked.  After a few hours of struggling, I finally stumbled upon this gem of a script that fixes the issue.  All you have to do is run it before you connect to the VPN, and like magic, Docker works!


Happy Dockering and I hope I saved you the hassles I had to go through ;-)

Friday, February 6, 2015

Logging Continued

In my last blog post you saw how you can define a simple logging facility.  Now I need to show you how to implement it :-)  Let's start by implementing the prototypical instance I mentioned in the previous blog post.  Let's define the static configuration method as the following.  If the prototype has not yet been created, we'll do that here first.

+ (MDBLog *)configuration {

    static MDBLog *prototypeLog = nil;
    @synchronized(self) {
        
        //Initial defaults, can be overridden.
        if (prototypeLog == nil) {
            
            prototypeLog = [[self alloc] init];
        
            //Set the defaults
            prototypeLog.logLevelMDBLogLevelError;
            prototypeLog.logContext = [NSString stringWithFormat:@"%@", [MDBLog class]];
            prototypeLog.logFormat = @"[%@] (%@:%@): %@";
            prototypeLog.logWriter = [MDBLog defaultLogWriter];
        }
    }
    
    return prototypeLog;
}

First, we create the static Log prototype.  Then we set the defaults for that prototype.  Lastly, we return that static instance that can be configured.

You'll notice that I set the log level to Error (the highest, least detailed), use the default log writer which writes to the console, and give a message string format that will show something like the following.  This of course can be configured.

"[LOGLEVEL] (CLASS:METHOD): LOG MESSAGE PARAMETERIZED WITH VALUES"

When we create a new Log instance, we'll use one of the following initialization methods which uses the prototypical instance to set the defaults:

- (id)initWithClass:(Class)logClass {
    
    return [self initWithContext:[NSString stringWithFormat:@"%@", logClass]];
}

- (id)initWithContext:(NSString *)logContext {
    
    if (self = [super init]) {
        
        _logContext = logContext;
        _logFormat = [MDBLog configuration].logFormat;
        _logLevel = [MDBLog configuration].logLevel;
        _logWriter = [MDBLog configuration].logWriter;
    }
    
    return self;
}

The logging methods are just as you'd expect them to be.  Here they are for "Error" log level, the others are similar:

- (BOOL)errorEnabled {
    
    return [self levelEnabled:MDBLogLevelError];
}

- (void)error:(SEL)method format:(NSString *)format, ... {
    
    va_list args;
    va_start(args, format);
    
    [self logAt:MDBLogLevelError method:method format:format args:args];
}

The generic methods for logging are defined as the following:

- (BOOL)levelEnabled:(enum MDBLogLevel)intentLevel {

    return self.logLevel <= intentLevel;
}

- (void)logAt:(int)intentLevel method:(SEL)method format:(NSString *)format args:(va_list)args {
    
    [self.logWriter log:self logLevel:intentLevel method:method format:format args:args];
}

In this case, all the information is sent to the configured Log Writer, including the variable parameter list and associated log message format string.  In order to write out to the console, we will implement the Log Writer like so:

@implementation MDBLogWriterDefault : NSObject

- (void)log:(MDBLog *)log logLevel:(enum MDBLogLevel)logLevel method:(SEL)method format:(NSString *)format args:(va_list)args {
    
    if ([log levelEnabled:logLevel]) {

        NSString *str = [[NSString alloc] initWithFormat:format arguments:args];
        NSLog(log.logFormat, [MDBLog nameOfLevel:logLevel], log.logContext, NSStringFromSelector(method), str);
    }
}

Of course, you can provide your own Log Writer that could pipe the log message to any other output or another logging facility.

An example use of this Logging facility is something like the following:

// Unnecessary, but done to show how can set custom writer.
[[MDBLog configuration] setLogWriter:[MDBLog defaultLogWriter]];

#ifdef DEBUG
[[MDBLog configuration] setLogLevel:MDBLogLevelDebug];
#else
[[MDBLog configuration] setLogLevel:MDBLogLevelWarn];
#endif

_log = [MDBLog logWithClass:[self class]];

...

[self.log debug:_cmd format:@"Formatted log message with key/value pair: %@=%@", key, value];

And that's it!  Simple and powerful.  Read more to get the full implementation here...

Logging

My first development blog post, and what other topic would I pick other than the boring topic of Logging!  It might be boring, but it's fundamental in all we devs do.  I don't know about you, but I hate bringing in big frameworks or complex code, especially just to do logging.  I prefer to have my own simple set of classes, and if I want to later, I can simply pipe the results to an existing framework depending on the project I'm working on.

So here we go, how do we create a simple logging capability that is flexible enough to plug in else where?  It's simple!  I'll show you just what I did, in this case in Objective-C, but the concepts can easily convert to any other syntax.

So let's start with the basics, logging levels.  These are pretty standard across the board, and start from most amount of detail, to least.  So for example, selecting logging level "info" will include info message, as well as debug and fine messages.  So let's define an enum that represents this (note, using MDB as the prefix which stands for my blog name "My Dev Bits"):

enum MDBLogLevel {
    MDBLogLevelFine = 1,
    MDBLogLevelDebug,
    MDBLogLevelInfo,
    MDBLogLevelWarn,
    MDBLogLevelError
};

Next, let's define the how we get a Log we can use in our code.  We'll create two static methods:

+ (MDBLog *)logWithClass:(Class)logClass;
+ (MDBLog *)logWithContext:(NSString *)logContext;

The first will create a "Context" (a simple string) using the name of the Class.  If you want to use something else, you can specify that in the second method.

Now, we will want to be able to configure the Log globally, so we will add a static method for that.

+ (MDBLog *)configuration;

And a static helper method that we can use later to format our Log strings.

+ (NSString *)nameOfLevel:(int)logLevel;

Now we need to define a set of properties and getters/setters for a Log instance.  In this case, a Log instance will have a 1) logging level (the level at which it will either output or suppress the log output), 2) the format of the logging string, and 3) a string log context. 

@property (readwrite, nonatomic) enum MDBLogLevel logLevel;
@property (readonly, nonatomic, copy) NSString *logFormat;
@property (readonlynonatomiccopyNSString *logContext;

The log Level and log Format could actually just be statically defined, but instead I made them instance variables to give the most flexibility in case there is a reason to change it for a particular logging instance.  But we don't want to do this all the time, so in order to have default values, we will create a "prototype" Log instance that we will create internally and will be used to set the defaults for any new Log instances created.  More on that later.

Now we need to define the methods a client can use to instruct the Log to output some information, let's define them this way to match the possible logging levels:

// Determines if the Log is outputting at the specified intent level.
- (BOOL)levelEnabled:(enum MDBLogLevel)intentLevel;

// Determines if the particular Log level is enabled.
- (BOOL)fineEnabled;
- (BOOL)debugEnabled;
- (BOOL)infoEnabled;
- (BOOL)warnEnabled;
- (BOOL)errorEnabled;

- (void)log:(enum MDBLogLevel)logLevel method:(SEL)method format:(NSString *)format, ...;

- (void)fine:(SEL)method format:(NSString *)format, ...;
- (void)debug:(SEL)method format:(NSString *)format, ...;
- (void)info:(SEL)method format:(NSString *)format, ...;
- (void)warn:(SEL)method format:(NSString *)format, ...;
- (void)error:(SEL)method format:(NSString *)format, ...;

The main logging method here is the "log" method and it takes a logging level, a method that is calling the log, and the string that defines the format of this particular logging message.

Also note that the last parameter is "..." and the refers to a dynamic list of parameters that an be provided.  This will allow the client to call the logging methods and pass a string that will have substitute values, and then provide the list of those values to substitute.

The other methods are for convenience and correspond to logging at a particular logging level and will ultimately just call the more generic "log" method specifying the log level.

By default, we will just write the logging output to the console.  But we want the ability to "pipe" the logging output to some other output (e.g. another logging facility perhaps).   We can accomplish this simply by defining an interface for a "log writer" and then let clients of this Log facility create their own and plug them in.  So let's define that, first the interface:

@protocol MDBLogWriter <NSObject>

- (void)log:(MDBLog *)log logLevel:(enum MDBLogLevel)logLevel method:(SEL)method 
format:(NSString *)format args:(va_list)args;

@end

Implementors of this interface will get the calling Log class, the log level to output at, the calling method, a format for the string, and the variable list of argument parameters to replace in the format string.

We will also create a default Log Writer that will just output to the console, and we can define that with a property for the Log Writer in a particular Log instance.

@property (readwrite, nonatomic) id<MDBLogWriter> logWriter;

And a static method to get the default log writer:

+ (id<MDBLogWriter>)defaultLogWriter;

And then define the default Log Writer instance:

@interface MDBLogWriterDefault : NSObject <MDBLogWriter>
@end 

So that's it, this defines our Log class and related facilities.  In my next blog post I'll describe what these properties and methods do, and how to implement them.  In the mean time, read more here to get the complete header file...