Recents

External Object for iOS DevicesOfficial Content

Suppose we want to add a new feature to the iOS environment to show a 'non intrusive' message to the user.

You can create a Native External Object within GeneXus; publish the desired method and properties to be called and do the implementation to integrate with the iOS platform.

Below there is a walkthrough you can follow to create an External object for the iOS platform.

Toast notifications are not available on the iOS SDK as the Android platform. We will use this project which builds a similar feature for iOS devices using Objective-C. We will implement two methods to send short and long toasts.

ExternalObjectForSDv1502

What steps should be followed to add this feature in GeneXus ?

Create an External Object

Create a new native object called MyApi; make sure to set the following property values:
 
iOS External name: MyApiHandler
Header File Name: MyApiHandler
 
Add a new method called ShortToast with the following values:
 
Is Static: True
External member type: Instance
 
and a character parameter using:
 
Access Type: In
 
Add a second method called LongToast  with the same parameters as the previous one.
 
the following figure details the desired result:
 
ExternalObjectForSD02
 
Once the external object is saved, you can use it with the following syntax:
MyApi.ShortToast("Hello Toast!")
Now, we have to do some implementation to actually get this working.

Mapping the External Object with an implementation File

During execution, the GeneXus iOS application does not know the External Object implementation class, just the External Object method executed with it's arguments.
The iOS External Object developer must provide a binding for a Objective-C class which actually resolves the method execution.
 
Create an Objective-C class named GXCustomExternalObjectsMapper with the following interface and place it in the Classes folder (GenerationFolder\DashboardName\iOS\Genexus\Classes):
//GXCustomExternalObjectsMapper.h
#import <Foundation/Foundation.h>

@interface GXCustomExternalObjectsMapper : NSObject
- (NSString *)externalObjectClassNameForObjectName:(NSString *)name;
@end
The implementation file must bind the External object name with an implementation class; this must be solved in the externalObjectClassNameForObjectName: method; the implementation to bind MyApi with MyApiHandler is the following (GXCustomExternalObjectsMapper.m):
#import "GXCustomExternalObjectsMapper.h"

@implementation GXCustomExternalObjectsMapper

- (NSString *)externalObjectClassNameForObjectName:(NSString *)name {
    if ([name isEqualToString:@"MyApi"])
        return @"MyApiHandler";
    return nil;
}
@end

This class is necessary because it notifies the iOS GeneXus application that a new class definition exits outside the standard GeneXus infraestructure.

If you want to define several external objects, all of them must be declared there.

Implementation Class

The next step is actually to implement the MyApiHandler Objective-C class (.h and .m files) and call the neccesary methods to execute the toast feature.
 
To get started download the iToast.h and iToast.m files from here and copy them to the Classes folder (GenerationFolder\DashboardName\iOS\Genexus\Classes) for the Xcode project associated to the Dashboard object; you can locate the Target Environment Directory, and then check the following subfolder \mobile\ios\DashboardName\iOS\Genexus\Classes
 
The next step is to actually implement the MyApiHandler class; it must inherit from GXActionExternalObjectHandler and the following methods must be implemented:
+ (BOOL)canHandleAction:(id <GXActionExternalObjectDescriptor>)actionExObjDesc;
- (void)actionExecuteWithContext:(id)contextEntityData delegate:(id)delegate;
the minimal MyApiHandler.h header file is as follows:
@import GXCoreBL;

@interface MyApiHandler : GXActionExternalObjectHandler

@end
The MyApiHandler.m implementation file is:
#import "MyApiHandler.h"
#import "iToast.h"

@implementation MyApiHandler

#pragma mark - Overrides

+ (BOOL)canHandleAction:(id )actionExObjDesc {
  if (actionExObjDesc && [@"MyApi" isEqualToString:[actionExObjDesc actionCallObjectName]]) {
    NSString *exObjMethod = [actionExObjDesc actionExternalObjectMethod];
    if ([exObjMethod hasPrefix:@"ShortToast"]
            || [exObjMethod hasPrefix:@"LongToast"]
            )
    {
      return YES;
    }
  }
  return NO;
}

- (void)actionExecuteWithContext:(id)contextEntityData delegate:(id)delegate {
  [super actionExecuteWithContext:contextEntityData delegate:delegate];

  NSString *exObjMethod = [self.actionExObjDesc actionExternalObjectMethod];
  if ([exObjMethod isEqualToString:@"ShortToast"]) {
    [self handleShortToastAction];
  } else if ([exObjMethod isEqualToString:@"LongToast"]) {
    [self handleLongToastAction];
  }
  else {
    [self onFinishedExecutingWithDefaultError]; // Unknown method
  }
}

- (void)handleShortToastAction {
    NSString *message = [self getFirstParameter];
   
    [[[[iToast makeText:NSLocalizedString(message, @"")]
       setGravity:iToastGravityCenter] setDuration:iToastDurationShort] show];
    [self onFinishedExecutingWithSuccess];
}

- (void)handleLongToastAction {
    NSString *message = [self getFirstParameter];
   
    [[[[iToast makeText:NSLocalizedString(message, @"")]
       setGravity:iToastGravityCenter] setDuration:iToastDurationNormal] show];
    [self onFinishedExecutingWithSuccess];
}

- (NSString*)getFirstParameter {
    // Read parameters
    NSArray *parameters = [self readStringParametersFromEntityData:self.contextEntityData];
    if ([parameters count] != 1) {
        NSError *error = [NSError errorWithDomain:@"" code: userInfo:nil];
        [self onFinishedExecutingWithError:error];
        return nil;
    }
    NSString *message = [parameters objectAtIndex:];
   
    return message;
}

@end

Implementation details

Some comments about the previous MyApiHandler.m file.

The GeneXus iOS application will call the canHandleAction method whenever it finds an action that calls the external object, to check if the action can be handled and determine if it should appear on screen (only executable actions are show).

The actionExObjDesc parameter details the External Object name executed, that's why the following if sentence is added:

if (actionExObjDesc && [@"MyApi" isEqualToString:[actionExObjDesc actionCallObjectName]]) {

The next step is to check which method is actually called and return YES|NO.

Next, when executing an external object method in the iOS platform, the actionExecuteWithContext:delegate: method is executed.
The developer must check the method name executed, get the method parameters list; do the casting needed and delegate to the code that actually implements the feature.

In relation to the Toast sample, we defined private methods handleShortToastAction and handleLongToastAction; when executing MyApi.ShortToast("Hello Toast!") the following code will actually be executed:

if ([exObjMethod isEqualToString:@"ShortToast"]) {
  [self handleShortToastAction];
} else if ([exObjMethod isEqualToString:@"LongToast"]) {
        [self handleLongToastAction];
    }

Then, the handleShortToastAction|handleLongToastAction method will be executed, in this case we only need the method argument, check the getFirstParameter method:

NSArray *parameters = [self readStringParametersFromEntityData:self.contextEntityData];
    if ([parameters count] != 1) {
        NSError *error = [NSError errorWithDomain:@"" code: userInfo:nil];
        [self onFinishedExecutingWithError:error];
        return nil;
    }
    NSString *message = [parameters objectAtIndex:];

To read a string method arguments the readStringParametersFromEntityData method is used.

Then the makeText:setGravity:setDuration: method from the iToast class will be executed with a different duration depending on the method executed (short or long toast).

[[[[iToast makeText:NSLocalizedString(message, @"")]
       setGravity:iToastGravityCenter] setDuration:iToastDurationNormal] show];

Done ! you are ready to test your iOS MyApi external object with two available methods; make sure to copy the files under the detailed folders and recompile the application.

Summary

The following files were created to actually implement a MyApi External Object which implements a Short|LongToast methods with one argument:

  • GXCustomExternalObjectsMapper.h and GXCustomExternalObjectsMapper.m: declares the existence of an External Object and a class binding.
  • iToast.h and iToast.m: implementation class.
  • MyApiHandler.h and MyApiHandler.m: External Object handler.

Recommendations

When implementing the actionExecuteWithContext:delegate: method, always call the base class at the beginning of the method using:

[super actionExecuteWithContext:contextEntityData delegate:delegate]

To get the method executed (very handy when the external object defines several methods) use:

[self.actionExObjDesc actionExternalObjectMethod]

To read the methods arguments use:

  • readStringParametersFromEntityData: for strings.
  • readDateParameter:fromEntityData: for Date.
  • readDateTimeParameter:fromEntityData: for DateTime.
  • readIntParameter:fromEntityData: for Numeric.
  • readBoolParameter:fromEntityData: for Boolean Data type.

All this methods receive a "action parameter descriptor":

[[self.actionDesc actionParametersDescriptor] actionParametersDescriptors]

returning an id<GXActionParameterDescriptor> array.

Make sure the External Object method calls the onFinishedExecutingWithSuccess, so the base class can call any other object to finish the method execution. When the method executes with error use the onFinishedExecutingWithError: method.

When the method is asynchronous, make sure to call this method when the associated Callback method is executed.

When the external object returns a value, call the setReturnValue: method with the return value as argument.

External Object Definition

Make sure the External Object definition is complete; all the iOS specific properties are set. The current implementation always uses the execute method detailed above, in future versions the GeneXus iOS framework will directly call to the method detailed in the External Object reference; the execute method will no longer be needed.

Installation

This article explains the extension point for external objects; all modifications were done in the target generation folder.
 
To update a GeneXus installation, the "iOS\Templates\iOS_Genexus\Classes" folder must be updated with the classes detailed above.
 
The \iOS\iOS_Swift.impl template file must add extra steps to copy the new files to the target generation folder and transfer them to the MAC.
Following the example, the Bootstrapping section added the following items in the Templates section:
<!-- Extra files to support Toasts -->
<Template Id="iOS_Genexus\Classes\GXCustomExternalObjectsMapper.h" Output="iOS\Genexus\Classes\GXCustomExternalObjectsMapper.h"/>
<Template Id="iOS_Genexus\Classes\GXCustomExternalObjectsMapper.m" Output="iOS\Genexus\Classes\GXCustomExternalObjectsMapper.m"/>
<Template Id="iOS_Genexus\Classes\iToast.h" Output="iOS\Genexus\Classes\iToast.h"/>
<Template Id="iOS_Genexus\Classes\iToast.m" Output="iOS\Genexus\Classes\iToast.m"/>
<Template Id="iOS_Genexus\Classes\MyApiHandler.h" Output="iOS\Genexus\Classes\MyApiHandler.h"/>
<Template Id="iOS_Genexus\Classes\MyApiHandler.m" Output="iOS\Genexus\Classes\MyApiHandler.m"/>
In future version this mechanism will be improved.
 
When using extension points; the Knowledge Base navigator utility is no longer a valid option because it does not cointains the platform specific code added via External objects or User Controls. For those cases use the compiled application.

External Dependencies

If the External object uses external libraries, make sure to create a universal library for each one.
Then copy the *.a files to the UserControls folder, and the header files to the Classes folder.

Restrictions

Do not include a external library as a subproject, use a Universal library instead.

Source Code

You can download the source code from here. If you download the Toast files directly from Github please comment the following lines from the iToast.m file

[label release];
[imageView release];

and change the following line

iToast *toast = [[[iToast alloc] initWithText:_text] autorelease];

to

iToast *toast = [[iToast alloc] initWithText:_text];

otherwise you will get the following error when compiling

ARC forbids explicit message send of 'release'|'autorelease'

Troubleshooting

Use Xcode to debug the GX application calling the External object methods.

See also

Was this page helpful?
What Is This?
Your feedback about this content is important. Let us know what you think.