In this tutorial I will show how to create an iOS native extension for Adobe AIR. My extension enables sending an e-mail by invoking MFMailComposeViewController on iOS. This way you can send an e-mail without leaving you app, attachments are supported, too.
Grab the source from github: iOS In-app mail Native Extension for Adobe AIR
How to use iOS mail extension
Create an intance of class pl.randori.air.nativeextensions.ios.MailExtension and call method
sendMail(subject:String, messageBody:String, toRecipients:String, ccRecipients:String = ””, bccRecipients:String = ””, attachmentsData:Array = null):void
To add an attachment from application bundle:
- filename|bundle|file_mimetype|name_of_file_to_be_shown_in_mail
to add an attachment from application documents directory use
- filename|documents|file_mimetype|name_of_file_to_be_shown_in_mail
sendMail method implementation should make it more clear
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /** * @param subject Mail subject * @param messageBody Mail body (can include HTML) * @param toRecipients To: recipients in format: "mail@example.com,mail2@example.com" * @param ccRecipients Cc: recipients in format: "mail@example.com,mail2@example.com" * @param bccRecipients Bcc: recipients in format: "mail@example.com,mail2@example.com" * @param attachmentsData Attachments in format: ['filename|bundle|mimetype|name of file to display in attachment'] * example: ["Default.png|bundle|image/png|Application splash screen.png","Example file.dat|documents|text/xml|A file saved in Adobe AIR iOS app.txt"] */ public function sendMail(subject:String, messageBody:String, toRecipients:String, ccRecipients:String = '', bccRecipients:String = '', attachmentsData:Array = null):void { extensionContext.addEventListener( StatusEvent.STATUS, onStatusEvent); extensionContext.call( "sendMailWithOptions", subject, messageBody, toRecipients, ccRecipients, bccRecipients, attachmentsData); } |
MailExtension dispatches MainExtensionEvent.MAIL_COMPOSER_EVENT to let you know what’s going on. You will be notified if the user has sent the mail, saved it, canceled, etc.
Also there will be notifications WILL_SHOW_MAIL_COMPOSER and WILL_HIDE_MAIL_COMPOSER so you could know when the mail composer is shown and dismissed.
This can be used to stop / resume task in AIR while the mail composer is present on the screen
Creating an native extension for Adobe AIR requires some iOS/Objective-C knowledge so don’t worry if not everything’s clear at the beginning.
I divided the process of development to three steps
1. Creating a native library in Xcode
2. Creating an ActionScript library which will act as a middleware between Adobe AIR and iOS
3. Creating an example Adobe AIR app
My development environtment was Flash Builder 4.5 with Flex SDK 4.5.1 and AIR SDK 3.1 and Xcode 4.2 and iOS SDK 5. Flash Builder 4.5 doesn’t support native extensions development, so I’ve used ANT to compile and package everything.
Before starting be sure to merge Flex SDK with Adobe AIR SDK.
Creating the native library in Xcode for AIR an iOS
A native library can be written in Objective-C, C/C++ or Java depending on the target platform. Currently extensions for iOS, Android, BlackBerry PlayBook and AIR TV are supported. The library exposes an API that the Adobe AIR application can use, which can include functionalities that are not available in the current release of Adobe AIR or make use of performance of native code (math, physics computations; image processing, etc.).
On iOS Adobe AIR cannot access to such APIs as: Game Center, In-App Purchase, Twitter or Bluetooth. With native extensions we can create a fully featured iOS app.
In Xcode create a new static library project and set the project setting as follows:
Set “Enable Linking with Shared Libraries” to NO if when packaging your app into an ipa archive you see in console a message that looks like:
ld warning: unexpected srelocation type 9

Add FlashRuntimeExtensions.h and implement required methods
Next, you need to add FlashRuntimeExtensions.h to your project. This file can be found in FLEX_SDK\include. This file will provide necessary data types definitions and functions that will be used for communication between native code and AIR app.
Native extension written using C API (which iOS extension are) requires four methods to be implemented:
- extension initializer
- extension finalizer
- context initializer
- context finalizer
In MailExtension.m those functions are: ExtInitializer, ExtFinalizer, ContextInitializer, ContextFinalizer. Those are just the names I used, they can actually be named anything you want, but you must provide those functions’ names in configuration xmls.
My main extension file MailExtension.m looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 | // // MailExtension.m // MailExtension // // Created by Piotr Kościerzyński on 11-11-29. // Copyright (c) 2011 Randori. All rights reserved. // #import "MailExtension.h" @implementation MailExtension static NSString *attachmentsSeparator = @"----"; static NSString *event_name = @"MAIL_COMPOSER_EVENT"; FREContext g_ctx; MailComposerHelper *mailComposerHelper; - (id)init { self = [super init]; if (self) { // Initialization code here. } return self; } int canSendMail(void) { BOOL result = NO; //On pre iOS 3.0 devices MFMailComposeViewController does not exists Class mailClass = (NSClassFromString(@"MFMailComposeViewController")); if (mailClass != nil) { // We must always check whether the current device is configured for sending emails if ([mailClass canSendMail]) { result = YES; } else { result = NO; } } //this will never happen since Adobe AIR requires at least iOS 4.0 else { result = NO; } return (int)result; } //Can we invoke in-app mail ? FREObject PKIsMailComposerAvailable(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[]) { BOOL ret = canSendMail(); FREObject retVal; FRENewObjectFromBool(ret, &retVal); return retVal; } //Send mail FREObject PKSendMailWithOptions(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[] ) { BOOL ret = canSendMail(); if (!ret) { FREDispatchStatusEventAsync(ctx, (uint8_t*)[event_name UTF8String], (uint8_t*)[@"MAIL_COMPOSER_NOT_AVAILABLE" UTF8String]); return NULL; } if(argc<3) { FREDispatchStatusEventAsync(ctx, (uint8_t*)[event_name UTF8String], (uint8_t*)[@"NOT_ENOUGH_PARAMETERS_PROVIDED" UTF8String]); return NULL; } //Subject uint32_t subjectLength; const uint8_t *subjectCString; //To Recipients uint32_t toRecipientsLength; const uint8_t *toRecipientsCString; //CC Recipients uint32_t ccRecipientsLength; const uint8_t *ccRecipientsCString; //Bcc Recipients uint32_t bccRecipientsLength; const uint8_t *bccRecipientsCString; //Message Body uint32_t messageBodyLength; const uint8_t *messageBodyCString; NSMutableString *attachmentsString = nil; NSString *subjectString = nil; NSString *toRecipientsString = nil; NSString *ccRecipientsString = nil; NSString *bccRecipientsString = nil; NSString *messageBodyString = nil; //Create NSStrings from CStrings if (FRE_OK == FREGetObjectAsUTF8(argv[0], &subjectLength, &subjectCString)) { subjectString = [NSString stringWithUTF8String:(char*)subjectCString]; } if (FRE_OK == FREGetObjectAsUTF8(argv[1], &messageBodyLength, &messageBodyCString)) { messageBodyString = [NSString stringWithUTF8String:(char*)messageBodyCString]; } if (FRE_OK == FREGetObjectAsUTF8(argv[2], &toRecipientsLength, &toRecipientsCString)) { toRecipientsString = [NSString stringWithUTF8String:(char*)toRecipientsCString]; } if (argc >= 4 && (FRE_OK == FREGetObjectAsUTF8(argv[3], &ccRecipientsLength, &ccRecipientsCString))) { ccRecipientsString = [NSString stringWithUTF8String:(char*)ccRecipientsCString]; } if (argc >= 5 && (FRE_OK == FREGetObjectAsUTF8(argv[4], &bccRecipientsLength, &bccRecipientsCString))) { bccRecipientsString = [NSString stringWithUTF8String:(char*)bccRecipientsCString]; } uint32_t attachmentsArrayLength = 0; //argv[5] is a an array of strings if (argc >= 6 && (FRE_OK != FREGetArrayLength(argv[5], &attachmentsArrayLength))) { //No valid array of attachments provided. } if (attachmentsArrayLength >= 1) { attachmentsString = [[NSMutableString alloc ] init]; uint32_t attachmentEntryLength; const uint8_t *attachmentEntryCString; for (int i = 0; i < attachmentsArrayLength; i++) { FREObject arrayElement; FREGetArrayElementAt(argv[5], i, &arrayElement); FREGetObjectAsUTF8(arrayElement, &attachmentEntryLength, &attachmentEntryCString); [attachmentsString appendString:[NSString stringWithUTF8String:(char*)attachmentEntryCString]]; if (i<(attachmentsArrayLength-1)) [attachmentsString appendString:attachmentsSeparator]; } } if (mailComposerHelper) { } else { mailComposerHelper = [[MailComposerHelper alloc] init]; } [mailComposerHelper setContext:ctx]; [mailComposerHelper sendMailWithSubject:subjectString toRecipients:toRecipientsString ccRecipients:ccRecipientsString bccRecipients:bccRecipientsString messageBody:messageBodyString attachmentsData:attachmentsString]; if (attachmentsString != nil) [attachmentsString release]; return NULL; } //------------------------------------ // // Required Methods. // //------------------------------------ // ContextInitializer() // // The context initializer is called when the runtime creates the extension context instance. void ContextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctionsToTest, const FRENamedFunction** functionsToSet) { //we expose two methods to ActionScript *numFunctionsToTest = 2; FRENamedFunction* func = (FRENamedFunction*) malloc(sizeof(FRENamedFunction) * 2); func[0].name = (const uint8_t*) "sendMailWithOptions"; func[0].functionData = NULL; func[0].function = &PKSendMailWithOptions; func[1].name = (const uint8_t*) "isMailComposerAvailable"; func[1].functionData = NULL; func[1].function = &PKIsMailComposerAvailable; *functionsToSet = func; g_ctx = ctx; } // ContextFinalizer() // // The context finalizer is called when the extension's ActionScript code // calls the ExtensionContext instance's dispose() method. // If the AIR runtime garbage collector disposes of the ExtensionContext instance, the runtime also calls // ContextFinalizer(). void ContextFinalizer(FREContext ctx) { [mailComposerHelper setContext:NULL]; [mailComposerHelper release]; mailComposerHelper = nil; return; } // ExtInitializer() // // The extension initializer is called the first time the ActionScript side of the extension // calls ExtensionContext.createExtensionContext() for any context. void ExtInitializer(void** extDataToSet, FREContextInitializer* ctxInitializerToSet, FREContextFinalizer* ctxFinalizerToSet) { *extDataToSet = NULL; *ctxInitializerToSet = &ContextInitializer; *ctxFinalizerToSet = &ContextFinalizer; } // ExtFinalizer() // // The extension finalizer is called when the runtime unloads the extension. However, it is not always called. void ExtFinalizer(void* extData) { return; } @end |
MailExtension.m is responsible for instantiating the extension and invoking the methods called from ActionScript.
Second part of Objective-C code is MailComposerHelper class which does all the work needed to send the mail.
This class manages MFMailComposeViewController and dispatches events back to ActionScript.
MailComposerHelper implementation looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | // // MailComposerHelper.m // NativeMail iOS extension for Adobe AIR // // Created by Piotr Kościerzyński on 11-11-28. // Copyright (c) 2011 Randori. All rights reserved. // #import "MailComposerHelper.h" @implementation MailComposerHelper static NSString *attachmentPropertySeparator = @"|"; static NSString *attachmentsSeparator = @"----"; //Event name static NSString *event_name = @"MAIL_COMPOSER_EVENT"; -(void) sendMailWithSubject:(NSString *)subject toRecipients:(NSString *)toRecipients ccRecipients:(NSString *)ccRecipients bccRecipients:(NSString *)bccRecipients messageBody:(NSString *)messageBody attachmentsData:(NSString *)attachmentsData { FREDispatchStatusEventAsync(context, (uint8_t*)[event_name UTF8String], (uint8_t*)[@"WILL_SHOW_MAIL_COMPOSER" UTF8String]); MFMailComposeViewController *mailComposer = [[MFMailComposeViewController alloc] init]; mailComposer.mailComposeDelegate = self; if (subject != nil) [mailComposer setSubject: subject]; if (messageBody != nil) [mailComposer setMessageBody:messageBody isHTML:YES]; if (toRecipients != nil && [toRecipients rangeOfString:@"@"].location != NSNotFound) [mailComposer setToRecipients:[toRecipients componentsSeparatedByString:@","]]; if (ccRecipients != nil && [ccRecipients rangeOfString:@"@"].location != NSNotFound) [mailComposer setCcRecipients:[ccRecipients componentsSeparatedByString:@","]]; if (bccRecipients != nil && [bccRecipients rangeOfString:@"@"].location != NSNotFound) [mailComposer setBccRecipients:[bccRecipients componentsSeparatedByString:@","]]; //Add attachments (if any) if (attachmentsData) { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSString *filePath; NSArray *attachmentProperties; NSString *fileName; NSString *fileExtension; NSString *fileSearchSource; NSString *fileMimeType; NSString *fileAttachName; NSArray *attachments = [attachmentsData componentsSeparatedByString:attachmentsSeparator]; for (NSString *attachmentEntry in attachments) { attachmentProperties = [attachmentEntry componentsSeparatedByString:attachmentPropertySeparator]; fileName = [[[attachmentProperties objectAtIndex:0] componentsSeparatedByString:@"."] objectAtIndex:0]; fileExtension = [[[attachmentProperties objectAtIndex:0] componentsSeparatedByString:@"."] objectAtIndex:1]; fileSearchSource = [(NSString *)[attachmentProperties objectAtIndex:1] lowercaseString];//bundle or documents fileMimeType = [attachmentProperties objectAtIndex:2];//mime type of file fileAttachName = [attachmentProperties objectAtIndex:3];//how to name the file //search for file in app bundle if ([fileSearchSource isEqualToString:@"bundle"]) { filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:fileExtension]; } else //search for file in Documents if ([fileSearchSource isEqualToString:@"documents"]) { filePath = [documentsDirectory stringByAppendingPathComponent:(NSString *)[attachmentProperties objectAtIndex:0]]; } else { //ERROR - ignoring continue; } if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { NSData *fileData = [[NSData alloc] initWithContentsOfFile:filePath]; if (fileData) { [mailComposer addAttachmentData: fileData mimeType:fileMimeType fileName:fileAttachName]; } [fileData release]; } } } //show mail composer [[[[UIApplication sharedApplication] keyWindow] rootViewController] presentModalViewController:mailComposer animated:YES]; [mailComposer release]; } // Dismisses the email composition interface when users tap Cancel or Send. -(void) mailComposeController: (MFMailComposeViewController*)controller didFinishWithResult: (MFMailComposeResult)result error:(NSError*)error { NSString *event_info = @""; // Notifies users about errors associated with the interface switch (result) { case MFMailComposeResultCancelled: event_info = @"MAIL_CANCELED"; break; case MFMailComposeResultSaved: event_info = @"MAIL_SAVED"; break; case MFMailComposeResultSent: event_info = @"MAIL_SENT"; break; case MFMailComposeResultFailed: event_info = @"MAIL_FAILED"; break; default: event_info = @"MAIL_UNKNOWN"; break; } FREDispatchStatusEventAsync(context, (uint8_t*)[event_name UTF8String], (uint8_t*)[event_info UTF8String]); FREDispatchStatusEventAsync(context, (uint8_t*)[event_name UTF8String], (uint8_t*)[@"WILL_HIDE_MAIL_COMPOSER" UTF8String]); context = nil; //hide mail composer [[[[UIApplication sharedApplication] keyWindow] rootViewController] dismissModalViewControllerAnimated:YES]; } -(void)setContext:(FREContext)ctx { context = ctx; } @end |
Native iOS code can dispatch events for ActionScript – it’s done by calling FREDispatchStatusEventAsync. This will be seen as a StatusEvent.STATUS in Adobe AIR. I used it to let my application know whether the mail composer can be shown and to inform about the mail compose result and status.
When you build the static library you will get libMailExtension.a file in ‘build’ directory.
This file will be needed in ‘NativeMail-iOS’ library project to create .swc and .ane files.
Creating Adobe Native Extension project
In Flash Builder create a new ActionScript Library project. The project will contain ActionScript classes responsible for calling iOS code.
My project’s structure – you can see the libMailExtension.a file present
Since I used command line tools to create the extension there are lot of configuration files and ANT scripts, if you’re using Flash Builder 4.6 it should be much simpler.
iOS library methods are invoked by calling extensionContext.call method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | package pl.randori.air.nativeextensions.ios { import flash.events.EventDispatcher; import flash.events.IEventDispatcher; import flash.events.StatusEvent; import flash.external.ExtensionContext; /** * An iOS native extension for Adobe AIR 3.1 for sending mail. * Implements MFMailComposeViewController in iOS * * @author Piotr Kościerzyński, piotr@flashsimulations.com * www.flashsimulations.com * www.randori.pl * * */ public class MailExtension extends EventDispatcher { protected var extensionContext:ExtensionContext; private static const EXTENSION_ID : String = "pl.randori.air.nativeextensions.ios.MailExtension"; public function MailExtension(target:IEventDispatcher=null) { super(target); extensionContext = ExtensionContext.createExtensionContext( EXTENSION_ID, null); } /** * @param subject Mail subject * @param messageBody Mail body (can include HTML) * @param toRecipients To: recipients in format: "mail@example.com,mail2@example.com" * @param ccRecipients Cc: recipients in format: "mail@example.com,mail2@example.com" * @param bccRecipients Bcc: recipients in format: "mail@example.com,mail2@example.com" * @param attachmentsData Attachments in format: ['filename|bundle|mimetype|name of file to display in attachment'] * example: ["Default.png|bundle|image/png|Application splash screen.png","Example file.dat|documents|text/xml|A file saved in Adobe AIR iOS app.txt"] */ public function sendMail(subject:String, messageBody:String, toRecipients:String, ccRecipients:String = '', bccRecipients:String = '', attachmentsData:Array = null):void { extensionContext.addEventListener( StatusEvent.STATUS, onStatusEvent); extensionContext.call( "sendMailWithOptions", subject, messageBody, toRecipients, ccRecipients, bccRecipients, attachmentsData); } /** * @private * Handle mail compose result. * When the native mail composer finished an result event will be dispatched. * Event will contain the result information. * */ private function onStatusEvent( event : StatusEvent ) : void { if( event.code == MailExtensionEvent.MAIL_COMPOSER_EVENT) { dispatchEvent( new MailExtensionEvent(event.code, event.level )); } } /** * Can the in-app mail composer be invoked? */ public function isMailComposerAvailable() : Boolean { return extensionContext.call( "isMailComposerAvailable") as Boolean; } /** * Clean up */ public function dispose():void { extensionContext.removeEventListener( StatusEvent.STATUS, onStatusEvent ); extensionContext.dispose(); } } } |
ANT script used for creating .ane and .swc files:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | <?xml version="1.0" encoding="UTF-8"?> <project name="NativeMail iOS for Adobe AIR Extension" default="build-extension" basedir="."> <property file="local.properties" /> <property file="build.properties" /> <target name="clean"> <delete dir="${app.builddir}"/> <delete dir="${app.releasedir}"/> <mkdir dir="${app.builddir}"/> <mkdir dir="${app.releasedir}"/> <delete file="${app.rootdir}/library.swf"/> <delete file="${app.rootdir}/${app.swcfilename}"/> </target> <target name="build-extension" depends="clean"> <exec executable="${ACOMPC}"> <arg line=" -output ${app.builddir}/${app.swcfilename} -load-config+=${app.configfile} +configname=airmobile -swf-version=14 "/> </exec> <copy file="${app.builddir}/${app.swcfilename}" tofile="${app.rootdir}/${app.swcfilename}"/> <unzip src="${app.builddir}/${app.swcfilename}" dest="${app.rootdir}"/> <delete file="${app.rootdir}/catalog.xml"/> <exec executable="${ADT}"> <arg line=" -package -target ane ${app.releasedir}/iOS_MailExtension.ane ${app.extensionxmlfile} -swc ${app.swcfilename} -platform iPhone-ARM library.swf libMailExtension.a -platformoptions ios-platformoptions.xml "/> </exec> <delete file="${app.rootdir}/library.swf"/> <delete file="${app.rootdir}/${app.swcfilename}"/> </target> </project> |
Native extensions ADT parameters
One of the most important parameter in this script is -platformoptions ios-platformoptions.xml . Why?
My extension uses iOS frameworks that are not linked by ADT packager by default, as a result the linker won’t fine MFMailComposeViewController definition and will fail.
Sadly, the documentation is very poor and I haven’t found any examples except from this one Adobe Blogs: iOS5 support for AIR/Using external SDKs to package apps
MFMailComposeViewController requires MessageUI.framework so adding it to ADT’s linker solves the problem.
Also, note that -swf-version is set to 14 because we’re targeting for AIR 3.1. See: Adobe Docs: Building the ActionScript library of a native extension
ios-platformoptions.xml looks like this:
1 2 3 4 5 6 7 | <platform xmlns="http://ns.adobe.com/air/extension/3.1"> <sdkVersion>5.0</sdkVersion> <linkerOptions> <!-- to use the MessageUI framework --> <option>-framework MessageUI</option> </linkerOptions> </platform> |
Another new parameter is extensions.xml file. It holds information about native libraries we want to include. Here we define the id of extension and the names of extension’s initializer and finalizer methods. See that the names of functions in <initializer> and finalizer match the names of functions in MailExtension.m
Here’s extension.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 | <extension xmlns="http://ns.adobe.com/air/extension/3.1"> <id>pl.randori.air.nativeextensions.ios.MailExtension</id> <versionNumber>1.0.0</versionNumber> <platforms> <platform name="iPhone-ARM"> <applicationDeployment> <nativeLibrary>libMailExtension.a</nativeLibrary> <initializer>ExtInitializer</initializer> <finalizer>ExtFinalizer</finalizer> </applicationDeployment> </platform> </platforms> </extension> |
After running ANT script we will have two new files: NativeMail_iOS.swc in ‘build’ directory and iOS_MailExtension.ane in ‘release’ directory.
iOS_MailExtension.ane is the AIR native extension we wanted to create.
Example – using native extension in Adobe AIR application
‘Can I send mail?’ button calls MailExtension.isMailComposerAvailable() method which returns true / false.
This method will return false if iOS mail client hasn’t been properly configured. This check is also done before sending the mail to prevent the app from crashing.
‘Send mail’ button calls MailExtension.sendMail method. In the example all available mail fields are filled and two attachments are added.
Invoked mail composer view
Example app project structure:
Copy ‘iOS_MailExtension.ane’ file to ‘extensions’ directory.
One last thing left to is to add the following information you you application descriptor file. extensionID has to match our extension’s id. Otherwise ADT will fail to package the app.
1 2 3 | <extensions> <extensionID>pl.randori.air.nativeextensions.ios.MailExtension</extensionID> </extensions> |
Packaging application is done by following ANT script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | <?xml version="1.0" encoding="UTF-8"?> <project name="NativeMail iOS extension for Adobe AIR example app" default="publish-ios" basedir="."> <property file="local.properties" /> <property file="build.properties" /> <target name="clean"> <delete dir="${app.builddir}"/> <delete dir="${app.releasedir}"/> <mkdir dir="${app.builddir}"/> <mkdir dir="${app.releasedir}"/> </target> <target name="copy" depends="clean"> <copy todir="${app.builddir}" preservelastmodified="true" verbose="true"> <fileset dir="${app.sourcedir}"> <patternset> <include name="assets/**"/> <include name="*.png"/> <include name="*.xml"/> <include name="*.ane"/> </patternset> </fileset> </copy> </target> <target name="compile" depends="copy"> <exec executable="${MXMLC}"> <arg line=" +configname=airmobile -output ${app.builddir}/${build.swfname} ${app.sourcedir}/NativeMail.mxml -source-path+=${app.rootdir}/src -load-config+=NativeMail_app.config -swf-version=14 "/> </exec> </target> <target name="publish-ios" depends="compile"> <copy file="${app.builddir}/${build.swfname}" tofile="${app.rootdir}/${build.swfname}"/> <copy file="${app.sourcedir}/NativeMail-app.xml" tofile="${app.rootdir}/NativeMail-app.xml"/> <exec executable="${ADT}"> <arg line="-package -target ipa-test-interpreter -provisioning-profile ${build.mobileprofile} -storetype ${build.storetype} -keystore ${build.keystore} -storepass YOUR_PASSWORD ${app.releasedir}/${build.name} ${app.descriptor} ${build.swfname} -extdir extensions -C ${app.builddir} Default.png Default@2x.png"/> </exec> </target> </project> |
Notice ‘-swf-version=14′ and ‘-extdir extensions’ parameters
That’s it.
Worth reading:
Adobe Blogs: iOS5 support for AIR/Using external SDKs to package apps
MFMailComposeViewController Class Reference
Adobe Docs: Building the ActionScript library of a native extension
Native Alert iOS native extension tutorial
AIR Native Extension Example: iBattery for iOS
as3c2dm – AIR native extension to push notifications with C2DM






Piotr, this is fantastic. Was just scouring around trying to find a way to shove a file into an email on iOS and here it is. Thanks!
The FlashBuilder with the extension example looks great !
Do you have an example on how to integrate the email extension, with a simple Actionscript project ?
Great to hear you find this example useful. To use mail extension in AS project create an ActionScript Mobile project in Flash Builder / Flash Developer and pack the application using ADT with additional parameters (extdir).
Great stuf!!
I love youre work. More of that pleas…. Just one but… On the ipad it turns the app to landscape mode ignoring the position. For me its something great but for the user: ” why its doing that..”
Ironically, I have the opposite behavior Mateus is seeing. My app is a landscape only application and the mail composer opens in portrait. Not a showstopper for me, but something that would be great to see in an update.
You could extend MFMailComposeViewController and implement shouldAutorotateToInterfaceOrientation method to override default behaviour.
This way you could make the mail composer always show in portrait or landscape mode.
I think that the behaviour you both encountered might be related to AIR itself since my component doesn’t implement any orientation handling – MFMailComposeViewController is presented modally and that’s it.
Anyway, on iPhone 3GS the extension works as expected, adjusting to device orientation.
I’ll have to give it a shot.
I also noticed that once the MailComposer opens in portrait on the iPad, anything that is mirrored to an external display is stuck in portrait for the life of that application (must be killed in the multitasking menu) including the AIR interface despite the application being set to landscape and appearing as landscape on the iPad itself.
Thanks again for writing this!
This post is really, really helpful. Thank you for going to such lengths to explain the process and post your code.
I have successfully integrated your extension in an iPad project. I am also running into the orientation issues mentioned above. The only configuration that worked reliable seems to be Portrait mode with Auto-orientation turned off. Obviously not a solution for every case but hopefully a fix can be put in place.
Thank you again, and keep up the good work.
I am having orientation issues as well on an iphone app. My mobile application orients to landscape after the composer view closes.
Did you modify the actionscript or the objective c to keep it from rotating.
Thanks a lot for the great plugin. I tried using it in my flex app it works great. One issue though is that when i attach files if the file is too big say 5 Mb, the application crashes. In console it says application received low memory warning. Is There aby way to handle this or workaround ?
I got memory warning notice even when showing the composer with no attachments – it’s not an exception that causes application crash.
Try stopping apps running in the background and then run you app, see if the crash still occurs.
It’s possible that the OS might kill the application as a result of high memory usage, but it’s very unlikely.
Probably there’s a memory leak in the Objective-C or unhandled exception, but I’d have to do some tests to be 100% sure.
I’m busy right now, so I can’t help you with this at the moment.
If you have Xcode go to Organizer -> Devices and see the Device logs – there should be a crash report that will be more helpful with the debugging.
One thing is I dont care about the events returned by the native extension. So I’m just ignoring them. Do you think that might be the issue ?
You don’t have to listen to any on those events in general. The only event that might be relevant is composer did disappear. After this event you know that the native view has been removed from the view stack and memory used by this component has been released back to the iOS.
Piotr ,
Thanks for your prompt replies. Writing new comment as lots of replies was making the typing space too cramped up. I was not able to figure out yet. Let me know in case you are able to test it. Maybe try attaching a big file > 4M in the app and check. What i Observed is that the app receives low memory warning and then if you try to do anything in the mail composer for instance entering mail address, or if you press done etc. it crashes in my case its giving segmentation fault.
Also one more interesting thing is this was working fine until IOS on device was 4.* … the unfortunate moment I updated to ios 5.* … it started throwing this issues.
Thanks.
Thanks for the info, I’ll look into that.
Looks like there’s a problem with iOS 5 autoreleasing objects and hence the crash.
I tested it on 4.3.5 and it didn’t crash.
It seems the issue is with opening image files greater than some size. I was able to attach a 11Mb pdf without crashing or memory errors.
So as a work around what i do now is I scale down the image and compress it and it has no issues.
Few more Observations:
So what I observed is that in 4.* IOS the image file used to attach as file (no preview) but in 5.* onwards its puts the image preview there, so when this happens I profiled my app it shoots up by 40 Mb or so even though image is only 5 Mb. Now with scaled down and compressed image (its less than 500k) there are not issues with emailing the file.
Hi Piotr,
Thanks for contributing this plugin and very informative blog entry! It was a great help for me in understanding how to build native extensions. I currently have a need for an extension to compose SMS messages (with a pre-populated body) and was wondering if you came across any of these yet – seems like a common need.
You can modify my extension. Just follow this tutorial http://iphonedevelopertips.com/core-services/how-to-send-an-sms-progammatically.html
Hi,
Is it possible to create an ANE with the SMS functionality, that can easily be used in the AIR project? I have tried to modify your extension, but I somehow got lost in the process. I just cannot manage to do it…
Any native iOS API can be used in mobile Adobe AIR application, SMS is not an exception. It’s not that diffucult to create SMS support, I’m sure you’re on the right way. Good luck!
Hi Pieter, did you ever build this extension?
Hi,
This is Manoj. Need to develop an Alarm application for iphone and android. I was researching with google and Adobe site and found we have to use Local Notification class to do an Alarm App. But Flex 4.5.1 is not supporting native functions and herd adobe has launched 4.6 which support native function and i converted to 4.6. Now the same problem here also, there is no default classes and get an example script from Adobe Blog itself which has the native Local Notification Class. But there they used just very simple method and used button click to dispatch the notification event. I searched with Apple SDK there they used an method and class called Scheduler to schedule an event and triggers then by automatic at particular time. But with flex i don’t see that method. Could you please advice me can we do this with Flex and how? if possible with an example.
Hi, you need a custom Objective-C native extension it can’t be done directly in Flex. Follow the tutorials, use Adobe’s code as a base for your extension and add the stuff that’s missing.
Is it possible to compile with this extension from the command line in Flash Professional CS5.5? I’ve compiled with other ane files before but this one appears to not work when I follow the procedure that works for those. Any advice is appreciated.
If you are compiling under Windows then it won’t work.
Why should this native extension don’t work in flash 5.5 (windows)? each other ane works by the adt command line, why not?
Would someone prepare an example for me? I would pay for the work, because i need this function..
Just to let you know: the app crashes if the mail app has already a email-draft open.
Hi,
I want to trace the background services running in the mobile using my app. Is it possible to do using native extensions. Could you please advice me can we do this with Flex and how? if possible with an example.
It’s not possible.
Pingback: Adobe Native Extension for iOS Game Center – Part 1 « David Flatley
When sending an attachment, are “bundle” and “document” the only options for the second parameter in the array? Essentially, I’m creating a PDF in my app and adding it to a temporary directory that is cleaned up on app exit. I’m sure that I can have it write to documents directory, and manually clean it up after mail is sent, but there may be other instances that I don’t want to do that.
Thanks.
FYI my array is created as follows, so if it is merely a matter of syntax being incorrect:
var attach:Array = new Array (file.name + “|” + dataObj['tempDir'].nativePath + “pdf|application/pdf|Project Summary.pdf”);
Any solution to this yet?
any chance this works on android?
This ANE is for iOS only.
hi guys. first of all great job!!!
does anyone fix mirror and orientation bugs? I think all will become wonder if someone will share solution.
Great post. I have been using it for a few months now. But did the iOS 5.1 update break it? My app crashed now after being updated to 5.1.
I haven’t tested it on iOS 5.1, I’ll check this issue and post an update.
Well I haven’t used it since that time – but I’ve seen that some guys fixed the rotation issue and a few other issues were found – I’ll prepare an upgrade when I have to free time.
Hi,
Thanks for the extension.
I tried to use this ” In-app mail composer” extension with other extension but when i tried to compile my project it is giving me error “Multiple ANEs with the same Initializer ExtInitializer are being packaged”.
Can you have any idea why i m getting this?
Thanks.
Hi,
It’s working here on iOS 5.1 – but I do have the Orientation issue.. Any chance someone would have patched or fixed that?
Regards,
D.
Pingback: » Native extensions for mobile AIR apps – getting round the orientation issue DiaDraw Blog
Hi Piotr,
Thank you very much for this example!
I spent a couple of weeks trying to find a fix for the orientation issue and finally found a workaround:
- subclass MFMailComposeViewController and override shouldAutorotateToInterfaceOrientation (which you already suggested);
- add a category to UIViewController and implement orientation methods;
- add an event listener for StageOrientationEvent.ORIENTATION_CHANGING in Actionscript to prevent resizing of the stage, which can occur even if the orientation doesn’t change.
I’ve put details and example code (tested on iOS 5.1 and 4.3.3) in this blog post: http://blog.diadraw.com/native-extensions-for-mobile-air-apps-getting-round-the-orientation-issue/
Hi and very good job/tutorial. But there are some problems in my version:
I’m using Mac and Flash5.5/Air3.2 and with this prompt:
“adt -package -target ipa-test-interpreter -storetype pkcs12 -keystore Zertifikate.p12 -storepass xxxxxx -provisioning-profile dev.mobileprovision testEmailANE.ipa testEmailANE-app.xml testEmailANE.swf Default@2x.png Default.png -extdir email”
i get this error message:
“/email/iOS_MailExtension.ane is not a valid ANE file.”
following questions:
- do i have to add this command “-platformoptions ios-platformoptions.xml” at creating ipa file or only at creating ane?
- can i use it in air 3.2 or just in 3.1?
Could you upload a .fla and .swf file example?
Thank you very much!
This is helps me!
Awesome post! I just want to verify. This will not work with the camera roll, right?
Even attachmentsData parameter of the sendMail function is optional, application crashes if this parameter is not set. I also tried to call with an empty array but still not working. You should attach at least one file.
Note: I tried with latest flash builder 4.6.0 and air 3.3 beta sdk
Hi,
your blog is very helpful I implemented and it works for me.
But I am facing a issue when I use this extension along with other extension eg:flurry.
when I compile it in Terminal it gives me the below error:
ld: warning: -dead_strip with lazy loaded static (library) archives has resulted in a duplicate symbol. You can change your source code to rename symbols to avoid the collision. This will be an error in a future linker.
ld: warning: duplicate symbol _ContextInitializer originally in /var/folders/63/63BlUx7sHbiI3-DZvMNrM++++TI/-Tmp-/8884d339-ba21-423e-82f6-bd9fcf428556/libpl.randori.air.nativeextensions.ios.MailExtension.a(MailExtension.o) now lazily loaded from /var/folders/63/63BlUx7sHbiI3-DZvMNrM++++TI/-Tmp-/8884d339-ba21-423e-82f6-bd9fcf428556/libcom.sticksports.nativeExtensions.Flurry.a(FlurryIosExtension.o)
ld: warning: duplicate symbol _ContextFinalizer originally in /var/folders/63/63BlUx7sHbiI3-DZvMNrM++++TI/-Tmp-/8884d339-ba21-423e-82f6-bd9fcf428556/libpl.randori.air.nativeextensions.ios.MailExtension.a(MailExtension.o) now lazily loaded from /var/folders/63/63BlUx7sHbiI3-DZvMNrM++++TI/-Tmp-/8884d339-ba21-423e-82f6-bd9fcf428556/libcom.sticksports.nativeExtensions.Flurry.a(FlurryIosExtension.o)
I am using AIR 3.1, iPhoneOS5.0.sdk, Flash CS5.5
Can you plz plz help me on this I am under immense pressure to get this working.
Hi,
Thank you very much your blog is very helpful.
Also had problem with using 2 extensions which is resolved by changing the names of initializer and finalizer.
Is this still active?
Hello,
I’m a newbie with XCode and I’m trying harder to play a video file with iOS native player in consumption of use of the Native Extension Flex AIR. But no idea I’m failing again and again. Can I ask you if you can suggest me with some code XCode that I can popup the native video player through the main Object C class?
It’d be very helpful. Please suggest.
Thank you.
hey, I can’t seem to get it saving an image to the app storage dir, and haven’t found any documentation for it. Is it possible to write a jpg to the phone and then attach it to the email?
this is what i am trying right now, but it doesn’t look like it is creating the file. Or at least it isn’t attaching it to the message
var f:File = File.applicationStorageDirectory.resolvePath(“attachment.jpg”);
var fs:FileStream = new FileStream();
fs.open(f, FileMode.WRITE);
fs.writeBytes(data);
fs.close();
var attach:Array = new Array (f.name + “|bundle|image/jpeg|MyNoodleArt.jpg”);
var mailExtension:MailExtension = new MailExtension();
mailExtension.sendMail(ARG_subject, “”+ARG_body+”", “”, ”, ”, attach);
I’m getting Main Thread (Suspended: VerifyError: Error #1014: Class pl.randori.air.nativeextensions.ios::MailExtension could not be found.)
I added the ANE and the SWC and also added the
pl.randori.air.nativeextensions.ios.MailExtension
I’m getting this error on the sample project.
Any help?
I’ve been trying everything to get this working on Windows. It now works with Air 3.4 Beta!
Thankyou!
hey so i know it was awhile ago, but how did you integrate it? i’ve been tryin for a couple of days now and im finding it difficult. is there any way you could explain it in the flex source view?, because im having difficulty following this tut.
thanks,
spencer
Flash Builder 4.6
xcode 4.2