Friday, January 25, 2013

Android: Invoking a URL Scheme Intent from a WebView

A few days back, I was working on a project where one of our PhoneGap Android apps needed to invoke another application using the custom URI scheme.

We were able to invoke the other app, when clicking on a link from the mobile browser, but the same did not work from our app.

We figured out that the issue was with the Webview as it was not able to recognize the custom URI.

Solution: After a lot of research, I was able to get to a fix.

The Android Webview component allows using a WebView client. The Webview client exposes a method and I just overrided it as follows: public boolean shouldOverrideUrlLoading(WebView view, String url)
{
                if(url.toLowerCase().startsWith("http") || url.toLowerCase().startsWith("https") || url.toLowerCase().startsWith("file"))
                {
                                view.loadUrl(url);
                }
                else
                {
                                try
                                {
                                                Uri uri = Uri.parse(url);
                                                Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                                                                startActivity(intent);
                                }
                                catch (Exception e)
                                {
                                                Log.d("JSLogs", "Webview Error:" + e.getMessage());;
                                }
                }
    return (true);
}

That's it, once you configure the code as above, any URL scheme inside the HTML page would work like a charm. What I simply did was to tell the Webview that if the URL is HTTP, HTTPS or FILE, you handle it, otherwise fire an intent and let the OS handle the same. Simple, yet no one blog has this solution clearly articulated, hence this post.

AIR Android Native Extensions: Part 7: Launching AIR's main activity from other Android activities and passing information

In my previous post, I covered, Preventing Android activity from being recreated on orientation change and relaunch.

In this post, the last of this series I would explain how to invoke your AIR application from other activities and passing data to the same. This is not about using custom URI, but about using Intent to invoke the AIR activity.

The first question that you may have is "What is AIR activity?". When you compile your Flex project into an APK, AIR automatically generates a new activity named AppEntry. This activity class is the single Android activity for all your Flex App. What I am trying to say here is that unlike Android native apps where the application is divided into multiple activities, AIR apps have just one activity i.e. AppEntry.

The package for this AppEntry class is your application's id, that you define in yourproject-app.xml. However, please note that whenever you compile your AIR app for Android, AIR prepends an "air." to the application id. So, if you have defined your app's id as "com.techie.pulkit", APK would get the id as "air.com.techie.pulkit". So, this is what the package for AppEntry class is, so the fully qualified classname for the AppEntry class, in this case, becomes "air.com.techie.pulkit.AppEntry".

Now that we know that our Air application also has an activity, how do I invoke it from my extension and pass data to it? Obviously, you could use FREContext.dispatchStatusEventAsync to dispatch events, but here we would use the Android Intent class to invoke the AIR activity and send data. Let's see how.

Activity myAIRActivity = myFREContext.getActivity();
notificationIntent = new Intent(myAIRActivity, myAIRActivity.getClass());
notificationIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
notificationIntent.setData(Uri.parse("any_data_you_want_to_send"));
startActivity(notificationIntent);

The code above would allow you to invoke your AIR activity and pass the data to it. Let's go over each statement to understand better.
Activity myAIRActivity = myFREContext.getActivity();
Here, we get the reference to the AppEntry activity with the help of our extension's FREContext.
notificationIntent = new Intent(myAIRActivity, myAIRActivity.getClass());
Here, we create a new Intent using the activity's context and the activity's class.
notificationIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
This one is important, read it carefully. Android Intents by default create a new instance of the activity to be started, however, we don't want that. So, we add a flag, which tells the intent not to create a new instance of the activity, but look for an existing instance and simply reorder it to front. Now, to the next step.
notificationIntent.setData(Uri.parse("any_data_you_want_to_send"))
This is where we send the data to the AIR activity. Simply parse the data as a URI and send it across. Later in the post, we'd see how to receive this data in AIR.
startActivity(notificationIntent);
Finally, simply start the activity.

Now that we are done with the Android part, let's see how to receive this data in your Flex project.

First, add a listener to the event:
NativeApplication.nativeApplication.addEventListener(InvokeEvent.INVOKE, appInvokeHandler);
Now, listen to the event like this:
private function appInvokeHandler(event:InvokeEvent):void
  {
    trace("appInvokeHandler ::: ::::: "+event.arguments.length);
  }
That's it, if the event's arguments' length > 0, you have received the data.

So, with this, I conclude this post and the series. Hope you learnt something in the process and resolved some of the issues with AIR and Android ANEs. If you'd like to go through the other posts, this is the bast place to start:
AIR Android Native Extensions - Issues and Solutions

I would try to keep sharing my findings in the field of technology as I keep getting my feet wetter. You can drop in a note to me: pulkit.gupta@gmail.com

AIR Android Native Extensions: Part 6: Preventing Android activity from being recreated on orientation change and relaunch.

In my previous post, I explained how to package XHDPI and XLarge resources with the AIR app. In this post, I would cover how to prevent Android activities from being recreated on orientation change or resumption of app.

In the process, we'd see how to compile an AIR android app with a different Android SDK.

First, let's understand what the issue is. There are a lot of times, where you need to create native android activities. For example, if you want to use the Android WebView component to launch the web pages in your app, you'd need to create a new activity for the same.

By default, Android recreates the activity on various configuration changes, such as SoftKeyBoard open or Orientation change. To prevent the recreation of activity, we used to add the android:configChanges property to activity's declaration in the manifest:

<activity android:name="com.webview.Webview"
android:configChanges="keyboardHidden|orientation" >
</activity>

As you can notice above, we have added an attribute android:configChanges and the values for it are keyboardHidden|orientation. This much was sufficient to prevent the activity from getting recreated in Android below 3.2. However, these values alone are unable to prevent the activity from being killed in Android 3.2 and above.

Problem: Android introduced a new configuration property "screenSize" in Android 3.2, hence you need to add this as well as in the android:configChanges attribute like this:

<activity android:name="com.webview.Webview"
android:configChanges="keyboardHidden|orientation|screenSize" >
</activity>

This would tell Android to not recreate the activity on screenSize changes. However, you won't be able to use this property with the default android SDK AIR uses as it would not recognize this property and throw an error.

Solution: Updating Android SDK. No, the platformsdk compiler argument, as explained in my previous post, would not help here.

So, we'd need to replace the Android SDK in the AIR SDK's library folder. The old Android SDK JAR is at the following location:
{AIR_SDK_FOLDER}\lib\android\lib\resources\android-res.jar


You'd need to copy the JAR file from the android SDK folder, rename it as "android-res.jar" and paste it at the location above, replacing the old one. The Android SDK JAR, for say, Android 4.0, would be present at "platforms/android-15/android.jar". You can download the SDK for a particular version from the android website.

Once the above is done, you'd be able to successfully package the application with screenSize attribute added to the activity declaration.

Unfortunately, even after doing this, the Android activity may get recreated on resume on certain devices. Fortunately, adding a few lines to your manifest can fix this, see below:

<android>
<colorDepth>16bit</colorDepth>
<manifestAdditions><![CDATA[
<manifest android:installLocation="auto">
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" />
<application android:enabled="true">
<activity android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity> </application>
</manifest>
]]>
</manifestAdditions>
</android>

As you'd notice above, I've added an activity node under the application node and assigned it a property android:launchMode="singleTop". This is what does the trick. The main thing is the launchMode, that we have set to singleTop. But, to set that we'd need to add the lines emboldened as well.

Well, this would ensure that your activity would never recreate unless you want it to. Hope this helps someone banging head against the wall, trying to figure out what to do. Like what I was doing sometime back, till I figured this out.

Do keep watching this space for my last article in this series: Launching AIR's main activity from other Android activities and passing information.

AIR Android Native Extensions: Part 5: Exporting xLarge and xHDPI resources with the AIR application

In my previous post, I explained how to use 3rd party JARs in your app. In this post, I am going to cover how to bundle xLarge resources and xHDPI assets with the AIR app.

On the higher version of Android tablets, you may not be able to get the correct UI by using standard sizes and may need to use xLarge and xHDPI values. However, when you put a xHDPI asset or xLarge resource in your res folder, you'd be able to package the ANE but when you compile the final AIR application, it would throw errors saying that these are not supported.

Reason: AIR, by default uses Android 2.2 SDK and xLarge and xHDPI were introduced in Android SDK 3.2. So, the Android SDK 2.2 doesn't recognize them.

Solution: We'd need to compile the app with an Android SDK on and above 3.2. Question is How?

There are 2 ways to update the Android SDK uses to compile AIR:
  • Using the -platformsdk compiler argument
  • Replacing the Andoid JAR in the AIR SDK

Well, sadly it is not an either/or solution. You may need to follow both the steps for different problems. For packaging xHDPI and xLarge resources, you'd need to follow step 1.

Firstly, make sure you have the Android SDK folder on your machine in the following structure:

  - android_sdk_folder
      - platform
      - platform-tools
      - tools
      - add-ons
      - AVD Manager
      - SDK Manager

Now ensure that inside the android_sdk_folder/platforms folder, you have the correct platform available i.e. if you want to use Android 4.0, there should be a folder named android-15 inside the platforms folder. You can Google for the Android SDK to API version mapping.

So, now that you have the correct SDK, what do you specify in the platformsdk? Suppose the android_sdk_folder, I spoke about is in root of d:/. The path that would go in platformsdk is d:/android_sdk_folder. Read this carefully:, you would not give the path to d:/android_sdk_folder/platform/android-15, but only till the d:/android_sdk_folder. Now, you must be wondering, how AIR would know which Android SDK to use, since there may be multiple SDKs inside the platforms folder. This is where your application's app.xml would come into play.

Open your application's app XML i.e. myproject-app.xml. You'd need to add the following line to this file:
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" />

This implies that the minimum API version that the app supports is "8" i.e. Android 2.2 but you want to use API version 15 i.e. Android 4 to package the build. Even after adding this line, you can be rest assured that your app would work in the older android versions as well.

Your App XML's section would look something like follows:

<android>
<colorDepth>16bit</colorDepth>
<manifestAdditions><![CDATA[
<manifest android:installLocation="auto">
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" />
<application android:enabled="true"></application>
</manifest>
]]>
</manifestAdditions>
</android>

Now, this is how, Android identifies which version of android SDK to use. So, that's it, now you'd be successfully be able to compile your AIR app with xLarge and xDPI resources.

We did not discuss the usage of Step 2 i.e. Replacing the Andoid JAR in the AIR SDK. I have covered it in the next post of the series: Preventing Android activity from being recreated on orientation change and relaunch.

AIR Android Native Extensions: Part 4: Using Third Party Libraries (Jar) with your extensions

In my previous post, I explained how to speed up access of resources in an Android ANE.

In this post, I would discuss how to bundle 3rd party JAR library code along with your ANE. There are numerous instances where we need to use JAR files in our code, for example to support an older platform compatible version for an android component, we'd need to use android-support JAR. However, when you export the JAR for your ANE, the code of the other JAR files doesn't go along. And, AIR's ADT command supports only one JAR per ANE? You are stuck because the code will not run without the other JARs.

You can use the source code instead of the JAR. However, not all 3rd party libraries would provide the source code.

Solution: Merge all your JARs into one and package the merged JAR with the ANE. Simple, you can use the following ANT script to achieve it:

<?xml version="1.0" encoding="UTF-8"?>
<project name="OpenPageMobile" default="combine-jars" basedir=".">
<target name="combine-jars">
<mkdir dir="output"/>
<unzip dest="output">
<fileset dir="jars">
<include name="**/*.jar"/>
</fileset>
</unzip>
<delete dir="output/META-INF"/>
<jar destfile="final.jar" basedir="output" />
<delete dir="output"/> 
<!--<delete dir="output"/> -->
</target>
</project>

So, what does this ANT script do, it takes up all the JARS in the jars directory, extract them, removes the META-INF file and repackage them as a single JAR in the output folder. It's important to remove the META-INF file as it corrupts the merged JAR.

You can now package this merged JAR with your ANE and all would work fine.

Quick solution, but may save you some valuable time.

The next in the blog series is: Exporting xLarge assets with the APK.

AIR Android Native Extensions: Part 3: Speeding up access of resources in Native extensions

In my previous post of this series, I explained how to access resources inside an native extension for Android. In this post, I am going to explain ways to speed up access of resources. In most scenarios, you may not need it at all, but if you are using really heavy Android UI components in your extension, like the Calendar component, you are at the right place.

I had an issue where I used a calendar component in one of my native extensions. When I ran that activity natively, it took 1 second to show up, but running the same thing in native took around 9 seconds. That's huge!!!

After a lot of research, I figured out that the issue was with Context.getResourceId calls. AIR was internally running a long loop, which used to take time accessing each resource. Calendar component had 31 cells (item renderers) and each item renderer had 9 calls to Context.getResourceId. AIR doesn't maintain a dictionary of the resources once fetched, which causes it to run the loop again and again.

As a quick fix, I maintained a dictionary of resources once fetched and it brought the load time of the activity down to 3 seconds. On loading the activity again, the time was down to 1 second only.

It was still not the ideal situation. This is where a colleague of mine, a Java bond, came to rescue. The next few lines of code are sheer magic and bring the activity load time exactly the same as native, even when launched first time. Checkout the magic:

public static int getResourceId(String resourceString)
{
    packageName = getActivity().getPackageName()+".R$";
    String[] arr = new String[2];
    arr = resourceString.split("\\.");
    Class someObject = Class.forName(packageName+arr[0]);
    Field someField = someObject.getField(arr[1]);
    return someField.getInt(new Integer(0));
}

That's it done. Magic, ain't it? Just call this function to get resource id instead of using extensionContext.

Someone please pass this code on to Adobe guys, I'd give you the name of my colleague for the credits section ;)

Hope this helps and saves your life at a time where you feel completely helpless. The next one in the series, that I have posted is: Using Third party libraries (JARs) in your android code.

P Android Native Extensions Part 2: Accessing R file variables from native extensions

In the previous post, I covered how to bundle the resources with the ANE. In this post I would cover how to access them from the native code, when it runs with the native extension.

Let us understand the issue first. When creating Android native applications or libraries, we use several resources such as images, layout XMLs, ids, strings. At the time of compilation Android generates an R.java file which provides the native code access to these resources. Examples:

R.id.myLayout
R.layout.mywebviewlayout
R.strings.close_button

This works perfectly fine when you run the app natively. However, when we run this code as an extension from the AIR application, the app crashes. A look at the DDMS log would tell you that a null pointer exception occurred at the place you were accessing the R file.

So, what is happening here? Let me explain.

When you compile an Android application, Android runs through all the resources used by the app and assigns them a unique integer id. If you open the R.java file from the gen folder of your app, you'd find something like this:
public static final class drawable
{
    public static final int ic_action_search=0x7f020000;
    public static final int ic_launcher=0x7f020001;
}
public static final class id
{
    public static final int menu_settings=0x7f070001;
    public static final int openwebview=0x7f070000;
}
public static final class layout
{
    public static final int activity_main=0x7f030000;
    public static final int rla_activity_main=0x7f030001;
}

Now, when you exported this code as a JAR, everywhere in the code where the R class was used, the references were replaced by their values.

Take a look at the following code:

    setContentView(R.layout.rla_activity_main);

In your JAR, the code would have compiled like this:

    setContentView(2130903041);

So, R.layout.rla_activity_main has been replaced with it's int value 2130903041

Now, what does AIR do that we don't find this resource, even if it is bundled with the application? Let us see.

Your AIR application has it's own resources plus it would have resources from your native extension and any other native extensions that you use. So, when compiling the final APK, AIR generates it's own R file. The Android SDK would once again loop through all the resources collectively and assign unique integer ID to each of them. Let's have a look at the same values defined in AIR's R.java file:

public static final class drawable
{
    public static final int ic_action_search=2130968576;
    public static final int op_action_some=213137980;
    public static final int ic_launcher=20979292;
}
public static final class id
{
    public static final int menu_settings=2131296275;
    public static final int openwebview=2131296260;
}
public static final class layout
{
    public static final int activity_main=2130837582;
    public static final int rla_activity_main=2130837585;
    public static final int someother_activity_two=2476689229;
}

As you can see, the IDs assigned to each item have changed. And this is where the issue is. Your native code is :
  setContentView(2130903041);

So, it is looking for rla_activity_main with the id 2130903041, however, AIR has assigned it a different id i.e. 2130837585. So, when you run the extension within the AIR app, it tries to find for the resource with if 2130837585, which is not present, hence the null exception.

Solution: AIR to rescue. The FREContext object that we use in our extensions exposes a function named getResourceId("resource_name"). This function would always give you the correct IDs mapped with the AIR's R file.

So, instead of using:
    setContentView(R.layout.rla_activity_main);

use:
     setContentView(extensionContext.getResourceId("layout.rla_activity_main"));

You can follow the same approach for all the resources,

For colors, use extensionContext.getResourceId("color.colorName") For id, use extensionContext.getResourceId("id.my_resource_id")

Also, do note that the extensionContext object is the extension's FREContext and not Android application context. Although, this works without fail, but at times accessing resources via extensionContext can slow down the application majorly. I would cover ways to speed up access to Android resources through the extension contxt. Stay tuned.

Hope this would save someone a few hours of googling atleast. Thanks and do check out the next article in the series. AIR Android Native Extensions: Part 3: Speeding up access of resources in Native extensions

Thursday, January 24, 2013

AIR Android Native Extensions - Issues and Solutions

In the last few months, I have worked on a lot of native extensions for AIR applications, both Android and iOS. I faced a lot of issues and there was little help available on the web and even if it was available, it was all scattered and not easy to understand.

In this post, I would focus primarily on the issues that I faced when creating Android native extensions. The issues range from trying to bundle resources into a native extension to accessing resources and so on. Some of the issues would have been real show stoppers if they were not resolved. The idea is to share my experience to help others save time.

I assume that the readers are familiar with basics of native extensions and are versed with compiling ANEs.

Let me first list down the issues I am going to talk about in this series of blogs. Series, not to make a single post too lengthy.
  1. Exporting assets / resources with the extension
  2. Accessing R file variables from an extension
  3. Speeding up access of resources in Native extensions
  4. Using third party JARs in your native code
  5. Exporting xLarge assets with the APK
  6. Preventing Android activity from being recreated on orientation change and relaunch
  7. Launching AIR's main activity from other Android activities and passing information


Exporting assets / resources with the extension and common errors
When we create a native extension for Android which contains a view, we generally use layout XMLs to design the layout and some images for buttons and icons and these files are placed in the res folder. By default, you'd tend to export these files along with the JAR and create the ANE. However, the final APK doesn't have any of these assets and your application would crash

To bundle the files along with the APK, we do not need to export the res folder along with the JAR. However, to bundle it with the ANE, we need to copy it separately alongside the JAR and make some change in the way you compile the ANE.

I generally follow the folder structure as follows:
[build]
  extension.xml
  extension.swc
  compile.bat
  [platform]
    [android-arm]
      res
      library.swf
      extension.jar

As you'd notice above, I have created an android-arm folder which has the res folder. The res folder contains the resources for the native extensions. We can name the folder anything, but it's important to keep the three items i.e. res folder, library.swf and extension.jar.
Once we've prepared the folder structure, you can execute a command line script as follows to compile the extension:

adt -package -target ane extension.ane extension.xml -swc extension.swc -platform Android-ARM -C ./platform/Android-ARM/ .

Note: Please note the space and dot (" ." ). It is supposed to be like this only. You can add iPhone platform in the similar way. However, the XIBs in iPhone need to be compiled first.

Doing the above would ensure the resources are bundled with the app. However, please ensure that the you keep unique names for your resource files so that they don't conflict with resources from other extensions. This is essential, otherwise AIR SDK would give errors when compiling when the final app. for example, by default when you create an eclipse project, it creates a file "strings.xml" in res/values folder. Rename the file to "yourproject-strings.xml" or anything unique you can think of. Similarly, the variable names inside the resource files must also be unique. So, if extension 1 defines are resource with id "webview", extension 2 should not use the same name, else you won't be able to compile the final APK. I hope this turns out to be useful for some. Do check out Part 2:Accessing R file variables from an extension.


Monday, June 18, 2012

HTML5, Flex or Native?


We are in a world which is moving at a very rapid pace and it's important that we move along.

The world is going mobile and most of the companies want to extend their offerings to mobile platform. For any developer or organization, the first question that comes to mind is which technology to adopt. When I started, I had heard of three HTML5, Flash and Native (Java for Android and Objective C for iOS), of course there are others like Corona and Titanium.

When I started, I also faced this difficult choice. However, the decision was made easier by the fact that Flash / Flex was not supported on iOS and I had not worked on Objective C at all so HTML5 was the easier way out, more so, considering the fact that our product was going to be a cross platform app.

HTML5
HTML5 is cool and it's easy to develop things once you get the hang of it. With amazing libraries like Backbone JS, Sencha, Jquery and others, it's fun to develop with HTML5. For the APIs like Camera, FileSystem etc. that HTML doesn't have direct access to, PhoneGap comes to rescue. The most commonly used APIs are provided by PhoneGap and it also provides the ability to write your own plugins to extend the reach of your app. The best part of using a cross platform technology like HTML is that your core application code needs to be written just once and it's only the plugins that need to separately developed for each supported platform. So, overall the combination of HTML5 and PhoneGap is amazing. This is the reason that a lot of companies are adopting HTML5 as their preferred technology.

However, there are some limitations too. As HTML5 is dependent on the browser component, the behavior is not always consistent across devices. The issue is more prominent on Android as each device manufacturer comes up with their own browser which interprets things differently. Such issues make developer's life miserable as it is impossible to always depict the correct behavior.

Flash
Coming to the other cross platform technology i.e. Adobe Flash. Despite all the predictions of Flash being on verge of death and the PR blunder by Adobe, Flash is still going strong. It is still the most preferred platform for games development. Like HTML5 Flash is also a cross platform tool that allows you to code once and deploy it to multiple platforms. One major advantage Flash enjoys over HTML5 is that it is not dependent on the browsers so the behavior is generally unique across devices. Like PhoneGap plugins, Flash has a concept of ANE. AIR Runtime Extensions allow the developers to write native plugins to expand their app's reach. Stage3D and StageVideo enable developers to improve the performance by getting rid of the display list. Another great advantage of using Flash is that you can make use of the huge amount of eLearning assets made in Flash over the years.
As with HTML5, there are certain limitations with Flash too:
  1. AIR run time gets included with all the mobile apps which adds up around 2 MB. If you use Flex SDK, add 3-4 MB more
  2. Build process for iOS is slow and it's very painstaking to debug iOS apps built with AIR
  3. For any issues or bugs in the AIR run time, one needs to wait for the new releases by Adobe.
Flex has been donated to Apache by Adobe and they are pretty close to rolling the first version of Apache Flex out. I see a lot of promise here. So, despite what people say, Flash is still live and kicking.

Native Development
By native development, I mean Java for Android and Objective C for iOS.Developing in native language would assure you of one thing for sure and that's that the performance of your would be much better when compared the cross platform technologies, be in HTML5 or Flex.

When developing native, you would invariably be forced to follow the workflow designed for the particular platform. This is one aspect, which generally gets ignored when developing cross platform and hampers the user experience. An iPhone user is used to seeing the apps behave in a way and Android users in other so it's important to take care of these while development. Native development helps in faster development too. As all the APIs are available for disposal, there is no need to write extensions to be able to use them and this saves a lot of development effort. The only shortcoming with developing native is that you'd need to learn multiple technologies and each app would need to be rewritten for all the platforms it supports.

So, at the end of it all, do we have a clear answer on which technology to use? No, I believe that it's horses for courses. Carefully analyze the job in hand and then decide on the technology. Love the times we are in, so many tools available to do the job, pick and choose any one.

Tuesday, November 30, 2010

Unit Test Cases - Mocking and Stubbing

When it comes to unit testing, what's the biggest issue you face? I generally get stuck with the dependencies among different classes, stubbing the actual environment. I am a Flash - Flex programmer but feel that the issue applies to any language you code in.

I am not too deeply familiar with the jargon of Unit Testing but I always try and keep implementing new things that help write better code. So, if you are a master of Unit Testing, the post is not for you. It is more for the people like me getting started with this. Do not expect it to be a tutorial on Unit Testing, but I would share my first experience with mocking. I also do hope that audience have a basic understanding of writing test cases with Flex Unit 4.

I wanted to test out the function below:

public function handleTaskComplete(evt:TaskEvent):void
{
   this.data = evt.target.data.toString().toLowerCase();
}

The problem is that the TaskEvent class does not allow me to specify the target property so I cannot simply create an instance of TaskEvent and pass it on. Similarly the Object returned by TaskEvent does not allow to set the data property. Mocking comes in handy here.

I can create a mock class for TaskEvent and also mock it's target method to return what I desire. There are loads of free Flex-AS3 mocking libraries but I found mockolate easiest to comprehend ;) hence I used it. Open source library, available at "http://mockolate.org/".

It took me a litte time to get all the pieces together. Basically, the mockolate source uses these libraries as well:
  • asx.swc
  • flexunit-core-flex-4.1.0-beta3.x-sdky.y.y.y.swc
  • FLoxy.swc
  • hamcrest-as3-1.1.0.swc
I create a new library project and combined all of these to one SWC. If you need it, give me a shout.

Once I had the SWC ready and included in my project, I proceeded to code.


[Test(async, timeout=30000)]
public function testMenuBarLoad():void
{
   Async.handleEvent(this,
   prepare(TaskEvent, XmlLoaderTask),
   Event.COMPLETE, startActualTest, 10000);
}

As you'd notice, the function above is my test function. I've made it an asynchronous function with some timeout. Before, being able to mock or stub any classes, we have to prepare them. Once the classes are prepared, "complete" event is dispatched. I can specify any number of classes to be prepared at once.

Once my classes were ready for mocking, startActualTest event was dispatched.

private function startActualTest (event:Event, data:Object):void
{
   var data:XML = getTestData();
   var taskEvent:TaskEvent = nice(TaskEvent, null, ["A"]);
   var xmlTask:XmlLoaderTask = nice(XmlLoaderTask, null, ["A"]);

   mock(taskEvent).getter("target").returns(xmlTask);
   mock(xmlTask).getter("data").returns(data);

   myComponent.handleTaskComplete(taskEvent);
   assetEquals(data, myComponent.data)
}

Let me go through the code in the function above.

Firstly, I create a nice mock object for the TaskEvent and XMLLoaderTask with the constructor arguments in the array. Mockolate allows 2 types of mock, nice and strict. Read documentation for details.

Secondly, we mock the getter "target" in taskEvent mock to return our mock object of xmlTask.

Thirdly, we mock the getter of xmlTask to return the desired data. Finally, we invoke component's handleTaskComplete method with the mocked taskEvent . In the last line, we add an assert to test whether data has been successfully set.

Mockolate provides us with provisions to test methods. It even allows us to define that if a method is called with a particular argument or a particular argument type, what should be returned. For example:

stub(flavour).method("combine").args(otherFlavour).returns(combinedFlavour);
We can also verify whether the functions mocked by us have been invoked or nor. This is just one of many cool things you can do with mockolate. I found mocking using mockolate to be of great help in creating a stub environment for unit testing.

Hope this post be of some help to you. Write to me at pulkit.gupta@gmail.com for any feedback.

I faced some issues getting a build ready for unit testing using ANT, so I would probably add that in my next post.