nwebb

Flex, Flash, AIR

Archive for February, 2010

A Few Swiz Tips For Beginners

Swiz Beginner's FAQ

When I started to look in to Swiz for the first time the other day, I had a few questions; Some answers were found in the official FAQ, and others elsewhere (mailing-group, web searches etc). I thought I'd collate what I found most useful, in to an easy to digest post that would make handy reading before starting your first Swiz project.

Remember that the docs are being updated all the time, and the alpha of Swiz 1.0 is already out, so check the official site for the most up-to-date info relating to the version of Swiz you are using. However I'd guess that most stuff here will remain fairly relevant for some time to come. I'm also likely to update this post when I come across/remember anything else, so check back.


Q & A:

Q: It looks as if I could end up with a lot of classes being instantiated in my BeanLoader(s). Wouldn't that have a negative impact on start-up performance?

A: Look at using the Prototype class, which among other things offers deferred instantiation. Prototype beans will not be created until they are needed for injection.

Q:[Mediate] looks a lot like the equivalent of addEventListener(). Is there an equivalent of removeEventListener()?

A: "No. [Mediate] is generally used in non-view classes like controllers, so we didn't account for that use case (weak listeners / removing mediate). Views should rarely be listening for 'system events' (and I think that is even more the case with views that are added and removed during app lifecycle*). Your views should generally just have a presentation model injected into them, or be managed by a mediator, or work in some other mostly passive fashion."

From Ben Clinkinbeard via GoogleGroups archive.
* relates to the specific question being asked by the user.

NOTE: You can still use [Mediate] within a view. Take this example where the MainView class (which encompasses all the app's views) listens for application-wide alerts and displays them via the Alert popup. This 'listener' does not need to be removed during the life of the application so it doesn't seem to pose much of an issue (although there would still be better places to put this code, such as in the presentation model if you are using one).

//From Richard Lord's Swiz "Flexcast" example (http://www.richardlord.net/blog/flexcaster-swiz)
//This "listener" doesn't need to be removed during the life of the application.
[Mediate( event="AlertEvent.SHOW_ALERT" )]
public function alertEventHandler( event : AlertEvent ) : void
{
     Alert.show( event.message, event.title );
}

Q: Where do I start implementing my logic?

A:  Let your main controller implement the interface IInitializingBean and start your application logic in the initialize() method. Swiz calls initialize() on these beans when the autowiring process is complete.

e.g. public class ApplicationController extends AbstractController implements IInitializingBean
NOTE : Swiz autowires the view on addedToStage which is after creationComplete.

Q: I've looked at someone else's Swiz project & I cannot see where [x] is being done. Am I going mad?

A: Possibly! ... but it's also possible to initialise a class in the BeanLoader and not refer to it elsewhere in the application, yet still have it react to mediated events. I've often seen things like an app's main controller instantiated and used like this in demo applications. So, remember to check the BeanLoader for classes that are only referred to / initialised in that one place.

Q: I am injecting a property in to my view/presentation-model, but when I try to access that property it is still null. How can I be notified of when that property is set?

A:  Instead of injecting in to a property, use a getter/setter instead. See the example below.

Actionscript:
  1. //THE FIRST WAY (but how do you know when 'locations' has been set?)
  2. //[Bindable]
  3. //[Autowire(bean="appModel", property="locations")]
  4. //public var locations:ArrayCollection;
  5.  
  6. //THE 'IMPROVED' WAY ;)
  7. [Bindable]
  8. [Autowire(bean="appModel", property="locations")]
  9. public function get locations():ArrayCollection
  10. {
  11.     return _locations;
  12. }
  13. public function set locations(value:ArrayCollection):void
  14. {
  15.     _locations = value;
  16.        
  17.         //let other parts of my view react to that fact that locations is now set. For example ...
  18.     dispatchEvent (new Event("locationsLoadedFromDB") ); //dispatching a custom event-type
  19. }
  20.  
  21. //AND IN THE SAME CLASS ....
  22. // The custom property dispatched from the setter will trigger this binding, and therefore anything bound to 'nextBtnEnabled' will update.
  23. [Bindable("locationsLoadedFromDB")]
  24. public function get nextBtnEnabled():Boolean
  25. {
  26.     if(locations) return (locations.length> 0);
  27.     return false;
  28. }

No comments

Using ‘Proxy’ For Loading & Accessing Config Files

Configuration Files

Externalising some of your application's settings in to config files is something most (hopefully all) of us do when writing Flex & AIR apps. It becomes almost essential when writing apps that may be moved around (e.g.  say your place of work has a development server, a UAT/staging server and a production server - you don't want to have to recompile your app each time you move from one server to the other ... which is what you'd have to do if your application had things like URLs hardcoded in to them).

If you don't yet use config files, consider how they would work given the above situation.
On our DEV server we'd have a file called 'config.xml', on our UAT server we'd have a file called 'config.xml', and on our PROD server we'd have a file called 'config.xml' too. Each file would contain settings specific to that particular environment. EG:

//IN DEV CONFIG
<prop id="remotingEndpoint"  value="http://localhost/weborb30/weborb.aspx"/>

//IN PROD CONFIG
<prop id="remotingEndpoint"  value="http:/mycompanywebsite.com/services/weborb.aspx"/>

In our Flex app we would do the same thing regardless:

//IN OUR FLEX APP (not showing full tag here, but you get the idea)
<mx:RemoteObject
destination="GenericDestination"
endpoint="{appSettings.remotingEndpoint}"/>

The key point is that the Flex app doesn't have to be recompiled when it is moved from server to server - it just picks up the local config settings for each environment.

A Common Approach To Creating/Loading Configs

One way people deal with config files is to create a class which loads in some settings file, usually as XML. This class most likely defines properties which correspond to the properties in the XML - the idea being that you populate the class properties from the XML properties. There are more efficient variations on this approach, but doing it that way is common - I was prompted to write this post after seeing someone doing just this. It has several drawbacks though. For example, each time you add a property to your XML you have to add that property (or getter method) to your class as well. You also end up having one class per config file, so it's not particularly reusable.

The flash.utils.Proxy Approach To Creating/Loading Configs

What would be nice is if we had an easy way to load in our config XML file and have all the properties instantly available via our class. This is where flash.utils.Proxy comes in. There are four things we need to do in order to get this to work (five if we want our class to dispatch events as well):

  1. Make our class dynamic
  2. Extend flash.utils.Proxy
  3. Override its 'getProperty' method
  4. Use the flash_proxy namespace (in order to be able to override getProperty)
  5. (optional) Implement IEventDispatcher

I'll go over the key points now but it's easier to see it in action so I would recommend downloading this example file ( in FlexBuilder choose file->import->existing projects in to workspace ... select 'archive file' and browse to the file you downloaded).

(1) Our class is declared using the dynamic keyword - essentially this allows other classes to request any properties from it, at runtime, and errors won't be generated even if the property doesn't exist. So we could ask for myinstance.thisVariableDoentExist and no error would be thrown.

(2) Next we extend flash.utils.Proxy because we want a way to capture these undefined property requests; With flash.utils.Proxy, when such a request occurs, a method of the Proxy class called getProperty() is automatically triggered for us (there are other methods triggered by other actions, so be sure to check out the docs - this is one handy class), and this allows us to do something in response to that request.

(3) We override the getProperty() method, so that we can write code which responds to the undefined requests.

(4) One small quirk is that the getProperty() method exists inside the flash_proxy namespace; All this means for us is that we have to import flash.utils.flash_proxy (intellisense won't pick it up, but it does exist) and use it in front of the method (much in the same way we use access modifiers like 'public' and 'private') - see the code for an example.

(5) Our class also needs to load in some XML (our external config file), store it, and optionally dispatch an event once the data has loaded in, which is why I also implement the IEventDispatcher interface - if you're not familiar with implementing IEventDispacher I suggest you look it up in the docs because about half the code in this class can essentially be ignored if you don't want to dispatch events. We can't extend multiple classes in AS3 - we are already extending Proxy, so we have to implement IEventDispatcher rather than extend EventDispatcher in order to get event dispatching functionality.

Putting It All Together

Okay, now that we can store XML and catch undefined property requests, we can see what property is being requested and look for it inside our loaded XML file. We return the value being requested, from the XML, but it appears as if the property belongs to our class. As the name implies, we are proxying the request on to the XML.

Below is a snippet of our class. You can see that in getProperty() I use e4x to grab the value out of the saved XML file. If the XML doesn't contain the value then our method returns an empty String (of course you can modify this how you see fit):

package config
{
/**
* config.AppConfig.
*
* Loads &amp; stores an XML config file, and returns the data from it as if the data belonged to the class instance.
* XML data nodes must be in the form:
*
(where nodeID &amp; nodeValue represent your values).
*
* NOTE: 	Class is dynamic.
* 		Class overrides Proxy.
* 		Class imports &amp; uses flash_proxy namespace.
*
*/

import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.events.IOErrorEvent;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.utils.Proxy;
import flash.utils.flash_proxy;

public dynamic class AppConfig extends Proxy implements IEventDispatcher
{
     protected var _eventDispatcher:IEventDispatcher;
     protected var _data:XML;
     protected var _dataLoaded:Boolean = false;
     private   var _urlLoader:URLLoader;

     public function AppConfig()
     {
          _eventDispatcher = new EventDispatcher();
     }

     public function loadConfigFile(url:String):void
     {
          var request:URLRequest = new URLRequest(url);
          _urlLoader = new URLLoader();
          _urlLoader.addEventListener(Event.COMPLETE, onDataLoaded);
          _urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
          _urlLoader.load(request);
     }

     protected function onDataLoaded(event:Event):void
     {
          _data = XML(_urlLoader.data);
          _dataLoaded = true;
          removeListeners();
          dispatchEvent(new Event(Event.COMPLETE));
     }

     override flash_proxy function getProperty(name:*):*
     {
          if(_data)
          {
               var propName:String = name.toString();
               return (_data.prop.(@id == propName).@value).toString();
          }
     }

//.....more class stuff not shown

So the upshot is that we now have a class that can load in any config file (as long as the XML follows the same formatting), and this class doesn't have to be updated if/when the XML changes. We can instantiate n instances of our class and load in n types of XML file (one for config settings, one for strings, one for whatever else we need). It's a handy way to approach the loading of config files.

9 comments

Using WebOrb with AIR (Step 1)

[I'm using the .NET version of WebOrb but this should be generic enough to apply to all versions]

Examples Work For Flex But Not AIR
When you load WebOrb you get access to the 'weborbconsole' which is a Flex app that runs on localhost). In the 'Getting Started' section of this app there is a tutorial which will work if you run it as a Flex application, but will throw an error if you run it as an AIR application. I found some very helpful documentation pointing to the necessary changes, but it is slightly outdated so I thought I'd fill in the gaps.

Recommended Reading
First of all, here is the documentation which I'd highly recommend reading (I'll summarise some of the key points below): Channels, endpoints and destinations

Config Files
Okay, so if you stuck with the default installation then you should find all your config files located here:
C:\inetpub\wwwroot\weborb30\WEB-INF\flex
The two files we are most interested in are services-config.xml (specifies channels) and remoting-config.xml (specifies destinations). The former references the latter.

In remoting-config.xml you can see that some default channels are defined:

<default-channels>
     <channel ref="my-amf"/>
     <channel ref="my-secure-amf">
</default-channels>

You can also see that a bunch of destinations are declared, one of which is called "GenericDestination" and uses a wildcard (*) for the source:

<destination id="GenericDestination">
     <properties>
          <source>*</source>
     </properties>
</destination>

When the wildcard is used, WebOrb uses one of the default-channels. The default channels apply to all remoting destinations unless a channel is specifically referenced in a destination declaration (thanks Mark!).

You can see that the first of the default channels references the "my-amf" channel, so by default any [non-secure app?] setting the destination to be "GenericDestination" will actually end up using the "my-amf" channel. Here is an example of the Flex RemoteObject tag doing just that:

<mx:RemoteObject id="compinfo" destination="GenericDestination" source="noteperfect.remote.ComputerInfoService"
showBusyCursor="true"
fault="faultHandler(event)">
     <mx:method name="getComputerInfo" result="getComputerInfoHandler(event)"/>
</mx:RemoteObject>

A Solution
The documentation I linked to ( above) explains how AIR needs absolute URLs rather than relative URLs, and this is why we need to use different channels for Flex and AIR apps ( the only difference being that the AIR channel has an absolute reference to the endpoint uri ) . The doc also refers to "my-air-amf" and "GenericAIRDestination". I could only find an "air-http" channel definition in my version of WebOrb, and as for "GenericAIRDestination", that does exist (in weborb-services-config.xml) but it is exactly the same as "GenericDestination".

So, I was able to get the example working as an AIR app by doing the following:

  • Changing the default channel in remoting-config.xml to be  <channel ref="air-http"/>
  • Setting the destination on my RemoteObject in Flex to be "GenericDestination" (rather than GenericAIRDestination)

I'm brand-new to WebOrb and I'm not suggesting that this is the only/best/recommended way to get things working - although the documentation seems to suggest it is. Please feel free to leave a comment if there is a better way.

-------------------------------------------------------------------------------------------
Edit:

After chatting to Mark from MidnightCoders I've changed things slightly. All I've really done is:
(1) Reverted the default-channel to be "my-amf" (in remoting-config)
(2) Copied "GenericAIRDestination" ( from weborb-services-config.xml) in to remoting-config,  and added both a channels attribute and tag - see the xml below.
(3)
Changed my RemoteObject in Flex to use "GenericAIRDestination"

In Remoting Config:

<default-channels>
     <channel ref="my-amf"/>
</default-channels>

Also In Remoting Config (copied over from weborb-services-config & ammended):

<destination id="GenericAIRDestination" channels="air-http">
     <channels>
          <channel ref="air-http" />
     </channels>
     <properties>
          <source>*</source>
     </properties>
</destination>

In Services Config:

<channel-definition id="air-http" class="mx.messaging.channels.AMFChannel">
     <endpoint uri="http://localhost:80/weborb30/weborb.aspx" class="flex.messaging.endpoints.AMFEndpoint"/>
     <properties>
          <polling-enabled>false</polling-enabled>
     </properties>
</channel-definition>

In Flex App:

<mx:RemoteObject id="compinfo"
destination="GenericAIRDestination"
source="noteperfect.remote.ComputerInfoService"
showBusyCursor="true"
fault="faultHandler(event)">
     <mx:method name="getComputerInfo" result="getComputerInfoHandler(event)"/>
</mx:RemoteObject>

I won't go too much further in to this, because (a) Mark has indicated that things will probably be made easier in WebOrb 4, and (b)  my next post is going to be about using WebOrb & AIR without referring to the config files at all ( courtesy of Dan from the moov2 team).

Thanks to Mark for all his help.

No comments

Next Page »

Bad Behavior has blocked 399 access attempts in the last 7 days.