Tuesday, September 21, 2010

Migrating Flex 3 to Flex 4 pitfalls

It should be fairly easy to compile a project written in Flex 3 in the new Flash Builder 4. However, there are a few pitfalls that I encountered and you may encounter too.

So here they are, plus the solutions.

Before you begin
  • Make sure you are using the latest SDK (currently 4.1)
  • Copy all your old source file, you don't want to ruin them just yet... 
    Create a new project, you will copy most of your old application mxml code there.
  • In Project Properties. Select Flex Compiler on the left, and in "additional compiler arguments" add -theme=${flexlib}/themes/Halo/halo.swc. 
    This will make your application look more or less like it used to when it was under Flex 3 which used the default Halo theme. Of course, if you used a different theme, include that theme instead

    <mx:Application> is now <s:Application>
    <mx:Style> is now <fx:Style>,   <mx:Script> is now <fx:Script>

    Copy your main application mxml and change it.
    In the application tag these namespace attributes should now be:
                   xmlns:fx="http://ns.adobe.com/mxml/2009"
            xmlns:s="library://ns.adobe.com/flex/spark"
            xmlns:mx="library://ns.adobe.com/flex/mx"

    Change <mx:Script> to <fx:Script> on that file, too, and <mx:Style> to <fx:Style>.
    For now, you don't have to do it in ALL your project files, just in the main application mxml.

    If you have global style references, for example you are styling Button in all of your application, you should prefix it with the proper namespace. That is, because Flex 4 has its own button component with the same name.
    So, if you wanna style only your Halo buttons, include in your <fx:Style> tag:
    @namespace mx "library://ns.adobe.com/flex/halo"; 
    and change button to
    mx|Button
    {
       color:#ffffff
    };  

    If you are going to add spark buttons later and want to style them globally too, you will add something like:

    @namespace s "library://ns.adobe.com/flex/spark"; 
    s|Button
    {
       color:#ffffff
    }; 


    Application.application is obsolete

    In Flex 4, you can no longer use Application.application to reference your application. Instead, you now need to use FlexGlobals.topLevelApplication.

    Luckily, in one of the latest Flex SDK 4 builds this is transparent to you as Application.as contains a property called "application" which simply returns FlexGlobals.topLevelApplication.

    So you shouldn't need to change your code (especially if you have this reference all over your code, like I do).

    Application.addChild() WILL fail

    If you try to add an mx container or component to the application itself, it will no longer work with addChild. So use addElement() instead.
    I won't explain here why, but addElement will actually create and add a proper spark container for you and add your component to it.
    If you want to dig deeper on this check out this post (Update: Actually, this post explains this issue).
     
    backgroundImage for Application is no longer supported

    If you used backgroundImage in your old application, you will notice that it's no longer accepted. You will need to use a skin for your application instead Here's how.

    "The style '___________' is excluded by type 'mx.containers._________'."

    If you get this compiler error message it means you fucked up a style in Flex 3, but the old compiler looked the other way - whereas Flex 4 doesn't.
    For example I had this piece of code in my project for some reason:

    <mx:Canvas paddingLeft="0"
    It took me a few seconds to realize that Canvas doesn't even support padding (it's excluded from the class, as you can see in the class's code).

    So just delete these non-existing styles and these errors will be cleared.

    Runtime Error in FocusManger:childHideHandler()

    Once I got my app up and running, it wasn't the end of it. On certain scenarios I would get this runtime error:

    TypeError: Error #1009: Cannot access a property or method of a null object reference.
    at mx.managers::FocusManager/childHideHandler()[E:\dev\4.x\frameworks\projects\framework\src\mx\managers\FocusManager.as:1759]
    at flash.events::EventDispatcher/dispatchEventFunction()
    at flash.events::EventDispatcher/dispatchEvent()
    at mx.core::UIComponent/dispatchEvent()[E:\dev\4.x\frameworks\projects\framework\src\mx\core\UIComponent.as:12528]
    at mx.core::UIComponent/setVisible()[E:\dev\4.x\frameworks\projects\framework\src\mx\core\UIComponent.as:3088]
    at mx.core::UIComponent/set visible()[E:\dev\4.x\frameworks\projects\framework\src\mx\core\UIComponent.as:3047] 


    Well, this actually turns out to be a known bug introduced in SDK 4.1, and apparently it was only fixed in Flex SDK 4.5!

    The bug is caused when the focus is set on a component which goes invisible. There are a few alleged workarounds (I tried one or two, but they didn't work in my case).

    One suggestion is to override the event listener for HIDE and invoke event.stopImmediatePropagation().

    If that doesn't work, check out this forum thread for a possible solution.

    Personally, the course of action I took was simply to download 4.5 and use it (I wasn't happy about it, though. I think Adobe should have fixed this back in 4.1. It's not a minor bug).

    mx:GridLines's direction property is obsolete

    Property is now named "gridDirection" :@


    Wednesday, September 15, 2010

    TabBar buttons with hand cursor (Flex 3)

    It's a small thing really, but I haven't seen it posted anywhere, so I thought I'd share.

    PROBLEM:
    You have a TabBar and you may want a hand cursor on mouseover of unselected tabs (the currently selected tab should keep showing a regular arrow cursor).

    SOLUTION:
    Extend TabBar, and -
    • Override resetNavItems() - adding a loop that also sets a hand cursor on all buttons.
    • Override hiliteSelectedNavItem()  - reset the cursor to a hand cursor on the currently selected tab. Select the new tab (by calling super.hiliteSelectedNavItem) and then clear the hand cursor from that tab.
    • Override public get dataProvider() just adding a call to hiliteSelectedNavItem(selectedIndex).
      The reason for that is that those other two methods are only called as a response to user interaction (clicking on tabs). We want to set the cursors as soon as the tabs are created, assuming the first tab is selected.

    I'm pretty sure it's not the most efficient way of doing it, but hey - it works.
      Source code here.
      DermaRoller