puremvc
|
|
« Reply #1 on: September 26, 2010, 12:38:48 » |
|
Your handling of alwaysInFront is ok, but unless you're performing a complex update, usually the thing to do is Mediate the app and or the stage. This cuts down on the number of Commands involved if you do lots of trivial updates to the same component.
When we use a Mediator as the single touchpoint in the codebase for important components, then we limit the coupling between the component and the app.
That said, if this is the only time you manipulate the App or the Stage, your single Command solution would be fine. The final exception is if you do that one thing *a lot*, then you still might want to Mediate the component (i.e. the stage in this case) so as not to have the runtime burden of repeated Command instantiation which does take time.
If you communicate with the stage a bit in your app, you should have a StageMediator (a long-lived actor) that tends the Stage itself. You send a note (SET_ALWAYS_IN_FRONT) that it responds to by setting its view component's property like:
protected function get stage():Stage { return viewComponent as Stage; }
override public function handleNotification(note:INotification):void { switch (note.getName()) { case SET_ALWAYS_IN_FRONT: stage.alwaysInFront=Boolean(note.getBody()); . . . } } As for your handling of the AirUpdater, even though it is not a view component in the hierarchy, it definitely has to be communicated with, so mediation is a much better choice than a command, because there is a conversation that needs to take place. It could be done with scads of Commands, but it's much easier to handle (and understand) with a Mediator
Below are two classes and the updater version file I used successfully in a recent AIR application, the names have been changed of course. Also, you'll note that the StateMachine was used in the example. You don't have to use the StateMachine, but you'll find it super useful when your app goes through multiple steps (that can fail and possibly be retried) at startup such as checking for the net, checking for updates, checking for license info to run the app, checking for credentials to a service the app uses, etc...
AIRUpdateView is a panel that is shown when we get to the point in the app where we're checking for an update. It shows whether the update is required or not (an extension to the update config file that I added), a description of the update (release notes), and buttons for skipping or accepting the update. If the update is required, the user has only the option to continue, otherwise they see the skip button.
<?xml version="1.0" encoding="utf-8"?> <mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script> <![CDATA[ import com.myapp.common.constants.MyAppIcons; import com.myapp.common.constants.MyAppSays; import com.myapp.common.events.MyAppEvent; import air.update.events.StatusUpdateEvent;
[Bindable] public var newVersion:String; [Bindable] public var releaseNotes:String; [Bindable] public var required:Boolean; private function sendEvent( name:String, data:*=null ):void { dispatchEvent( new MyAppEvent( name, data ) ); } ]]> </mx:Script> <!-- AIR UPDATE PANEL--> <mx:Panel title="{(required)?'Required':'Optional'} Update Available" status="New Version: {newVersion}" titleIcon="{MyAppIcons.EXCLAMATION}" horizontalAlign="center" verticalAlign="middle" width="400" height="250" right="20" top="20" backgroundAlpha="1" backgroundColor="#FFFFFF" themeColor="#ffff00">
<!-- FORM --> <mx:Form height="156" width="353" paddingBottom="0" paddingTop="0" horizontalScrollPolicy="off" verticalScrollPolicy="off"> <!-- RELEASE NOTES --> <mx:Label text="Release Notes" fontWeight="bold"/> <mx:TextArea height="123" width="330" editable="false" wordWrap="true" id="taReleaseNotes" htmlText="{releaseNotes}" textAlign="left" cornerRadius="0" borderStyle="none" paddingRight="10" />
</mx:Form> <!-- CONTROL BAR --> <mx:ControlBar paddingTop="0" paddingBottom="0" height="42"> <mx:ViewStack selectedIndex="{(required)?0:1}" width="100%" height="28"> <mx:HBox width="100%" horizontalAlign="right" verticalAlign="middle" paddingTop="0" paddingBottom="0"> <mx:Button label="Install Required Update" click="sendEvent(MyAppSays.DOWNLOAD_UPDATE)" /> </mx:HBox> <mx:HBox width="100%" horizontalAlign="right" verticalAlign="middle" paddingTop="0" paddingBottom="0"> <mx:Button label="Install" click="sendEvent(MyAppSays.DOWNLOAD_UPDATE)" /> <mx:Button label="Skip" click="sendEvent(MyAppSays.SKIP_UPDATE)" /> </mx:HBox> </mx:ViewStack> </mx:ControlBar> </mx:Panel> </mx:Canvas>
The AIRUpdateMediator has the AIRUpdateView as its view component, but it also communicates with the AIR updater to initiate the check, listen for results and tell the updater to update if the user accepts it. Note that the AIRUpdateView component doesn't actually exist when this Mediator is registered, it first communicates with the updater to find out if we even need to show it. If so, the StateMachine is notified. Another Mediator is listening for the state change, switches its view component's ViewStack, causing the AIRUpdateView to be shown. That Mediator retrieves the AIRUpdateMediator and sets its view component, giving it a way to listen to the user's choice.
package net.myapp.view { import air.update.ApplicationUpdater; import air.update.events.DownloadErrorEvent; import air.update.events.StatusUpdateErrorEvent; import air.update.events.StatusUpdateEvent; import air.update.events.UpdateEvent; import flash.events.*; import flash.filesystem.File; import flash.utils.*; import mx.controls.Alert; import com.myapp.common.constants.MyAppSays; import com.myapp.view.components.AIRUpdateView; import org.puremvc.as3.multicore.patterns.mediator.Mediator; import org.puremvc.as3.multicore.utilities.statemachine.StateMachine;
public class AIRUpdateMediator extends Mediator { public static const NAME:String = "AIRUpdateMediator"; public function AIRUpdateMediator( ) { super( NAME ); } override public function onRegister():void { updater.configurationFile = new File("app:/myapp-update-config.xml"); updater.addEventListener( UpdateEvent.INITIALIZED, onInitialized ); updater.addEventListener( UpdateEvent.DOWNLOAD_COMPLETE, onDownloadComplete ); updater.addEventListener( StatusUpdateEvent.UPDATE_STATUS, onStatusUpdate ); updater.addEventListener( StatusUpdateErrorEvent.UPDATE_ERROR, onStatusUpdateError ); updater.addEventListener( DownloadErrorEvent.DOWNLOAD_ERROR, onDownloadError ); updater.addEventListener( ErrorEvent.ERROR, onError); updater.initialize(); }
protected function onInitialized( event:UpdateEvent ):void { updater.checkNow(); } protected function onStatusUpdate( event:StatusUpdateEvent ):void { event.preventDefault(); if ( event.available ) { status = event; sendNotification( StateMachine.ACTION, null, MyAppSays.UPDATE_PENDING ); } else { sendNotification( StateMachine.ACTION, null, MyAppSays.NO_UPDATE_PENDING ); facade.removeMediator(NAME); } }
protected function onError( event:ErrorEvent ):void { Alert.show( event.text, "Unexpected Update Error" ); sendNotification( StateMachine.ACTION, null, MyAppSays.UPDATE_ERROR ); } protected function onStatusUpdateError( event:StatusUpdateErrorEvent ):void { Alert.show( event.text, "Update Installation Failed" ); sendNotification( StateMachine.ACTION, null, MyAppSays.UPDATE_ERROR ); } protected function onDownloadError( event:DownloadErrorEvent ):void { Alert.show( event.text, "Error Downloading Update" ); sendNotification( StateMachine.ACTION, null, MyAppSays.UPDATE_ERROR ); }
protected function onDownloadComplete( event:UpdateEvent ):void { event.preventDefault(); event.stopImmediatePropagation(); updater.installUpdate(); }
protected function onDownloadUpdate( event:Event ):void { sendNotification( StateMachine.ACTION, null, MyAppSays.DOWNLOAD_UPDATE ); updater.downloadUpdate(); } protected function onSkipUpdate( event:Event ):void { sendNotification( StateMachine.ACTION, null, MyAppSays.SKIP_UPDATE ); facade.removeMediator(NAME); } override public function setViewComponent( viewComponent:Object ):void { super.setViewComponent(viewComponent); updateView.newVersion = status.version; updateView.releaseNotes = status.details[0][1]; // not localized var updateDescriptor:XML = (status.target as ApplicationUpdater).updateDescriptor; var fs:Namespace = updateDescriptor.namespace("fs"); updateView.required = (updateDescriptor.fs::required == "true"); updateView.addEventListener( MyAppSays.DOWNLOAD_UPDATE, onDownloadUpdate ); updateView.addEventListener( MyAppSays.SKIP_UPDATE, onSkipUpdate );
}
override public function onRemove():void { // unhook from updater updater.removeEventListener( UpdateEvent.INITIALIZED, onInitialized ); updater.removeEventListener( UpdateEvent.DOWNLOAD_COMPLETE, onDownloadComplete ); updater.removeEventListener( StatusUpdateEvent.UPDATE_STATUS, onStatusUpdate ); updater.removeEventListener( StatusUpdateErrorEvent.UPDATE_ERROR, onStatusUpdateError ); updater.removeEventListener( ErrorEvent.ERROR, onError ); updater=null; // unhook from view if connected if (updateView != null) { updateView.removeEventListener( MyAppSays.DOWNLOAD_UPDATE, onDownloadUpdate ); updateView.removeEventListener( MyAppSays.SKIP_UPDATE, onSkipUpdate ); } }
protected function get updateView():AIRUpdateView { return viewComponent as AIRUpdateView; } private var updater:ApplicationUpdater = new ApplicationUpdater(); private var status:StatusUpdateEvent; } } myapp-version.xml is the standard updater file that is downloaded that tells the app about the update. Note I've added my own namespace to include the required field.
<?xml version="1.0" encoding="utf-8"?> <update xmlns="http://ns.adobe.com/air/framework/update/description/1.0" xmlns:fs="http://schemas.futurescale.com/air/framework/update/description/1.0"> <version>1.0.1 Maintenance Release</version> <url>http://myapp.com/air/MyApp.air</url> <description> <![CDATA[<UL><LI>This release includes self-serve trials.</LI></UL>]]></description> <fs:required>true</fs:required> </update>
|