Thursday, July 26, 2012

Creating an External Object for the iOS generator

As Gaston explained in a previous post, one of the mechanisms for extending the model, is the use of External Objects. By creating an External Object you can provide functionality that is not provided by default by GeneXus.

The problem to be solved

Recently I needed for a project to interact with the actions of a panel, in a way that is not the default. There were basically to possibilities: to hack the solution into the GeneXus libraries, or to create an External Object to provide the functionality. We decided to use the later.

What I needed, was to be able to display the low-priority actions in a panel where the navigation bar is hidden.

Some background here. When you create a Smart Device Panel in GeneXus, you can define actions, which have a priority: High, Normal or Low. The low-priority actions are displayed in an action sheet, that is presented from a button in the navigation bar.

The problem was that the navigation bar was hidden, so the user didn't even see the button to show the action sheet.

Creating a new External Object

The implementation of an External Object consists on three parts:
  1. The implementation of the functionality, in Xcode
  2. The GeneXus object that defines the External Object's API
  3. The mapper, that translates the name of the GeneXus object to the Objective-C class.
Defining the methods in GeneXus

Lets start by creating the definition. To do that, in your GeneXus KB, create a new object of type "External Object" and give it a name. In this case, the object is called EOActions.

After that, you need to define the methods of the External Object. In this case, it has only one method named "ShowLowPriorityActions" that doesn't receive any parameters and has no return value.

Important: every method must be declared as "static" in the method properties.

Implementing the External Object

The implementation is of course in Xcode, since we need to create a library.

So, open Xcode and create a new library project.

Add a reference to the GXFlexibleClient.framework.

The main class implementing the External Object must subclass GXActionExternalObjectHandler (you'll need to add an '#import <GXFlexibleClient/GXFlexibleClient.h>' ), and it must provide two methods:
  • + (BOOL)canHandleAction:(id <GXActionExternalObjectDescriptor>)actionDesc
  • - (void)actionExecuteWithContext:(id<GXActionHandlerContext>)context delegate:(id<GXActionHandlerDelegate>)delegate
In the second one, don't forget to call [super actionExecuteWithContext:context delegate:delegate];

After that, you need to provide your custom implementation, and once you are done, you must call either one of:
  • [self onFinishedExecutingWithSuccess];
  • [self onFinishedExecutingWithError:error];
depending on the result, so the actions that are after the External Object call, can get executed.

After you compile the library, you need to copy it to the GeneXus KB. It must be placed in
  • <model_dir>\mobile\iOS\<main_obj_name>\iOS\Genexus\UserControls
so it gets copied when compiling and liked against.

Mapping the External Object name to the implementation class

The last piece, is the source file to provide the mapping between the GeneXus' object name and the class implementing it.

The class must be named GXCustomExternalObjectsMapper, and has only one method:
  • - (NSString *)externalObjectClassNameForObjectName:(NSString *)objName;
that receives the name of the object and returns the name of the class.

This sources (GXCustomExternalObjectsMapper.h and GXCustomExternalObjectsMapper.m) should be placed in
  • <model_dir>\mobile\iOS\<main_obj_name>\iOS\Genexus\Classes
Example implementation

I know this may be a little intimidating at first, but once you've done an External Object, you'll see it is not too difficult. It involves only this three steps as described above.

I've uploaded the XCode project, the GeneXus External Object export (as a .xpz file) and the mapper class to a GitHub repository, so you can play with the code.