Posts Tagged ‘label’

Label validation with FlexUnit

Posted in ActionScript on January 18th, 2011 by Mattes – 2 Comments

While working in interdisciplinary teams where graphics are produced by artists and the code comes from the developers, a solid designer-developer work-flow is crucial. At my current project team at wooga we already established a very good work-flow. The artists can produce graphics and see them in the running application after committing them. Therefore we use SVN and the Hudson integration server.

The problem

In our project we use SWC asset libraries to have a compile time check and strong typed access to all our graphics. To add logic to MovieClip frames you could either put the code directly on the timeline or the artist defines labels which are then utilized from the code. In order to keep the view separated from the code you should always use the label approach. But this is also risky if the artist accidentally deletes or renames a label. The code would then behave unexpectedly without knowing it. The only way to see the problem is to start the application and test all the MovieClip logic.

The first approach

To protect the labels from unintended changes, our first approach was to check their existence wherever we used them in code. This means we had to provide a separate Vector that contained all the expected labels.

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
private static var labels:Vector.<String> = new Vector.<String>();
private var view:IconView;

public function IconMediator(view:IconView):void
{
    initializeLabels();
    assertLabelsExist(view);
}

private function initializeLabels():void
{
    if (labels.length == 0)
        labels.push(IconState.ON, IconState.OFF);
}

private function assertLabelsExist(view:ButtonRounded):void
{
    var requiredMatchesRemaining:int = labels.length;
   
    for each (var requiredLabel:String in labels)
    {
        for each (var label:FrameLabel in view.currentLabels)
        {
            if (requiredLabel == label.name)
                requiredMatchesRemaining--;
        }
    }
   
    if (requiredMatchesRemaining != 0)
        throw new IllegalArgumentError(
            'IconView requires all labels: ' + labels);
}

Beside this we defined the labels itself in an enumeration class to provide strong typed access.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class IconState
{
    public static const ON:IconState = new IconState("on");
    public static const OFF:IconState = new IconState("off");
   
    private var type:String;

    public function IconState(type:String)
    {
        this.type = type;
    }

    public function toString():String
    {
        return type;
    }
}

This approach leads to three main problems:

  1. Depending on when the validation is executed in code, it could still happen that you don’t see the problems immediately
  2. We create a lot of code that is only necessary for validation but not for the application itself
  3. We create redundancy because we have to maintain the Vector of expected labels and the enumeration class (IconState). When changing labels it is very likely that you forget to update the Vector.

The solution

Thats why we came up with a different approach. We moved the validation into the unit tests. Now the code is separated but still executed because of our integration server (Hudson with FlexUnit support). The normal application code is now much slimmer and better readable. The artists/developers are automatically notified by mail if their changes break the tests.

To solve point 3 I wrote an assertion method that can reflect on enumeration classes and check all the defined labels on a certain MovieClip.

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
public function assertLabelEnum(target:MovieClip,
    enumClass:Class):void
{
    var classInfo:ClassInfo = ClassInfo.forClass(enumClass);
    var properties:Array = classInfo.getStaticProperties();
    var expectedLabel : String;
    var type:*;
    var labelCounter:int = 0;
       
    for each (var property : Property in properties)
    {
        type = property.getValue(enumClass);
               
        if (type is String)
            expectedLabel = String(type);
        else if (type is enumClass)
            type["toString"]();
        else
            continue;
           
        labelCounter++;
        expectedLabel = type["toString"]();
           
        assertLabel(expectedLabel, target);
    }
       
    assertEquals("Amount of expected labels differs from the
        amount of existing labels"
, labelCounter,
        target.currentLabels.length);
}

This method internally calls another assertion method assertLabel(). This method can also be used independently for testing specific labels without using enumeration classes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function assertLabel(target:MovieClip,
    expectedLabel:String):void
{
    var found:Boolean = false;

    for each (var label:FrameLabel in target.currentLabels)
    {
        if (expectedLabel == label.name)
        {
            found = true;
            break;
        }
    }

    if (!found)
        fail("Expected label [" + expectedLabel + "] not
            found in "
+ target);
}

Note: I used the reflection library from spicelib to retrieve all the static members of the enumeration class. You can download the library here.

And this is how the test method would look like:

1
2
3
4
5
[Test]
public function test_icon_frame_labels():void
{
    assertLabelEnum(new IconView(), IconState);
}

Summary

We made very good experiences with this approach because unintended changes on the labels no longer lead to awkward behavior in the application itself. And of course the application code can focus on the main logic and is therefor better readable.

Icon Badge Library for Air

Posted in Air Icon Badge, Libraries on February 8th, 2010 by Mattes – 8 Comments

Introduction

Adobe Air is often used to build feed readers or social media clients. This kind of applications can retrieve new data while they are running in the background. In that case it would be great to inform the user about the amount of new items. With OS X you can use the dock icon for that purpose. The Cocoa Framework allows to display a user defined text consistently on top of the application dock tile (icon). E.g. the screenshot at the right shows 2 unread mails in the inbox.

Native and emulated badge

Unfortunately the Air runtime allows no access to this native functionality. Thats why the idea for this library came up. It tries to emulate the native badge with the possibilities Air provides (see left image). Until now only the OS X badge is supported but a Windows implementation is possible, too. I will demonstrate the extensibility in one of the following blog posts.

Demo

If you have a Mac, you can download this Air application where the badge shows up on the real application icon. Running this application on windows will have no visual effect.

None Mac users can use the following demo that shows a preview of the dock icon:

You can browse the sources for this example on the google code repository.

Usage

To show the badge label you can use the static facade AirIconBadge. Internally it will create an IconBadge appropriate for the current operating system. Note: only one implementation for OS X is provided until now! Windows or Linux users won’t see a badge label.

With the static property label you can assign any string that should be displayed. Thats it.

1
AirIconBadge.label = "1";

If you assign an empty string or null, no badge will be displayed. To remove the current badge label you can also call the method clearLabel().

By default the biggest icon defined within the application descriptor will be loaded and shown. If no icon has been defined or the path is incorrect you must assign a customIcon in order to see the label. You can also assign a customIcon if you want to replace the default icon temporarily. Removing the customIcon will show up the default icon again.

1
AirIconBadge.customIcon = new CustomIconBitmap();

If neither of the icons could be loaded, no badge will be displayed. In this case an error event will be dispatched (UpdateErrorEvent). To get status information about the internals you can register for the InformationEvent. Both events will be dispatched by the IconBadge witch is statically stored within the AirIconBadge.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var iconBadge : IconBadge = AirIconBadge.iconBadge;
iconBadge.addEventListener(UpdateErrorEvent.UPDATE_ERROR,
handleError);
iconBadge.addEventListener(InformationEvent.INFORMATION,
handleInformation);

function handleError(event : UpdateErrorEvent) : void
{
    // do some error handling
}

function handleInformation(event : InformationEvent) : void
{
    trace(event.information.toString());
}

Download

All sources, binaries and examples are available for download under the MIT license.

Whats next

The documentation (especially the ASDocs) will be improved as well as the sources. If you have questions or feature requests, please let my know. The next blog posts will give you a deeper insight in the architecture and extensibility of this library.