Mastering the Tao of Personal Computing

Inspirational stuff!!! RT @GuyKawasaki: 15 weird but useful inventions http://om.ly/?jwJ via http://om.ly/?jwK NC 8 hrs ago

Flex Application Bootstrapping - Totally Custom Preloader

Jul 6th 2008
17 Comments
respond
trackback

I’m not going to define and discuss what a RIA is, but I rather stress here on one very important aspect of rich internet applications and in particularly saving time. Time is money (or opportunities for pleasures and entertainment), having more time is having more money (or opportunities for pleasures and entertainment), so that’s where ‘rich’ comes from. Flex applications that take eternity to load and don’t offer you something enough valuable as compensation for your patience are waiting in vain, just as in the song. This is one reason why rich internet applications come with very specific requirements and tight restrictions about what’s loaded and when. Chunks of functionality and data are dispersed in time and are ordered by their purpose in the concrete application.

The Specific Requirements

BombaySapphire.com - that’s a typical example. The requirements for the application loading can be summarized in the following diagram, where the gray bars depict the life-span of the different visual phases of the Bombay application, and the red ones are showing the time needed for loading the various parts and data, comprising the application. In case you wonder the LDA abbreviation stands for Legal Drinking Age validator.
Schematic Application Timeline
Instead of having all the gray bars be part of a monolithic whole, the guys from Adobe had provided us with the 2-frame Flex application model, where the first frame of the application is solely devoted on the preloader, while the second is available for the actual application.
Flex Application Two Frames Model
In the rest of the article I’m going to describe how to setup not just a skinned preloader, but a totally custom preloader. For those interested in just skinning the default Flex preloader - mx.preloaders.DownloadProgressBar - you should continue with Jesse Warden’s great article on the topic. But before proceeding with the implementation of the IPreloaderDisplay interface, we need to take a look at the Flex SystemManager and its responsibilities and also to get familiar with some events that the Preloader dispatches.

The Flex SystemManager

1. The SystemManager is the first display class created within a Flex application.
2. It is responsible for creating the Preloader and handling its events.
3. The SystemManager is also responsible for advancing the Flex application to the second frame once the application SWF is fully downloaded.

Besides these responsibilities directly related to the Preloader, the SystemManager is also responsible for:
4. Managing an application window and sending events if the size of the window changes.
5. Maintain tooltips, cursors and popup windows.
6. Expressly trapping the keyboard and mouse activity.
7. Some other stuff, I hadn’t time to read about.

Preloader’s most important events

1. ProgressEvent.PROGRESS
This is a typical progress event - it indicates what part of the application is loaded.
2. FlexEvent.INIT_PROGRESS
This event is dispatched when the Flex application completes an initialization phase - once the application SWF is fully loaded, the SystemManager advances the playhead to the next frame. Usually you’ll need to control the specific moment when the application advances to the second frame in the 2-frame model. In the samples below I’ll show one ways to achieve this - basically you need to capture the event, store it as pending and suspend the succeeding dispatches of these type of events - later you should re-dispatch the stored pending event.
3. FlexEvent.INIT_COMPLETE
This is the last event the Preloader dispatches. Next the Preloader should dispatch Event.COMPLETE indicating that the SystemManager is ready to remove the Preloader and to add the application to the display list.

Implementing the IPreloaderDisplay interface

I think this is the right place in the article to show you a sample implementation of the IPreloaderDisplay interface. Let’s start with the trivial part of the interface - some background and stage related properties. In the IPreloaderDisplay these are defined as get and set functions, which allows me to implement them by just declaring the corresponding public variables.

public var backgroundAlpha : Number;
public var backgroundColor : uint;
public var backgroundImage : Object;
public var backgroundSize : String;
public var stageHeight : Number;
public var stageWidth : Number;

The most convenient place for adding listeners for the events dispatched by the Preloader-component is in the set preloader() method of the interface.

private var _preloader : Sprite;
public function set preloader(value : Sprite) : void
{
    _preloader = value;

    _preloader.addEventListener(ProgressEvent.PROGRESS, onPreloaderProgress);
    _preloader.addEventListener(FlexEvent.INIT_PROGRESS, onPreloaderInitProgress,
        false, int.MAX_VALUE);
    _preloader.addEventListener(FlexEvent.INIT_COMPLETE, onPreloaderComplete);
}

The last method we must implement is the initialize() method. This method is called right after the Preloader is added as a child. This is the entry point where you can configure your preloader. For example I use this method to draw the background, filling it with a custom pattern or just adding visual components as children to this totally custom preloader.

public function initialize() : void
{
    // e.g. draw a background
    ...
    // or add visual children to the totally custom preloader
    ...
}

Let’s take a look how I handle the above mentioned event FlexEvent.INIT_PROGRESS:

private var readyToAdvanceToSecondFrame : Boolean = false;
private var pendingInitProgressEvent : FlexEvent;
public function onPreloaderInitProgress(e : FlexEvent) : void
{
    if (readyToAdvanceToSecondFrame)
    {
        pendingInitProgressEvent = e.clone();
        e.stopImmediatePropagation();
    }
}

It worths taking note on the way I’m subscribing to FlexEvent.INIT_PROGRESS:

_preloader.addEventListener(FlexEvent.INIT_PROGRESS, onPreloaderInitProgress,
    false, int.MAX_VALUE);

I’m setting the priority of the specified handler to the maximum possible value, making this handler the first one to execute when the event is dispatched.

Duplicating Binary Class Definitions

This probably needs a separate post, but I’ll share it here anyway. It is very possible that you’ll end up using a Flash component as your Preloader. The reasons for such a solution are clear. If your Preloader does a lot of stuff before your main application is even loaded, this better be a separate component. If you choose to put it in the application it will first increase the size of it, and second you’ll need to wait loading the whole application before using it. But making it separate imposes the question whether to use Flash or Flex when implementing it. Making this component a separate Flex application means you’ll have a preloader for the actual preloader, which is acceptable if you find a way how to load and use one Flex application inside of another Flex application. If you’ve done this, please share it with us and write a comment about it. Another thing we can’t rely on is whether the Flex framework is loaded - the size of the framework can be much bigger than the size of the Preloader you want to use.

Well, let’s assume we end up using a Preloader component written in Flash. When I did this I ran into a very subtle problem - duplicating binary class definitions. The picture below illustrates the problem:
Flex Application Domains
The Bombay Application Domain is the domain with the code of the main application. If this code holds a reference to the concrete type of the Flash-component, represented here by the LDA Application Domain we’ll run into trouble if the above application domain contains definitions of same classes - in this example the problematic duplication is the Tweener class. Such a duplication makes the application behave strange and unpredictable.

One way to avoid ugly situations like these is to use the exclude command before compiling the Preloader Flash component. The Flash 8 IDE provided this feature, but unfortunately it was removed from the new Flash IDE that has no such exclude definitions command.

Another way to avoid duplication is to avoid the above application domain to reference the concrete type of the Flash component. All you need to do is to encapsulate the preloader component under an interface Facade, thus the Flash component will conform with the Facade interface and the above application domain will not need to know the concrete type of the Flash component.

A Skeleton Implementation

I want to finish with a skeleton implementation, so you can base your code on it.

IPreloaderContentFacade.as:

package preloader
{
import flash.events.IEventDispatcher;

public interface IPreloaderContentFacade extends IEventDispatcher
{
    function setApplicationProgress(bytesLoaded : Number, bytesTotal : Number) : void;
}
}

TotallyCustomPreloader.as:

package preloader
{
import flash.display.Loader;
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.ProgressEvent;
import flash.net.URLRequest;
import flash.system.ApplicationDomain;
import flash.system.LoaderContext;

import mx.events.FlexEvent;
import mx.preloaders.IPreloaderDisplay;

public class TotallyCustomPreloader extends MovieClip implements IPreloaderDisplay
{
    public static const PRELOADER_CONTENT_URL : String = "preloader_content.swf";

    public var backgroundAlpha : Number;
    public var backgroundColor : uint;
    public var backgroundImage : Object;
    public var backgroundSize : String;
    public var stageHeight : Number;
    public var stageWidth : Number;

    private var _preloader : Sprite;
    public function set preloader(value : Sprite) : void
    {
        _preloader = value;

        _preloader.addEventListener(ProgressEvent.PROGRESS, onPreloaderProgress);
        _preloader.addEventListener(FlexEvent.INIT_PROGRESS, onPreloaderInitProgress,
            false, int.MAX_VALUE);
        _preloader.addEventListener(FlexEvent.INIT_COMPLETE, onPreloaderComplete);
    }

    private var preloaderContentFacade : IPreloaderContentFacade;

    public function TotallyCustomPreloader()
    {
        super();
        loadPreloaderContent();
    }

    public function initialize() : void
    {
    }

    private var loader : Loader;
    private function loadPreloaderContent() : void
    {
        loader = new Loader();
	loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaderComplete);

	var loaderContext : LoaderContext = new LoaderContext(false, new ApplicationDomain());
	var request : URLRequest = new URLRequest(PRELOADER_CONTENT_URL);
	loader.load(request, loaderContext);
    }

    private function onLoaderComplete(e : Event) : void
    {
        preloaderContentFacade = IPreloaderContentFacade(loader.content);
        addChild(MovieClip(preloaderContentFacade));
    }

    private function onPreloaderProgress(e : ProgressEvent) : void
    {
        if (preloaderContentFacade != null)
        {
            preloaderContentFacade.setApplicationProgress(e.bytesLoaded, e.bytesTotal);
        }
    }

    private var readyToAdvanceToSecondFrame : Boolean = false;
    private var pendingInitProgressEvent : FlexEvent;
    private function onPreloaderInitProgress(e : FlexEvent) : void
    {
        if (!readyToAdvanceToSecondFrame)
        {
            pendingInitProgressEvent = e.clone();
            e.stopImmediatePropagation();
        }
    }

    private function onPreloaderComplete(e : FlexEvent) : void
    {
        readyToAdvanceToSecondFrame = true;
        if (pendingInitProgressEvent != null)
        {
            dispatchEvent(pendingInitProgressEvent);
        }
        dispatchEvent(new Event(Event.COMPLETE));
    }
}
}

This skeleton implementation is omitting a lot of things a real implementation can have - all depends on the specific requirements of the application you develop. But I think the code above is general enough to serve as a guideline for a concrete implementation.


This post is tagged , , , ,

17 Comments

  1. Nice explanation of how preloader works. I am using something similar to your custom preloader. However, i really like your skeleton implementation code. Are you planning to license that?

    I have a question, though. How can i create a popup that appears the moment SystemManager switches to the Flex application (Frame 2)? When i put my popup creation code in the creationComplete event, it pops up during the preloader.

  2. admin

    Hi, Kenneth,

    The information in my blog is covered by Creative Commons (check the footer for more details), but Creative Commons is not meant for licensing code, so feel free to use the skeleton code I’ve provided in this article under the MIT license. Grouping the code with this comment has actually the power of signing the code with the license of my choice:

    Copyright (c) 2008 Vladimir Tsvetkov
    
    Permission is hereby granted, free of charge, to any person
    obtaining a copy of this software and associated documentation
    files (the "Software"), to deal in the Software without
    restriction, including without limitation the rights to use,
    copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following
    conditions:
    
    The above copyright notice and this permission notice shall be
    included in all copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    OTHER DEALINGS IN THE SOFTWARE.
    

    Now, to the questions:
    If I’m getting you right it seems to me you’re trying to do the following:

    dispatchEvent(new Event(Event.COMPLETE));
    PopUpManager.createPopUp( ... );
    

    If you’re doing this the popup will appear before the Preloader has been removed from the display. The Event.COMPLETE will cause the SystemManager to execute preloader_preloaderDoneHandler(), which will remove the Preloader and then will add the Application to the display. But creating your popup is such a manner is too early. You can try opening this popup on Application addedToStage - something like that:

    <mx:Application addedToStage="PopUpManager.createPopUp( ... );" ...
    

    Cheers

  3. Wow.. your suggestion “addedToStage” helped! i was looking for something instead of “show” or “creationComplete”.

    Regarding your code license, i noticed your Creative Commons logo on your blog. I’ll keep in mind your license.

    Thanks a lot!

  4. fabouney

    Great stuff, thanks a lot.
    Can you explain me the role of pendingInitProgressEvent.
    In my application I must load external sounds, and suspend the preloader until the sounds are completely loaded, in fact, I try to add a phase between the download and initialization.
    Or if not possible, after initialization.

  5. admin

    Good question! This certainly needs further explanation. This explanation involves some source code exploration of the Flex Framework. Take a careful look at the following method inside the mx.managers.SystemManager:

    /**
     *  @private
     *  Once the swf has been fully downloaded,
     *  advance the playhead to the next frame.
     *  This will cause the framescript to run, which runs frameEndHandler().
     */
    private function preloader_initProgressHandler(event:Event):void
    {
        // Advance the next frame
        preloader.removeEventListener(FlexEvent.INIT_PROGRESS,
                                      preloader_initProgressHandler);
    
        deferredNextFrame();
    }
    

    What happens in the fragment above is that the SystemManager advances the application to the next frame as soon as it gets the FlexEvent.INIT_PROGRESS event from the preloader.

    In order to suppress this advance to the next frame, we need to make the following things inside our custom preloader:
    1. Capture the FlexEvent.INIT_PROGRESS event before the SystemManager. You can achieve this by adding an event listener with the highest possible priority:

    preloader.addEventListener(FlexEvent.INIT_PROGRESS, onPreloaderInitProgress,
                false, int.MAX_VALUE);
    

    2. After you catch the event, you need to stop this event from further propagation, so the SystemManager won’t catch it. Think of the events as balloons full with gas, and think of your application as of a multi-storey building - event listeners with higher priority are on the lower floors, while the SystemManager is on top floor. Right now our custom preloader is on one of the first floors. It catches the event, and ties it with a rope to its balcony, so the balloon won’t ascend to the higher floors.

    if (!readyToAdvanceToSecondFrame)
    {
        pendingInitProgressEvent = e.clone();
        e.stopImmediatePropagation();
    }
    

    3. When you’re ready to advance to the second frame (e.g. your sound files are fully loaded), you need to re-dispatch the pendingInitProgressEvent. Untie the rope and let the balloon ascend to the top floor.

    if (pendingInitProgressEvent != null)
    {
        dispatchEvent(pendingInitProgressEvent);
    }
    

    I hope I’ve been of help to you!

  6. Devin

    Great explanation… I wonder though, if you could tell me why my preloader shows up so late and like half way through it’s progress. my Application is around 400k and it tends to confuse people when the preloader doesn’t show up for a few seconds… the page just looks empty.

    Thank you.

  7. admin

    Devin,
    It sounds pretty strange to me that your application has no activity at all for a few seconds.

    Any non-Flash dependencies of the SystemManager have to load in frame 1, before the preloader, or anything else, can be displayed. This can take some time, but I doubt it takes few seconds even if you use the default preloader - such a delay is possible on a very slow connection.

    One thing you can try is to use the Flash Player persistence caching of the Flex Framework. In the project properties you can choose to use the Flex Framework not as Merge into Code, but as a Runtime Shared Library, so the Flex Framework won’t be included in the size of SWF of your application. Further more the persistent cache will start storing the Flex Framework, so every subsequent loading of the application will be considerably faster.

    Check this out for more information on Flex Framework persistent caching.

  8. Bryan Dunbar

    Great article. But I have a question. I’ll explain my situation and maybe you can help me. I have an HTTPService that I want to call during the preload, before the application has been added to the stage. I’m trying to use your implementation to do this. What I’ve done is in the onDownloadComplete handler I’m calling my HTTPService. In trying to follow your code it seems like I would want to set readyToAdvanceToSecondFrame to true in the result event handler of the httpservice and dispatch the pendingInitProgressEvent at this point. However, after I dispatch that event its not getting back into my InitProgress event handler to allow flex to finish the init progress and effectively dispatch the complete event.

    Am I missing something? Do you have a working example using your skeleton code that may be helpful?

    Thanks!
    Bryan

  9. admin

    mx.rpc.http.HTTPService is part of the Flex framework, so keep this in mind when using it. This class wouldn’t be available until the Flex framework is fully downloaded. So you have two options:

    1. To call your web-service inside the preloader use something like this:

    private function onPreloaderComplete(e : FlexEvent) : void
    {
        // here should be safe enough to use Flex framework functionality as HTTPService
        // call your web-service here
    }
    
    private function onServiceResult(e : ResultEvent) : void
    {
        if (pendingInitProgressEvent != null)
        {
            dispatchEvent(pendingInitProgressEvent);
        }
        dispatchEvent(new Event(Event.COMPLETE));
    }
    

    In this scenario you don’t need the readyToAdvanceToSecondFrame flag, because you know for sure when your preloader is ready to advance to the next frame.

    2. Leave your preloader to do only pre-loading stuff - when it’s done, advance to the main application, but make the screen of the main application be exactly as the preloader, so you can continue smoothly with the application pre-loading logic and the user won’t even notice that he’s already in the main application.

    Sorry, but I can’t share the code of the real world example - but it uses this same skeleton.

  10. Phil

    Great article !

    How would you communicate to the main application the completed event from the pre-loader since it is encapsulated ? This is the case in which the pre-loader is in a different folder from your application… hence the duplicating classes right ? Please guide my understanding, so I catch the event , load the pre-loader and re-dispatch it.
    So say the pre-loader is loaded and then one wants to advance to the second frame for example by the click of a button… how would the re-dispatch work with in the pre-loader flash file while still keeping the movie encapsulated ?

    Thanks for the awesome article !
    Phil

  11. admin

    Hi, Phil,

    Thanks for your comment! I believe there is some confusion. Being in a separate Application Domain does not prevent events from propagating. Every component in the display list no matter the application domain it is placed in can dispatch events, and these events will propagate as expected.

    Nevertheless, when using different Application Domains you need to be very careful, because application domains are like containers for class definitions and loading third-party content can affect the class lookup within your application domain.

    Please, read this simple and clear explanation of Application Domains.

    I hope, I’ve understood your question correctly!

  12. Daniel

    Really good article, thank you!

    One thing I was hoping you could explain further though, was why you would want to create the IPreloaderContentFacade interface when the ‘preloader_content.swf’ is being loaded into a new Application domain. Wouldn’t that stop any duplication of binary class definitions?

    Thanks again!

    Dan

  13. I believe the default situation is loading into a child domain. Check this simple explanation of the Application Domains in Flex and see if my thoughts are in consistence with it.

    Probably loading the preloader code into a sibling domain will fix the issues of duplication or unwanted override of binary class definitions.

    The actual issue is unwanted override of binary class definitions.

    With the Facade interface we make sure the code of the preloader won’t depend on the code of the application - if it doesn’t depend on it, it won’t be needed to be linked by the compiler in the SWF.

    Another way to fix this issue is my using some sort of exclude directives for the Flash compiler. When we build this app, we hadn’t the time to write an exclude script for the new version of the Flash CS (the version back then was 3), so we stick to decoupling through interfaces.

    Thanks for the question Dan! Hope I gave you at least some direction where to seek for full clarity, because I’m ain’t sure I gave you a definitive answer. Right now I’m trying to answer by memory.

Leave a Reply