Modular applications overview

About modules

Modules are SWF files that can be loaded and unloaded by an application. They cannot be run independently of an application, but any number of applications can share the modules.

Modules let you split your application into several pieces, or modules. The main application, or shell, can dynamically load other modules that it requires, when it needs them. It does not have to load all modules when it starts, nor does it have to load any modules if the user does not interact with them. When the application no longer needs a module, it can unload the module to free up memory and resources.

Modular applications have the following benefits:

  • Smaller initial download size of the SWF file.

  • Less memory use of overall application when modules are unloaded.

  • Shorter load time due to smaller SWF file size.

  • Better encapsulation of related aspects of an application. For example, a “reporting” feature can be separated into a module that you can then work on independently.

Modules and sub-applications are similar in many ways. Before deciding on an architecture for your applications, see the comparison of these two approaches in Comparing loaded applications to modules.

Benefits of modules

A module is a special type of dynamically loadable SWF file that contains an IFlexModuleFactory class factory. This allows an application to load code at run time and create class instances without requiring that the class implementations be linked into the main application.

Modules are similar to Runtime Shared Libraries (RSLs) in that they separate code from an application into separately loaded SWF files. Modules are very flexible because modules can be loaded and unloaded at run time and compiled without the application.

Two common scenarios in which using modules is beneficial are a large application with different user paths and a portal application.

An example of the first common scenario is an enormous insurance application that includes thousands of screens, for life insurance, car insurance, health insurance, dental insurance, travel insurance, and veterinary pet insurance.

By using a traditional approach to Rich Internet Application (RIA) design, you might build a monolithic application with a hierarchical tree of MXML classes. Memory use and start-up time for the application would be significant, and the SWF file size would grow with each new set of functionality.

When using this application, however, any user accesses only a subset of the screens. By refactoring the screens into small groups of modules that are loaded on demand, you can improve the perceived performance of the main application and reduce the memory use. Also, when the application is separated into modules, developers’ productivity may increase due to better encapsulation of design. When rebuilding the application, the developers also have to recompile only the single module instead of the entire application.

An example of the second common scenario is a system with a main portal application, written in ActionScript 3.0, that provides services for numerous portlets. Portlets are configured based on data that is downloaded on a per-user basis. By using the traditional approach, you might build an application that compiles in all known portlets. This is inefficient, both for deployment and development.

By using modules, you can establish an interface that contains portal services, and a generic portlet interface. You can use XML data to determine which modules to load for a given session. When the module is loaded, you obtain a handle to a class factory inside the module, and from that you create an instance of a class that implements the portlet interface. In this scenario, full recompilation is necessary only if the interfaces change.

Module API details

Modules implement a class factory with a standard interface. The product of that class factory implements an interface known to the shell, or the shell implements an interface known to the modules. These shared interfaces reduce hard dependencies between the shell and the module. This provides type-safe communication and enforces an abstraction layer without adding significantly to the SWF file size.

The following image shows the relationship between the shell and the module’s interfaces:

The ModuleManager manages the set of loaded modules, which are treated as a map of Singletons that are indexed by the module URL. Loading a module triggers a series of events that let clients monitor the status of the module. Modules are only ever loaded once, but subsequent reloads also dispatch events so that client code can be simplified and rely on using the READY event to know that the module’s class factory is available for use.

The ModuleLoader class is a thin layer on top of the ModuleManager API that is intended to act similarly to the mx.controls.SWFLoader class for modules that only define a single visual UIComponent. The ModuleLoader class is the easiest class to use when implementing a module-based architecture, but the ModuleManager provides greater control over the modules.

The ModuleLoader class implements the INavigatorContent interface so that it can be used directly by MX-based navigator containers (such as TabNavigator). The ModuleLoader class does not have any UI associated with it. All UI is defined by the module that it loads.

The Spark Module class extends the SkinnableContainer class. This means you can skin it and add visual components, including graphics, as children.

Module domains and sharing class libraries

By default, a module is loaded into a child domain of the current application domain. You can specify a different application domain by using the applicationDomain property of the ModuleLoader class.

Because a module is loaded into a child domain, it owns class definitions that are not in the main application’s domain. For example, the first module to load the PopUpManager class becomes the owner of the PopUpManager class for the entire application because it registers the manager with the SingletonManager. If another module later tries to use the PopUpManager, Adobe ® Flash® Player throws an exception.

One solution is to use framework RSLs when compiling your applications and modules (RSLs are enabled by default). The definitions of the manager classes will be loaded in the framework RSL by the main application. Then, all the modules and sub-applications can share it. For more information about using framework RSLs with modules, see Using RSLs with modules.

If you do not use RSLs, the solution is to ensure that managers such as PopUpManager and DragManager and any other shared services are defined by the main application (or loaded late into the shell’s application domain). When you promote one of those classes to the main application, the class can then be used by all modules. Typically, this is done by adding the following to a script block in the main application:

import mx.managers.PopUpManager; 
import mx.managers.DragManager; 
import mx.managers.ToolTipManager; 
import mx.managers.CursorManager; 
import mx.core.EmbeddedFontRegistry; 
 
private var popUpManager:PopUpManager; 
private var dragManager:DragManager; 
private var tooltipManager:ToolTipManager; 
private var cursorManager:CursorManager; 
private var embeddedFontRegistry:EmbeddedFontRegistry;

You should only define these classes in your main application if your modules use the related functionality. For example, define the EmbeddedFontRegistry class in your main application if one or more of your modules uses embedded fonts.

This technique also applies to components. The module that first uses the component owns that component’s class definition in its domain. As a result, if another module tries to use a component that has already been used by another module, its definition will not match the existing definition.

To avoid a mismatch of component definitions, create an instance of the component in the main application. The result is that the definition of the component is owned by the main application and can be used by modules in any child domain.

By default, modules do not share the main application’s StyleManager, however. They have their own instances of the IStyleManager2 class. As a result, modules can define their own styles. For example, style properties set on a Button control in one module are not applied to the Button control in another module or to the main application.

Because a module must be in the same security domain as the application (SWF) that loads it, when you’re using modules in an AIR application, any module SWF must be located in the same directory as the main application SWF or one of its subdirectories, which ensures that, like the main application SWF, the module SWF is in the AIR application security sandbox. One way to verify this is to ensure that a relative URL for the module’s location doesn’t require "../" ("up one level") notation to navigate outside the application directory or one of its subdirectories.

For more information about application domains, see Developing and loading sub-applications.

Create a modular application

To create a modular application, you create separate classes for each module, and an application that loads the modules. Adobe® Flash® Builder™ provides some mechanisms for making module use easier.

  1. Create any number of modules. An MXML-based module file’s root tag is <s:Module>. ActionScript-based modules extend either the Module or ModuleBase class.

  2. Compile each module as if it were an application. You can do this by using the mxmlc command-line compiler or the compiler built into Adobe Flash Builder.

  3. Create an Application class. This is typically an MXML file whose root tag is <s:Application>, but it can also be an ActionScript-only application.

  4. In the Application file, use an <s:ModuleLoader> tag to load each of the modules. You can also load modules by using methods of the spark.modules.ModuleLoader and mx.modules.ModuleManager classes in ActionScript.

Using styles with modules

When you set styles on modules, the style properties are set on the local StyleManager. Each module has its own instance of the IStyleManager2 class. This means that each module can load its own set of styles, and its styles do not necessarily affect the styles of other modules.

After an application finishes loading a module, the module’s styles are merged with the styles of the application. The module’s StyleManager walks the chain of parent modules and applications, up to the top-level StyleManager, and merges its styles with those set on the StyleManagers above it.

If during a style merge, a module encounters a style that it already sets on itself, the style is ignored. If the module encounters a style not set on itself, the style is added to the merged style definition. The styles set on the StyleManager that is closest to the module wins.

The following example loads two modules. The main application sets the color and cornerRadius style properties on the Button control type selector. The modules each set the color property on the Button control type selector. The merged styles result in the Buttons having a corner radius of 10, with colors set by each module. This shows how style merges work.
<?xml version="1.0"?>
<!-- modules/StyleModLoaderApp.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx">

    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout>

    <fx:Style>
        @namespace s "library://ns.adobe.com/flex/spark";

        s|Button { 
            color:blue;
            cornerRadius:10;
        }
    </fx:Style>

    <s:VGroup>
        <s:Label id="l1" text="Module 1"/>
        <s:ModuleLoader id="ml1" url="mxmlmodules/StyleMod1.swf"/>
    </s:VGroup>

    <s:VGroup>
        <s:Label id="l2" text="Module 2"/>
        <s:ModuleLoader id="ml2" url="mxmlmodules/StyleMod2.swf"/>
    </s:VGroup>

    <s:Button id="myButton" label="Main App Button"/>

</s:Application>
Module 1:
<?xml version="1.0"?>
<!-- modules/mxmlmodules/StyleMod1.mxml -->
<s:Module 
    xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx">

    <fx:Style>
        @namespace s "library://ns.adobe.com/flex/spark";

        s|Button { 
            color:red;
        }
    </fx:Style>
    
    <s:Button label="StyleMod1"/>

</s:Module>
Module 2:
<?xml version="1.0"?>
<!-- modules/mxmlmodules/StyleMod2.mxml -->
<s:Module 
    xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx">

    <fx:Style>
        @namespace s "library://ns.adobe.com/flex/spark";

        s|Button { 
            color:green;
        }
    </fx:Style>
    
    <s:Button label="StyleMod2"/>

</s:Module>

To prevent style merges, set the isolate-styles compiler argument to false. By doing this, you might trigger type coercion errors when loading skins. Modules also might not be properly garbage collected when they are unloaded. This is because the main application’s StyleManager will maintain references to the module even after is is unloaded. When you set isolate-styes to false, if more than one module loads a style, the first one loaded wins. In this case, styles set on modules can be overridden by those set on other modules.

In the previous example, if you set the isolate-styles compiler argument to false, the color of the Button controls’ labels in both modules would be red, because that is definition that is first loaded.

The getStyleDeclarations() method returns only the local style definitions. To get the merged style definitions, you can use the getMergedStyleDeclaration() method. All methods that modify style definitions affect only the local style definitions and not the merged style definitions.

Style properties are merged when the module is loaded. This means that style properties set on the main application and all child applications and modules are set on the module if the module does not override that style. Merged styles are set on a per-property basis, starting with the closest StyleManager and working upwards to the top-level StyleManager.

Even when using merged styles, child modules still inherit their parent module or application’s inheritable style properties when those settings are applied at runtime. Their StyleManager is not changed, but the values of the properties are inherited and applied where applicable. If you set a property at run time on the main application, the modules inherit that style immediately, as the following example shows:
<?xml version="1.0"?>
<!-- modules/StyleModLoaderApp2.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx">

    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout>

    <fx:Script>
        /* When you set this style in the main application, the modules immediately
           inherit the value. */
        private function changeStyle():void {
            styleManager.getStyleDeclaration("spark.components.Button").setStyle("fontSize", 15);
        }
    </fx:Script>

    <fx:Style>
        @namespace s "library://ns.adobe.com/flex/spark";

        s|Button { 
            color:blue;
            cornerRadius:10;
        }
    </fx:Style>

    <s:VGroup>
        <s:Label id="l1" text="Module 1"/>
        <s:ModuleLoader id="ml1" url="mxmlmodules/StyleMod1.swf"/>
    </s:VGroup>

    <s:VGroup>
        <s:Label id="l2" text="Module 2"/>
        <s:ModuleLoader id="ml2" url="mxmlmodules/StyleMod2.swf"/>
    </s:VGroup>

    <s:Button label="Change Styles" click="changeStyle()"/>

</s:Application>

For information on using run-time style sheets with modules, see Using run-time style sheets with modules and sub-applications.

If you use run-time resource bundles with loaded modules, you should consider setting the addResourceBundle() method’s useWeakReferences parameter to true. For more information, see Preventing memory leaks in modules and sub-applications.

Using RSLs with modules

Starting with Flex 4.5, modules work much more efficiently with RSLs. Modules will not load RSLs that the main application has already loaded, and modules can share RSLs with other modules.

The application only loads those framework RSLs that are needed at startup, and creates placeholders for all remaining framework RSLs. When a module is loaded, it does not try to load RSLs that are already loaded by the main application. If the module needs a framework RSL that is not initially loaded by the main application (and has a placeholder), then the module loads the RSL.

In addition, when a module loads an RSL, you can specify which domain the RSL is loaded into with the application-domain compiler argument. This lets you load an RSL into the parent, current, or top-level application domains. This applies to both framework RSLs and custom RLSs.

For more information about using modules and RSLs, see Using RSLs with modules and sub-applications.

Using pop-ups with modules

When using modules as pop-ups, you might not be able to click or drag the pop-up. The solution is to create a subclass of the pop-up (such as a TitleWindow container) that you use as the top-level MXML tag in the Module.

For more information, see Alex's Flex Closet.