Apache Adobe Flex TutorialTutoriaux Adobe Flex & AIR en Français

30oct/0910

Flex Modules – TypeError: Error #1034: Echec de la contrainte de type : conversion de mx.managers::PopUpManagerImpl@18658d01 en mx.managers.IPopUpManager impossible [Résolu]

Voici un bug Flex que vous allez sûrement rencontrer si votre application comporte plusieurs modules à la fois. Ce n'est pas vraiment un bug en soi, c'est simplement un comportement du Framework Flex qu'il faut connaître pour éviter les surprises.

Le bug en question

Ce bug se produit de nombreuses manières différentes, mais toujours quand vous allez utiliser plusieurs modules dans une même application. Après des actions qui peuvent pourtant sembler banales, vous allez obtenir une RTE (RunTime Error) assez étrange du style:

TypeError: Error #1034: Echec de la contrainte de type : conversion de mx.managers::PopUpManagerImpl@18658d01 en mx.managers.IPopUpManager impossible.
 at mx.managers::PopUpManager$/get impl()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\managers\PopUpManager.as:68]
 at mx.managers::PopUpManager$/addPopUp()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\managers\PopUpManager.as:169]
 at mx.controls::ComboBox/getDropdown()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\controls\ComboBox.as:1459]
 at mx.controls::ComboBox/displayDropdown()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\controls\ComboBox.as:1552]
 at mx.controls::ComboBox/downArrowButton_buttonDownHandler()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\controls\ComboBox.as:1801]
 at flash.events::EventDispatcher/dispatchEventFunction()
 at flash.events::EventDispatcher/dispatchEvent()
 at mx.core::UIComponent/dispatchEvent()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\UIComponent.as:9298]
 at mx.controls::Button/http://www.adobe.com/2006/flex/mx/internal::buttonPressed()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\controls\Button.as:2504]
 at mx.controls::Button/mouseDownHandler()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\controls\Button.as:2750]
 at flash.events::EventDispatcher/dispatchEventFunction()
 at flash.events::EventDispatcher/dispatchEvent()
 at mx.core::UIComponent/dispatchEvent()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\UIComponent.as:9298]
 at mx.controls::ComboBase/textInput_mouseEventHandler()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\controls\ComboBase.as:1388]

Cette erreur de conversion (Coercion) se reproduit facilement sur le PopUpManager ou sur le DragManager mais peut aussi se produire avec vos propres classes. Vous obtiendrez des messages qui pourront vous sembler étranges. Par exemple:

TypeError: Error #1034: Type Coercion failed: cannot convert MyClass@5d73ce1 to MyClass.

Une conversion de type entre un type MyClass et le même type MyClass qui ne fonctionne pas, il y a de quoi se poser des questions. Ce bug a été maintes fois reporté sur la Bug Base d'Adobe (SDK-16474, SDK-14384, …) mais peut facilement être corrigé.

Un exemple d'application qui pose problème

29oct/093

Flex Modules – Décharger (Unload) correctement ses modules

Le principal intérêt des modules Flex est de découper votre application en plusieurs SWF pouvant être chargés et déchargés à la volée. Le fait de décharger (unload) permet de libérer la mémoire prise par le module une fois qu'il n'est plus utilisé. Cependant, même en faisant un unload() du module, il ne sera pas forcement libéré de la mémoire par le Garbage Collector. En effet, si des références vers ce module existent toujours, le module ne sera pas libéré. Parfois ces références sont évidentes mais parfois elles sont plus difficiles à trouver, surtout quand elles se trouvent au fin fond du framework Flex.

La Flex Team a publié un article pour récapituler les principales cause connues qui font qu'un module n'est pas libéré de la mémoire. Voici le détail.

Chargement de modules dans différents "applicationDomain"

Le fait de charger des modules depuis différents "applicationDomain" peut obliger Flex à référencer des classes dans ses Managers. Ces références peuvent causer la persistence du module en mémoire. Essayez de charger les modules avec l'applicationDomain par défaut (en utilisant les paramètres par défaut de la méthode load().

Fuites mémoire

Les "Memory leaks" peuvent créer des références vers votre module qui vont empêcher le Garbage Collector de libérer le Module Flex de la mémoire. Voici les causes principales:

Styles

Cela se produit même sans tag mx:Style dans votre MXML. Si votre module utilise un composant qui n'est pas utilisé par l'application principale, le module va enregistrer le style par défaut pour ce composant dans le StyleManager et coincer la première instance de votre module. Vous pouvez utiliser l'option du compilateur -compiler.keep-generated-actionscript pour voir quels styles le module et l'application principale utilisent. Vous pouvez aussi utiliser l'option -compiler.keep-all-type-selectors dans l'application principale pour forcer l'application principale à enregistrer les styles par défaut de chaque composant dans le fichier defaults.css. Les CSS chargés à l'exécution (StyleManager.loadStyleDeclarations) ne sont pas concernés par ce problème car ils sont chargés de manière différente.

Resources

Si votre module utilise des composants qui utilisent de ResourceBundles non-utilisés par l'application principale, ces Resource Bundles vont être enregistrés dans le ResourceManager et coincer la première instance du module. Vous pouvez utiliser l'option du compilateur -compiler.keep-generated-actionscript pour voir quels Resource Bundles le module et l'application mère utilisent. Vous pouvez forcer l'application principale à avoir ces Resource Bundles additionnels en ajoutant les metadata du Resource Bundle dans l'application principale.

Par exemple, vous ajouter le Resource Bundle "controls", vous devez ajouter à votre application:[ResourceBundle("controls")]. Si vous chargez vos Resource Bundles comme des modules, assurez-vous que les Resource Module ont bien fini leur chargement avant de charger le module Flex qui utilise le Resource Bundle.

ExternalInterface.addCallback

Les modules ne devraient pas utilier la méthode ExternalInterface.addCallback(). Cela enregistre la méthode dans le navigateur et il n'y a, à ce jour, aucun moyen de la supprimer. Il est recommandé de laisser l'application principale enregistrer une méthode qui va appeler une référence de fonction. Il suffit ensuite de remplacer cette référence par la méthode du module. Quand le module est déchargé, il suffit de mettre cette référence à null.

Timers et autres mécanismes de Timing

L'utilisation de Timer, setTimeout() et setInterval() peuvent causer des fuites mémoire. Les Timer doivent être arrêtés et avoir leurs listeners supprimés. setTimeout et setInterval doivent faire quant à elles appel à clearTimeout() et clearInterval() pour pouvoir être GCed correctement.

Listeners sur évènements depuis des objets à l'extérieur du module

Utilisez des références faibles (weak references) ou assurez vous d'avoir bien supprimé les event listener. Rappelez-vous que lorsque vous appelez A.addEventListener("foo", B.someMethod), "A" a une référence vers B, et pas l'inverse.

Focus

Si un des objets du module a le focus, le FocusManager aura toujours une référence vers le module. Il est conseillé de bouger le focus vers un autre objet à l'extérieur du module avant de le décharger.

RemoteObject

Si un module amène une nouvelle classe qui va faire partie d'une requête serveur, cette classe va être enregistrée par Flash Player et amener une référence vers le module. Liez les Data Classes à l'application principale lorsque c'est possible.

Images chargées

Si un module charge une image, cette image doit être déchargée sinon Flash Player va garder des buffers autour de cette image.

Autres éléments à connaitre

Une fois qu'ils n'y a plus de référence vers les objets d'un module, Flash Player peut encore garder un module en mémoire pendant un moment si certains des objets d'un module on eu le focus. Essayez de taper du texte ou de cliquez dans l'application pour éventuellement libérer des références vers le module.

Les versions Debug d'un module peuvent empêcher le module d'être libéré de la mémoire. Les version de debug contiennent des informations de debug qui peuvent être enregistrées par le debugger. Assurez-vous de tester avec des versions realease des modules et une application lue par un Flash Player classique.

29oct/096

Flex Modules – L'Event ModuleEvent.READY n'est jamais envoyé [Résolu]

Ce billet est consacré à la résolution d'un bug Flex qui se produit pendant le chargement des modules. Il est plutôt étrange et difficile à débugger. Ce problème est du au Garbage Collector qui va un peu trop vite en besogne lorsque l'on charge des modules Flex avec la classe IModuleInfo.

Voici un exemple de code qui permet de charger un module:

public function initApp():void {
 var moduleInfo:IModuleInfo = ModuleManager.getModule("SimpleModule.swf");
 moduleInfo.addEventListener(ModuleEvent.READY, onModuleLoaded);
 moduleInfo.load();
}

Dans l'esprit, ce code fonctionne bien et une fois le module chargé, la méthode onModuleLoaded devrait être appelée. Et bien à cause du GC, l'évènement ModuleEvent.READY ("ready") n'est jamais renvoyé. Apparemment, ce bug se produit sur Windows mais pas sous Unix, surement à cause d'une différence de Garbage Collector. Le problème est en fait un problème de scope. Même si la variable "moduleInfo" a des références vers elle (ne serait-ce que par l'évènement ModuleEvent.READY qui crée une back reference), la variable moduleInfo va aléatoirement être supprimée de la mémoire. l'évènement "ready" ne sera donc jamais envoyé.

Résolution du problème

Pour résoudre ce problème, il suffit de mettre la variable dans un scope au niveau de la classe et pas de la méthode:

private var moduleInfo:IModuleInfo=null;

 public function initApp():void {
 moduleInfo=ModuleManager.getModule("SimpleModule.swf");
 moduleInfo.addEventListener(ModuleEvent.READY, onModuleLoaded);
 moduleInfo.load();
 }

De cette manière, la variable ne sera pas détruite et l'évènement READY sera bien envoyé dans tous les cas.

D'autres bugs possibles…

Le fait que l'évènement READY ne soit jamais dispatché est un des effets de ce problème de scope. Il se peut que vous ayez les problèmes suivants:

  • Le module ne se charge pas la première fois mais se charge bien la deuxième fois
  • La méthode ModuleInfo.clearLoader() lance une Error #2029: This URLStream object does not have a stream opened when calling loader.close().
26oct/099

Créer un projet FlexLibrary pour les assets (icônes, éléments graphiques, …)

Parfois, quand vous compilez un projet Flex, vous allez peut-être vous apercevoir que certaines classes ne sont pas compilées dans le dossier bin-release, car vous avez du code ActionScript qui instancié une classe sous certaines conditions et le compilateur Flex assume que cette classe n'est pas requise.

Cet article est une traduction de l'excellent Creating an image asset library de russback.com.

C'est assez frustrant mais il y a un moyen de contourner ce problème. En créant une instance "dummy" (une instance non utilisée) d'une de ces classes non-compilées, vous pouvez vous assurer  que la classe sera bien compilée dans votre répertoire de destination. Une autre solution consiste à créer une librairie d'assets externe qui va être compilée en un SWC.

Création d'un projet Flex Library

Un projet Flex Library peut être crée de la même manière que tout autre projet, choisissez simplement Flex Library Project dans le menu New au lieu de Flex Project.

asset-library-create-library-project

A première vue, rien n'a changé à part l'icône dans la vue Flex Navigator mais vous verrez vite les bénéfices de cette approche.

asset-library-flex-navigator-view

Création d'une Class

Tout d'abord, on va créer au moins une Class. Pour cet exemple, l'auteur a choisit d'utiliser l'excellente FamFamFam Silk Icon Library, une collection de 700+ images 16x16px qui sont utilisées par de nombreuses interface web (WebDevelopper Toolbar Firefox et autres). Pour cet article, on va ajouter tous les icônes de cette librairie et en exposer seulement deux pour notre application.

Import des Assets

C'est très simple, téléchargez la librairie et déplacez toutes les images dans le projet FlexLibrary. Pour cet exemple, on place les icones dans le dossier /assets/images/famfamfam/silkicons. Il peuvent être placés n'importe où mais cette structure rend l'ajout d'assets d'autres sources plus facile dans la librairie.

Exposition des assets

Pour cet exemple, on a juste besoin de deux de cet icônes. Pour les exposer, on crée une nouvelle classe appelée IconLibrary (dans le package com.russback). Cette classe est une classe Bindable qui a une public static constant de type Classe pour chacun des éléments graphiques que l'on veut exposer:

package com.russback
{
	/**
	 * Class containing constants for each icon image available for use in the application
	 */
	[Bindable]
	public final class IconLibrary
	{

		/**
		 * Displays the accept icon from the FamFamFam Silk Icons library
		 */
		[Embed(source="assets/images/famfamfam/silkicons/accept.png")]
		public static const ACCEPT:Class;

		/**
		 * Displays the bomb icon from the FamFamFam Silk Icons library
		 */
		[Embed(source="assets/images/famfamfam/silkicons/bomb.png")]
		public static const BOMB:Class;

		/**
		 * Constructor
		 */
		public function IconLibrary()
		{
		}

	}
}
19oct/097

Flex Modules – Communication découplée entre modules avec EventBroker

On l'a vu dans les articles précédents, la communication avec les modules Adobe Flex peut se révéler difficile s'il on essaie de découpler complètement les modules de l'application Flex. Pour cela, on peut utiliser une interface ActionScript qui va permettre de brider la transaction à certaines méthodes identifiées. Cette méthode permet déjà de découpler le module de l'application de manière significative. Mais pour une communication dans les deux sens, cela peut devenir assez coûteux.

Une solution que j'utilise en environnement professionnel consiste à se servir de l'excellente classe EventBroker de Development Arc. Celle-ci permet en effet d'utiliser le Design Pattern Mediator pour avoir un objet commun (Singleton Facade) qui va s'occuper uniquement du transit des évènements dans l'application. Cette classe que vous pouvez utiliser simplement dans votre application va nous servir de point d'entrée pour la communication module/application ou application/module ou même module/module.

En utilisant simplement cette classe et quelques évènements personnalisés, vous pourrez créer cette communication de manière aisée. Voyons un petit d'exemple d'application:

Application Principale

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
  <mx:Script>
    <![CDATA[
      import events.ApplicationEvent;
      import mx.controls.Alert;
      import events.ProductTypeSelectionEvent;
      import util.EventBroker;

      private function onFirstModuleReady():void {
        EventBroker.subscribe(ProductTypeSelectionEvent.USER_SELECTION, onProductSelectionEvent);
      }

      private function onProductSelectionEvent(event:ProductTypeSelectionEvent):void {
        var selectedItem:String = event.selectedItem;
        Alert.show("Application::Selection dans le Module FirstModule: " + selectedItem);
      }

      private function onSendEventToModules():void {
        var selectedNumber:int = magicNumberStepper.value;
        var event:ApplicationEvent = new ApplicationEvent(ApplicationEvent.ON_MAGIC_NUMBER, selectedNumber);
        EventBroker.broadcast(event);
      }
    ]]>
  </mx:Script>
  <mx:HBox width="100%"
           height="100%">

    <mx:Panel title="Application Principale"
              paddingLeft="10"
              paddingRight="10"
              paddingTop="10">
      <mx:Text text="Le nombre choisi sera envoyé vers les deux modules par un evènement de type ApplicationEvent"
               width="350"/>
      <mx:Button label="Envoyer un Event depuis l'application"
                 click="onSendEventToModules();"/>
      <mx:NumericStepper id="magicNumberStepper"
                         value="38"/>
    </mx:Panel>
    <mx:ModuleLoader url="FirstModule.swf"
                     ready="onFirstModuleReady();"/>
  </mx:HBox>
  <mx:HBox width="100%"
           height="100%">
    <mx:ModuleLoader url="SecondModule.swf"/>
  </mx:HBox>
</mx:Application>

Pour cette application de démonstration, on écoute un évènement de type ProductTypeSelectionEvent.USER_SELECTION qui sera envoyé par un module. Au clic sur le bouton, on va envoyer en broadcast, un évènement de type ApplicationEvent.ON_MAGIC_NUMBER, qui va contenir le nombre choisi dans le NumericStepper.