« November 2005 | Main | February 2006 »
December 18, 2005
Day 6: Finished Product
2006-Apr-16My "port" of the Flickr PhotoSearch with Flex project from Flex 1.5 to Flex 2 is done. There is a link below where you can download the ZIP file and import it into your Flex Builder 2 workspace.
The code shown and provided by this article will not work with Flex 2 Beta 2 and beyond. This code was written with the Flex 2 Alpha 1 release and was meant to show what Flex 2 is or will be like.
This code has been ported to the latest Flex 2 Beta release on the labs.adobe.com site. Check in the showcase area.
|
Make sure you click this button within a browser that has the Flash Player 8.5 plug-in. Launch Application |
For Alpha-level software, Flex 2 did a great job, in opinion, and I don't think I'm being biased. Because I could not spend 100% of my time on this project it lasted over two week, so don't take that as an indication of how long it takes to work with Flex 2. Bear in mind there is a learning curve - even if you know Flex 1.5 already. You'll really want to understand what new things Flex 2 has to offer and how developing applications in Flex 2 differs from Flex 1.5.
To Port or Not to Port
I don't think you should port a perfectly good application from Flex 1.5 to Flex 2 unless the application falls into one of these categories:
- There is some deficiency in the application that Flex 2 can solve. For example some UI technique that your application lacks that is now possible with Flex 2.
- Your application is ready for its next version and you see features in Flex 2 that will benefit your application. Perhaps you wrote your own data-push code using XML sockets and you would like to abandon that in favor of Flex 2 Messaging (which is not yet available as an Alpha)
- You want to understand what Flex 2 is like and since you know your application thoroughly, porting it to Flex 2 will help you learn and understand Flex 2 better.
Keep in mind that porting an application is not just bringing it into Flex Builder 2 or running the Flex 2 compiler on the code. Sure you can fix the syntax errors and the data-typing issues, but are you really taking advantage of what Flex 2 has to offer? Just consider that before doing a port.
Caveat: when you do a port like this, it is essentially a re-write. This means you may introduce new errors.
The Flex 2 Code
Before you download this code, you must have the Flex 2 Alpha 1 installed. You must have the Flash Player 8.5 or this will not work.
Bear in mind this is the first public Alpha version of this software. Neither Flex Builder 2, Flex Framework 2, nor the Flash Player 8.5 have been optimized or tuned. While you may think the resulting SWF is large, just keep those facts in mind.
Flex 2 Update - Feb 1 2006
The source is no longer available from this entry. Instead, go to the Adobe Labs showcase page, run the Flickr PhotoSearch application and right-click on it to View the source.
PhotoSearch in Flex 2
Here's a brief list of what's different between the two versions:
- There is no need for a Flickr account - anyone can use this application. That's not a Flex 2 issue because I realized I did not need that requirement. Perhaps a later version will need authentication, but now you do not.
- Drag and drop to the Trashcan does not work correctly. It is because I cannot tell where the data is coming from - the Gallery, the Search History, or Favorites. I believe this will be rectified in the future. In the meantime, you can drag things from Search History and Favorites to the Trashcan where they will be discarded. But you can also drag Thumbnails from the Gallery to the Trashcan (it will not really delete the images from Flickr!) and you can duplicate images in Favorites. These last two things you should not be able to do, but you are not prevented from doing it right now.
- There is no Progress Bar because I could not figure out how to make the Gallery into a source for the Progress Bar. I decided not to spend too much time on this because it was not important.
- I used a TileList with the Thumbnail as a renderer versus using a Tile container and the Thumbnail as a child. To the end-user there is no visual difference. But I believe the application is now simpler.
- I used States in a number of places: the main application (welcome vs. running), Thumbnails (loading vs. image), SearchPanel (normal vs. advanced options), ImageViewer (loading vs. image), and even the Trashcan has multiple states.
There is more "code" in this Flex 2 version because I created a couple more classes, such as the SearchEvent class. I could have done that with the Flex 1.5, but Flex 2 encourages you to do this.
Flex 2 Differences
This is not meant to be an exhaustive list of the differences between Flex 1.5 and Flex 2. In fact, if I were to port another application I'm certain I would uncover other differences simply because I was following a different path. These items are in no particular order.
Data typing. You must data-type every variable now. This not only makes the code run faster, but you will also uncover errors sooner.
Runtime errors. Before Flash Player 8.5, errors during the execution of the SWF went unsaid. For example, if you simply said x += 5 without defining x you would not get a warning and the variable would assume to be undefined. In Flash Player 8.5 you will get a runtime error. Sometimes these unspoken errors were deliberately coded (I know I used that fact), but if they were not it could become difficult to find the error.
Packages. Formal packages are now introduced in ActionScript 3. You can see this in the com.macromedia.flickr package in this project.
Eclipse. Almost without saying, Eclipse is the base for the new Flex Builder 2. Java developers will find this a familiar environment. Flex Builder 2 is offered (or will be) in two flavors: a standalone version and a plug-in version if you already use an Eclipse environment.
Cell renderering. It is now much, much easier to make renderers for lists of all sorts. Most Flex classes now implement the DataObject interface and include a dataObject property which is set when the class is used as a cell renderer. The dataObject contains the current record for which the renderer is being used.
Cell editing. This project did not use any cell editors, but they are worth mentioning. In Flex 1.5 you had only a cell renderer. In Flex 2, editing a cell is different from rendering a cell. For example, if you have a Boolean value for a cell, you can render it as "Yes" and "No" while editing it with the CheckBox derivitive.
States. The idea of a State is simple: the user interface has a different appearance in each state is may be used. The SearchPanel is a good example. It has a normal state which shows just the TextInput and button for doing a search. But there is also an "advanced" button which changes the State and causes more controls to appear. Now you can certainly do this would states (just look at the Flex 1.5 version of this code), but having all of the controls appear and disappear just by changing a variable makes it easy and reliable.
Collections. Arrays should not be used as dataProviders for List (or Tree) classes unless you are happy with 1-time binding. Collections (such as ArrayCollection) now implement the proper ChangeEvent code.
Bindable. In Flex 1.5 all variables were automatically flagged as bindable. In Flex 2 you must flag a variable using the compiler directive, [Bindable], immediately above the variable declaration.
Import. You'll find you need to import more classes than before. That's because a number of classes that were included by default are no longer brought into the SWF. You will need to understand and use the import statement.
Events. Flex (and Flash) are event driven. This is even more apparent in Flex 2/ActionScript 3. Many of the classes now dispatch very specific event types. For example, the ListEvent, is dispatched by the list-bases classes. Before you could just dispatch an Object and add whatever values you like. Now the dispatchEvent function demands that its first argument be derived from the base Event class, making it impossible (or at lease, very hard) to do this.
Event Model. The event model is a bit more complex now. For example, you can have events bubble up from lower classes without having to make all of the intervening classes pass them on. You can see this in the events dispatched by the ImageInfo class.
Alphas and Scale. In Flex 2, the values for Alpha are 0 to 1 (not 0 to 100) and scale works the same way.
Layout Constraints. New to Flex 2 are layoutConstraints. When using a Canvas container you can specify how UI components are positioned relative to the Canvas' side in addition to or in place of their (x,y) location.
I'm sure there are obvious things I'm forgetting.
Conclusion
Don't port a perfectly good Flex 1.5 just to bring it into Flex 2. There is effort involved. It is certainly a good learning exercise, but to just fix compliation and link errors will not teach you anything. You need to sit down with Flex 2 and understand what it is capable of.
I'm very pleased with my approach - I used my Flex 1.5 version as a guide, but re-coded the UI and ported the AS classes. I also added classes, specifically events, to fill in the missing pieces.
While the application has a few issues, it is a useable application that demonstrates some of the new features of Flex 2. Remember, this is Alpha software and with that are no guarantees that this is the final code. New functions may come along in the future and whole classes may be dropped.
In the meantime, have fun!
Posted by pent at 01:36 PM | Comments (17)
December 17, 2005
Please wait...
If you've been anxiously waiting for the finished Flex 2 version of my Flickr PhotoSearch app - I'm almost done.
As many of you know, Macromedia merged with Adobe and we've all been learning new things, so my Flickr project has been on hold for a bit. I promise I will get to it this week, in time for the holidays.
Thanks for your patience.
Posted by pent at 07:22 PM | Comments (2)
December 12, 2005
Day 5: Nearing the End
Getting closer now. It is really just finding solutions at this point. For example, the old issue of having one of something is not the same as having an Array of 1 item, bit me. I had to use the ever-trusty ArrayUtil class:
var ac:ArrayCollection = new ArrayCollection( ArrayUtil.toArray( tags ) );
I spent a number of hours getting the ImageView and ImageInfo screens to work again. I found myself not really looking at the Flex 1.5 version code and just re-coded in the spirit of Flex 2. Sometimes I did reach over for an algorithm or technique, but mostly I just wrote new code. I guess I like typing. When you are thinking of using Flex 2 on an existing Flex 1.5 project, bear in mind that you will have to change code in some places, so approaching the application from a fresh perspective may turn out to be a better strategy in the long run.
ImageViewer and ImageInfo
The ImageViewer is the TitleWindow that pops up when you click on an image in either the Favorites Panel or the Photo Gallery. You'll definitely want to use the itemClick event instead of change on any list you intend to use with drag and drop. Remember: change signifies a change in the selection state of the list.
The ImageViewer works like this: a Thumbnail is clicked which causes a itemClick to be raised by the TileList. This is caught by the PhotoSearch component. If the ImageViewer is not already in existance, a new one is created via the PopUpManager. A number of events are registered for the ImageViewer (more on that below) and the ImageViewer is displayed.
Just before the click event handler exits, it makes a request to the Flickr service to get the image details. When those details arrive, they are given to the ImageViewer via its photoInfo property (a setter function).
When the ImageViewer first appears there is no image and no extra information (remember, that just went off to Flickr). So the ImageViewer is in its base state which shows the word "Loading..." in the lower right corner. As soon as the image information is given to it (via the photoInfo property), the ImageViewer shifts into the "loadedState".
The user can click on the Info icon to see more details. I decided this would be another opportunity to use States. The ImageInfo panel would be displayed in the ImageViewer's "infoState" which would be based upon the "loadedState". Here are a couple of benefits to doing that:
- I do not have to write any code to make the ImageInfo appear; the state-change mechanism does that
- If I am viewing image information and I click on another image in the gallery, this causes the ImageViewer to go to the base state and the ImageInfo is taken down - and - the owner's name is replaced with "Loading..."
This is all very neat and clean. And while you may have been thinking that you could just use a ViewStack instead of States, what I just described cannot be done with a ViewStack. Plus, having written this out the long way in Flex 1.5, I definitely think States have provided a very useful alternative.
Events
I was originally going to title this section, Death by One Thousand Events but that was too dramatic, even for me. Truth is, Events are our friends. Our very good friends. In Flex 2, it is just finding the right event.
Since this is an exploration of what is new with Flex 2, I decided that it was time I bubbled an event. That means I want a lower component to dispatch an event that a higher component (in the child-parent heirarchy) will catch, but without needing to involve any of the components in between.
For example, when you pick the info button on the ImageViewer, you see in the ImageInfo component a list of tags for an image. A search is kicked off if you click one of those tags. So how to do this?
The tags which appear in the ImageInfo window are Links. When a link is clicked, its click event handler dispatchs a com.macromedia.flickr.SearchEvent which contains the tag.
I want the SearchEvent to be caught by the PhotoSearch component, not ImageInfo's parent, ImageViewer. In Flex 1.5 you have to intercept the event in ImageViewer, then dispatch it again so PhotoSearch can receive it. In Flex 2, I just have PhotoSearch listen for a SearchEvent on ImageViewer. Very simple. Very loosely coupled. We like that.
Another cool thing I did, which is not specific to Flex 2, is have other components handle events. For example, on the ImageViewer you can click the Next Image button and the next image in the gallery (Favorites or Photo) will appear. The ImageViewer dispatches the nextImage event.
In the PhotoSearch class, I set up the event listener like this:
imageViewer.addEventListener("nextImage",gallery.nextImage);
imageViewer.addEventListener("nextImage",favorites.nextImage);
Now PhotoSearch isn't even involved with these events as only the Gallery and Favorites care about them. What this means however, is that both components will receive the same event - it is up to the components to know if they should handle it or not.
To make that happen, the ImageViewer is told for which TileList it is displaying images. That is, whenever an image is clicked, not only is that image's information given to the ImageViewer, that image's TileList is given, too.
Both the Gallery and Favorites, in their nextImage event handler, ask the event target (the ImageViewer) what gallery it is showing and if it is themselves, that gallery dispatches an imageClick event as if the user click on the image.
Again, loosely coupled, re-used event handling code.
Problems
Well, it would not be interesting without some problems to solve. Here's today's list of things I either could not figure out or have determined are missing in the Flex 2 Alpha 1 release.
- Effects for state changes. I was able to, with the help of a Flex 2 Forum member, make the ImageInfo window appear to Fade in, but I cannot get it to Fade out. I believe this is due in part to how children added by a State are removed (rather unceremoniously, no double) so there is no time for an effect to take place. We should see effects for States appear in a later release.
- Some of the image information descriptions contain HTML, specifically hyper-links. In Flex 1.5 I used a StyleSheet object to make any links appear blue and have an underline when you hover the mouse over them. That appears to be broken in Flex 2. So the link is there and it works, you just don't know it until the mouse pointer changes to a hand. I'm sure this will get fixed.
- It took me the longest time to figure out how to create the Links for the tags in ActionScript. I don't know why, it is so easy - it is just different from Flex 1.5. For example, in Flex 1.5 you used one of the
createmethods like,createChild( datatype, undefined, initprops ). In Flex 2, it is easier. See the code snippet below. - Drag and drop are easy, to a point. Since I am using TileList instead of Tile, I can have the TileList handle the initiation of a drag operation simply by coding
dragEnabled="true"on their definitions. The problem is, I cannot tell how started a drag operation. For example, you are supposed to be able to drag a Favorite over the Trashcan to delete it, but you are not supposed to be able to drag a Thumbnail from the Gallery. Only a drag out of Favorites should light up the Trashcan. But I cannot tell who started the drag, so the Trashcan cannot tell the difference. The jury is out on this one.
Here's how to create a Link using ActionScript. You'll find this in the ImageInfo component code when I put the code up on the weblog server.
import mx.controls.Link; ... var link:Link = new Link(); link.label = tagName; link.addEventListener( "click", handleLinkClick ); addChild(link);
Conslusion
Well, I'm almost ready to publish this code. I have to put in more comments, clean up some debug code, and hope that I can get drag and drop to work.
Stay tuned...
Posted by pent at 08:06 AM | Comments (1)
December 09, 2005
Day 4: Making Progress
At this point I have a lot more working. Here's what happened to get to this point.
Recap
Once I was able to receive the XML from Flickr containing the list of photos, I had to give it to the TileList. Then the TileList's listItemRenderer would take the data and build the URL to display the actual thumbnail image. This turned out to be pretty easy:
- The Gallery component has a setter for
dataProviderwhich expects an Array. This will be the Array of photo information from Flickr. - The Gallery
dataProvidersetter takes this Array and creates a new ArrayCollection.[Bindable] public var ac:ArrayCollection; ... // inside of dataProvider setter: ac = new ArrayCollection( ar );
- When the TileList has its dataProvider changed, it causes all of the renderers to receive new dataObjects (the elements of the ArrayCollection).
- The Thumbnail renderer has an override to the setter for
dataObjectwhich causes the Thumbnail to switch to its base state that displays the "Loading" message and hides the image. - The
dataObjectsetter also forms the URL for the thumbnail and assigns it to the Image. - When the Image
completeevent triggers, the Thumbnail switches to the image state, displaying the image and hiding the label.
That seems complicated, but the code is pretty clean right now. I know things need to become more complex in the next few days, but for right now, I'm getting to see thumbnails when I enter a tag into the Search Panel.
Not Much Progress
My next task was to get the Progress Bar to work. This is located in the ControlBar area of the Gallery panel and shows how the thumbnails are loading. In the Flex 1.5 version, I made the Gallery itself a source for the Progress Bar. This means that the Gallery dispatches progress and complete as well as implementing byteLoaded and bytesTotal methods. That was easy with the Gallery being a Tile container. But in my Flex 2 Alpha 1 version, the Gallery is a TileList and I need someway to know when each image has loaded.
I could not find out how a listItemRenderer knows what control it is a renderer for. In Flex 1.5, each cell renderer was given an object, listOwner. I do not see anywhere in the documentation what this is.
My idea was to tell the listOwner when an image was complete so it could bump up its byte count and be a good source to the Progress Bar.
Since I don't know how to do that know, I'm leaving out the Progress Bar.
Decisions
Up to this point the path has been straightforward. Now there are some choices: do I get the Image Viewer (the TitleWindow that displays the larger size image when you click) working? Do I get drag-and-drop working? Do I finish out the Search Panel and add the advanced options? Do I get saving the search history working?
I decided to do a little bit of everything.
Drag and Drop
The Flex 2 docs on how to do drag and drop are pretty good. I dragEnabled my TileList and it merrily allowed me to drag images all around. However, as soon as I pressed down the mouse, the TileList emitted a change event. Oops. After a quick read through the documentation, I found that I needed to use itemClick instead. You see, I want the ImageViewer to pop up when the mouse is clicked; I don't want it to pop up when I drag an image to the Favorites panel.
In the Favorites panel I also have TileList and on that I placed event handlers for dragEnter, dragOver and dragDrop.
The dragEnter event handler is where you determine if your component is a valid drop target. You get the dragSource from the DragEvent (in Flex 2 there are many more classes and this one is specific to drag and drop) and look at the dragFormats supported. If the items being dragged are what your component can handle, then you:
- Invoke
DragManager.acceptDragDrop(event.target)which is different from Flex 1.5. - Invoke
DragManager.showFeedback(DragManager.LINK). The default is a MOVE which, as it turns out, really removes the item. When I first implemented this in Flex 2, I dragged a thumbnail from the Gallery and dropped it onto the Favorites panel and the item was removed from the Gallery! So I changed this to a LINK operation. I could also have used COPY, but LINK seemed more appropriate.
The dragOver event handler is optional. I just set the focus to the Favorites' TileList.
The dragDrop event hanlder is where you add the data to the drop target. Since the data is what goes into the Gallery's ArrayCollection, it is perfectly suitable for the Favorites TileList since that also uses the Thumbnail for its listItemRenderer. All I did was add the data from the dragSource to the Favorites TileList dataProvider (also an ArrayCollection) and voila - thumbnail appears.
Search History
Once a search is successful, I want to remember it in the DataGrid, aka SearchHistory. I already had code to do this from the Flex 1.5 version, so I inserted that into the SearchHistory component and, with an adjustment for the ArrayCollection, things worked.
When using an ArrayCollection, you do use the addItem method just as you would have with an Array in Flex 1.5. Except, merely using addItem does not trigger an event to update the TileList. You also have to call notifyItemUpdate like this:
if( ac == null ) ac = new ArrayCollection(); ac.addItem( searchCriteria ); ac.notifyItemUpdate( searchCriteria );
When the SearchHistory DataGrid is bound to the ArrayCollection ac the notifyItemUpdate causes the new criteria to be placed into the DataGrid.
ImageViewer
This last task was much more complex than I would have liked. In hindsight, there wasn't much to do, but with 2 bugs in the Flex 2 Alpha 1, I had to find work-arounds.
First, I've only made the ImageViewer show the larger size image of the one clicked in either the Gallery or Favorites. None of the controls (information, print, etc.) work yet. Nor does the image information (tags, description, etc.). That will be later.
First issue: TitleWindows are very transparent. Panels are now given an alpha of about .6 I think. TitleWindows seem to be about .2 - nearly invisible. I wound up changing their style to have headerColors and an alpha of 1.
Second issue: The click event means 'click anywhere in the window', not just the close box. This means that if you have the ImageViewer open and click one of the control buttons (eg, Print), it triggers the click event for the TitleWindow. I had a listener for this event in the PhotoSearch component which dismisses the window and sets its instance variable to null. So no matter where I clicked, the ImageViewer disappeared.
To circumvent this, I removed the close button and added a specific "Close" button on the ImageViewer's ControlBar which dispatches a "close" event. Now the only way to dismiss the ImageViewer is to click that button.
Third Issue: Images, on the complete event have their width and height properties set to zero. This means that I could not resize the ImageViewer to see the entire larger image the way I did in Flex 1.5.
As I reported in the last journal, I found a response to that from likn who suggested that I use img.content.width and img.content.height. That worked. So now the ImageViewer reshapes itself properly.
Finally, in keeping with the flow from the Thumbnail, the ImageViewer now builds the URL to the full-size image itself, rather than it being part of the information in the TileList dataProvider ArrayCollection.
Conclusion
This 'day' was more intense as I uncovered some problems with the Flex 2 Alpha 1, but found reasonable work-arounds for them. The Flickr PhotoSearch application is about 50% of the way to running as a Flex 2 application. I need to save the search history and favorites list, get the image information/details to display, allow searchs from the image details tags, get searching by Flickr user to work, and more.
Getting drag and drop from the Gallery to the Favorites went really well. Getting the ImageViewer to work was harder due to the click event issue and then the image sizing issue. And I was disappointed by the Progress Bar, which I would probably have had issues with in Flex 1.5 if I had used a TileList there, too.
Stay turned...
Posted by pent at 06:08 PM | Comments (0)
December 06, 2005
Day 3: Here Comes Trouble
I finally got the user interface laid out without having to port any of the Flex 1.5 version files. This was a good thing because it forced me to use the Flex Builder 2 design tools and it made things much easier. The next task is to get the program to connect to Flickr and return some pictures.
As I suspected, it wasn't going to work right away. The com.macromedia.flickr.Flickr class was by and large fine. Instead of passing in a reference to an HTTPService that was created in the main application, the Flex 2 Flickr class creates an instance of HTTPService in its constructor function. It also used addEventListener to hook up result and fault handlers.
Delete Delegate
One thing I forgot to mention in my Day 1 notes is that you no longer need to use mx.utils.Delegate to scope your event handlers.
In other words, when you do this in Flex 1.5:
button.addEventListener("click",mx.utils.Delegate.create(this,doSearch));
you now do this in Flex 2:
button.addEventListener("click", doSearch);
ActionScript 3 now knows how to scope the functions properly.
Back to the Story
When I first created the Flex 1.5 PhotoSearch program, I found it a little confusing as to how you log in or authorize the application. But I followed the instructions on the Flickr site and got the program to work smoothly. So I used the same code with a minor change: getURL has been deprecated in favor of flash.network.navigateToURL which is just as simple to use.
I ran the program and the first phase, which gets what Flickr calls a FROB, returned what appeared to be a valid string. At least no error was returned and the value looks like it ought to look. The next phase involves taking that frob and, along with my application's API key and shared secret, uses navigateToURL to authenticate the session and get a token. This works like a charm in the Flex 1.5 version.
But in the Flex 2.0 version I cannot get it to work consistently. More than half the time I get an error from Flickr telling me my signature is incorrect. But then other times it works fine. I even ran the Flex 1.5 version in the Flash Player 8.5 Alpha and it worked consistently.
I tried everything, including getting a new API key from Flickr. Nothing makes it work consistently.
So I decided to abandon the Flickr authorization process for now. This is acceptable because PhotoSearch doesn't really need to be authenticated because it does not modify anything - it is just a search tool. I didn't realize this when I was first writing the Flex 1.5 version and I may go back and remove the need to authenticate from that version of PhotoSearch.
Since I didn't need to get the application authorizated, I still wanted to use States, so I replaced the AuthorizationWindow with a WelcomeWindow which just explains the program a little.
Thumbnails
Now that the trial of getting into Flickr has been resolved, I wanted to get searching by tags to work and displaying some images.
Remember that this version is using a TileList which is a control and not the Tile which is a container. This means my Thumbnail component now has to be a renderer. This turns out to be very easy in Flex 2.
A simple Thumbnail could look like this:
<mx:Canvas width="104" height="104" xmlns:mx="..." >
<mx:Image x="2" y="2" source="{dataObject.thumbURL}" />
</mx:Canvas>
If the dataProvider to the TileList is a collection of objects, each of which has a field called "thumbURL" that points to a remote image, then the TileList will display the thumbnail.
But I want this to be a bit more complicated because I want the user to see "Loading" while waiting for the image to display. So the actual Thumbnail has 2 states: loading and image. The loading state has a Label component with the word, "Loading" in it. The image state has the image of the thumbnail. Both the image and the label are contained in a Canvas and the Canvas provides the root tag for my Thumbnail.
I created the two states and set the image's complete event to switch from the loading state to the image state. But as I mentioned in the last article, I need to switch back to the loading state when a new search is done or else the images will not change (or at least, they won't show "Loading" while the user waits).
The Thumbnail now looks like this:
<mx:Canvas width="104" height="104" xmlns:mx="...">
<mx:states>
<mx:State name="imageState">
<mx:RemoveChild child="{loadingLabel}" />
<mx:PropertyOverride target="{img}" property="visible" value="true" />
<mx:PropertyOverride target="{img}" property="alpha" value=".8" />
</mx:State>
</mx:states>
<mx:Script>
<![[
[Bindable]
public var thumbURL:String;
override public function set dataObject( value:Object ) : Void {
super.dataObject = value;
currentState = ""; // reset
thumbURL = "http://static.flickr.com/"+dataObject.server+"/"+dataObject.id+"_"+dataObject.secret+"_t.jpg";
}
]]>
</mx:Script>
<mx:Image id="img" x="2" y="2" complete="currentState='imageState'" source="{thumbURL}" visible="false" />
<mx:Label id="loadingLabel" text="Loading" y="44" width="100%" textAlign="center" />
</mx:Canvas>
Notice that the source for the Image is bound to the variable thumbURL. Then note that the setter for the dataObject property has been overriden (it took me a number of hours before I went back to the documentation and found this). This override does 3 important things:
- it invokes the
super.dataObjectsetter method; - it sets the state to the base state, causing the image to become invisible and the label to reappear;
- it creates the
thumbURLvariable from the items in the dataObject. In the Flex 1.5 version I created this value in the Gallery when I received the data from Flickr. Now this is encapsulated as part of the thumbnail - it knows how to form the correct URL.
The data binding on the image's source property causes the image to be loaded. When the load completes, the state of the Thumbnail switches over to the image state: the label is removed and the image is made visible. When a new search is made the process repeats itself when the TileList calls upon the dataObject setter.
There seems to be a bug with the Image component in that the image's width and height are reported to be zero when the complete event is fired. In the Flex 1.5 version, I use that event as an opportunity to center the image within the Thumbnail. I posted a question on the Flex 2 Framework forum and received a reply (thanks xxx) that I should try not img.width, but img.content.width. That worked, so now the Thumbnails appear centered once again. I will apply this technique to the ImageViewer when the time comes.
Making the Request and Processing the Results
When the Go Search button is picked, the text from the TextInput field is given to the Flickr class and then an HTTP request is made to the Flickr service.
When the result comes back, the Flickr class dispatches a com.macromedia.flickr.FlickrEvent which contains the results.
The PhotoSearch component has an event listener for this which looks something like:
private function searchComplete( event:FlickrEvent ) : Void {
totalPhotos = event.data.total; // for the bottom of the Gallery
totalPages = event.data.pages; // for the bottom of the Gallery
pageNumber.value = event.data.page; // sets NumericStepper
gallery.dataProvider = data.photo; // this is an array
}
The searchComplete method sets some variables that are data-bound to controls and then it passes the list of photos over to the Gallery.
The Gallery component has a setter called dataProvider which simply converts the Array of photos in the result to an ArrayCollection> which is bound to the TileList displaying the Thumbnails.
In Flex 1.5 you could use an Array as the dataProvider to a DataGrid or any other List control. In Flex 2, you need to use a collection so I converted the photos into an ArrayCollection. In the Flex 1.5 version I took the result list of photo information and created an Array of com.macromedia.flickr.PhotoInfo objects, which included setting the URLs to the thumbnail and full-size images. In the Flex 2 version, I do not need to use the PhotoInfo class (because Thumbnail and make its own URL).
Conclusion
At first I was disappointed that I could not get this Flex 2 version of PhotoSearch to authenticate with Flickr. But when I realized I didn't need to do that, I switched gears and moved toward getting the thumbnails to display.
In the Day 2 journal entry I said that I did not want to port the Flex 1.5 application to Flex 2. Since I decided to use a TileList instead of a Tile container, this was a good decision. It made me rethink how the Thumbnail code works and it in fact simplified it. Using states in the Thumbnail made it easier (less code) to switch between displaying the "Loading" message and the image.
When a new search is made and the results come back, the TileList (or the Flex 2 framework) takes the contents of the dataProvider and invokes the dataObject setter on each renderer, aka Thumbnail. Because I overrode the setter, I was able to intercept this change, reset the state, and build a new URL for the new image.
In the Flex 1.5, the setValue method takes care of that, but setValue also gets called on many other occaisons, such as when the mouse rolls over the cell, so it can be difficult to code setValue properly.
My conclusion at the end of this day is that Flex 2 is good stuff. Once over the learning curve, the code has become more streamlined and even more object-oriented.
Posted by pent at 07:22 PM | Comments (0)
December 05, 2005
Day 2: A New State of Affairs
Once I had the simpler classes compiling, I had to move on to getting the user interface developed. I did not want to just copy the original Flex 1.5 version for several reasons.
- First, I wanted to use Flex Builder 2 to make the components from scratch. I simply wanted the experience of using the design tools.
- Second, I knew there would be some compatability issues and may be lots of errors. I didn't want to have to solve every error in all the files just to see something working. Imagine if this were an application that had a hundred components. I would have to make sure they all compiled cleanly, then cross my fingers that it would work. I did not want to face that amount of work (and frustration). So I decided the incremental approach was better.
- Finally, there are some new features in Flex 2 that I wanted to use and it would be easier to add them to a clean slate than to retrofit them.
The second point is very important, especially if you are porting a working application. If you copy all of your files and think you can just get the compilation errors removed and it will work, think again. In my first day's experience I found that I had to make some adjustments in how the application was written. And that was with a handful of files. So I believed it would be best to start over, but to use my already working, Flex 1.5 application, as my base. Many of the functions I wrote would still work and I would not have to re-write everything.

States
One of the new features of Flex 2 are states. States are a way of dividing a component into 2 or more related behaviors. For example, in this PhotoSearch application there is the "authorization" state and the "running" state. In the authorization state you see the panels that direct you through the Flickr authorization process. When that is satisfied the application switches to the running state. Likewise on the Search Panel, there is the normal state with just the TextInput field for the tags, the Search button and the Advanced Options button. When you pick the Advanced Options button, the state of the Search Panel changes to show more options. Click the Advanced Options button toggles the Search Panel between these two states.
In the Flex 1.5 version, I used a ViewStack in place of states. But the state concept is much more powerful.
In the Flex 1.5 version of PhotoSearch I used a Tile with a number of Thumbnail components as children. I debated whether or not to use TileList and decided that using a Tile container was easier. In the Flex 2 version I am going to use a TileList for both the Gallery and the Favorites panels. I'm doing it this way not only to be different, but because cell renderers (now called listItemRenderers) are so much easier to write.
I began by creating my main application and the AuthorizationWindow component. Since I knew that I wanted to use states, I also created a PhotoSearch component to handle the running state. In Flex Builder 2 I set up the authorization state to display the AuthorizationWindow and created a new state, "runningState", to display the PhotoSearch component. I tested this code by simply having the AuthorizationWindow dispatch an event when the button was picked which caused the main application to switch to the running state.
The AuthorizationWindow I initially created just had a button and it was time to modify it. I suddenly realized that this component also had states: the initial state and the "permission granted" state. So I did the same thing: created the first state with the title and text areas and a button. Then I created another state and changed the text and button label. When you get is something like this:
<mx:states>
<mx:State name="runningState">
<mx:PropertyOverride target="{text1}" property="text" value="Return to this window..." />
<mx:PropertyOverride target="{text2}" property="text" value="Once you are done..." />
<mx:PropertyOverride target="{button1}" property="label" value="Complete Authorization" />
</mx:State>
</mx:states>
In Flex 2, when a component's state is changed, the PropertyOverride instructions are employed - in this case the text is changed and so is the button's label. If you were to switch back to the initial state, those overrides are removed and the controls revert to their original values.
With the State definition you can also add and remove whole components along with changing components' properties.
I think you will find states to be a very convenient way to express a control's multiple personalities, if that's appropriate. For example, I think the Thumbnails also have 2 states: the one where the "Loading" text is displayed and the other where the image is displayed.
ListItemRenderers
While my goal at the moment is not to run the application, just lay it out, I did want to tinker with some of the new features to see how they work, if they work in this Alpha release, and see how to adopt them to the application. I already tried out states and am happy with how easy they are to work with.
One of the biggest - and best - changes in Flex 2 is the cell renderer, now known as the ListItemRenderer. If you have used cell renderers in Flex 1.5, you can use the essence of your code, but you should create a new component and not port it.
You have many options how how you want to use renderers: there are drop-in components, in line components, and custom components which are like the Flex 1.5 components. I know that I'm going to use TileList this time and so I wanted to know how to create a custom component (I'll be using it in both the Gallery and Favorites panels). This is my initial Thumbnail:
<mx:Canvas width="104" height="104" xmlns:mx="..." ><mx:states>
<mx:State name="imageState">
<mx:RemoveChild child="{loadingLabel}" />
<mx:PropertyOverride target="{img}" property="visible" value="true" />
</mx:State>
</mx:states><mx:Script>
<![CDATA[
private function showImage() : Void {
currentState = "imageState";
}
]]>
</mx:Script><mx:Image id="img" x="2" y="2" source="{dataObject.thumbURL}"
visible="false"
complete="showImage()" /><mx:Label textAlign="center" id="loadingLabel" text="Loading" x="0" y="40" width="104" />
</mx:Canvas>
You'll notice the absence of the setValue method and the presence of dataObject. Practically every Flex component can now be used as a renderer because they all implement the IListItemRenderer API. One of the properties the components now have is a dataObject which is set in the instance to be the data for that cell in the list. In the PhotoSearch application it will be the PhotoInfo which will have a property. The ThumbNail will be showing with a "Loading" label and as soon as the image is loaded, it will switch to displaying the image and not the label. At least that's the idea. We'll see what happens when I get this running.
I can see a potential problem with this already. What happens when the data changes - I do a new search? As the TileList is given a new set of data, I presume that each instance of the Thumbnail renderer is going to change the dataObject. So how does the state get reset to show the "Loading" message until the images arrive?
After doing some more reading, I think I found the answer: override the setting of the dataObject. If it works, it will be deceptively simple. Here's what I've changed to the Thumbnail.mxml file:
override public function set dataObject( value:Object ) : Void
{
super.dataObject = value;
currentState = "";
}
I took the function signature right out of the Flex 2 documentation. In Flex 2 you have to indicate you are overriding a method (hence the override keyword). The dataObject is passed to the super method and the currentState is reset. With luck, this will work.
DataProvider and Bindable
In Flex 1.5 it was pretty common to use an Array as a dataProvider to a List component, such as the DataGrid. That's because in Flex, the Array class implements the DataProviderAPI which provides data binding and change events. I Flex 2.0, the Array no longer implements the DataProviderAPI.
Flex 2 provides a more robust set of Collection classes, much like Java. For the Search History (DataGrid) and TileList components, I decided to use the ArrayCollection class since that was most like what I was using and has many of the same functions as the Flex 1.5 DataProviderAPI.
What this means is that when Flickr returns the array of information about the photos which meet the search criteria, I cannot just bind that result, I have to convert it to an ArrayCollection. Fortunately, that turns out to be pretty easy:
import mx.collections.ArrayCollection; [Bindable] public var ac:ArrayCollection = new ArrayCollection( photos );
You'll notice that a) I had to place the [Bindable] metatag above the variable declaration and b) I had to declare the variable public. The latter is a Flex 2 Alpha restriction.
You must place [Bindable] above every variable or class (to include all variables in the class) to tell the Flex 2 compiler for which variables data-binding code variables should be generated; in Flex 1.5 the code was generated for all variables.
Conclusion
Overall the design process went well. There was a bit of time where some of my panels did not have titles when I ran them, but then I discovered the "Run->Clean" option in Flex Builder 2 which did a full recompile and that seemed to have fixed everything.
I needed to write some quick experimental code to get the feel for renderers and dataProviders, and the documentation really helped on that. I only used the simple ArrayCollection, but I can see there are more options now than before.
At this point I have the PhotoSearch application displaying the AuthorizationWindow. When I click the button, it switches to its next state and when I click the Continue button, the application switches to the running state, displaying the three main windows and the trash can.
In the Search Panel I can click the Advanced Button and it causes the other options to appear. And that's all the program does. The core code in the com.macromedia.flickr package is not tied into this yet. That's the next task.
Stay tuned.
Posted by pent at 01:38 PM | Comments (2)
December 02, 2005
Day 1: The Port Begins
This section of my blog is devoted to the port of my recent Flickr PhotoSearch with Macromedia Flex application. As of this writing I do not have the app ported to Flex 2, I have just started. I thought it might be good to do a running journal of the effort just so I don't lose any interesting and useful facts along the way.
First, let me set some expectations. This is Flex 2 Alpha we are talking about. This is not a final product. It may turn out that I redo this effort a couple of times. I'm not even expecting to get through it all now nor have a working app. If it does work, then all the credit goes to the Flex 2 team.
Second, I think most people who have running Flex 1.5 applications will not port them to Flex 2 unless there is a compelling reason. Oh, you may want to add new features to your app once you see what Flex 2 has to offer, but I don't think you'd take a perfectly good application and port it. Afterall, you can run your Flex 1.5 app in the Flash Player 8.5 and just get a better performing app. My goal here is not a 'port' this application, but to enhance it with new Flex features and to take advantage of the improvements Flex 2 offers over Flex 1.5.
With the spirit of Rewrite sitting on my shoulder, I decided to take the plunge.
My plan of attack is to start at the bottom and work up. Afterall, I have to get the classes that are used throughout the application to compile before I can get the rest to work. So I brought over the com.macromedia.flickr.* files - the package in Flex 2/ActionScript 3.0 terms.
Even with my careful planning of putting 'public' and 'private' before all of my methods and variables and with data-typing everything there were some issues to resolve.
- I didn't data-type everything as it turns out. Some little things slipped by me.
- I had to change every
undefinedtonull. I personally likeundefined. It means something to me. A variable that is set tonullhas a value. But what can I do? - Of course I had to use the
packagesyntax in the files. Could not have done that in Flex 1.5. - And then there were the Events. Those I had not data-typed and now, not only did I have to select a data-type, I also had to fix my quick syntax of
dispatch( {type:"click"} )to use a real Event class as the argument todispatchEvent.
Data-typing
Using data-types is a good thing. Your code is more readable, the code executes faster, and errors can be found sooner. Plus you can alway use Object if you aren't sure. This is especially true if you have a dynamic object - one that gets new properties or members at runtime. For example:
var test:Object = new Object(); test.firstName = "Peter";
That is perfectly acceptable in ActionScript 3.
Numbers are another matter. In AS3 you now have int and uint. There may also be floating point data-types, too; I haven't looked them up yet. But the Number type is still there and should be used for values which will exceed the capacity of int and uint. What that means is something simple like:
for( var i:Number=0; i < dp.length; i++) { /* do something */ }
should now be changed to:
for( var i:int=0; i < dp.length; i++) { /* do something */ }
and the loop will run much faster.
I retained the Number type for use in the MD5 class because the produces very large integers during the encoding process.
Events
The com.macromedia.flickr.Flickr class was the biggest challenge. The Flickr class in Flex 1.5 extended UIObject so I could easily dispatch events. For Flex 2, I have the class extending EventDispatcher. That gets me the functions (dispatchEvent, addEventListener, etc.), but I still need events.
In the Flex 1.5 version, I just dispatched events without thinking about them. For example, when there is a failure from Flicker, I just dispatched a "fail" event with the Flickr code and message. For others, I just dispatched a simple event with a type. Cannot do this so easily in Flex 2.
So I decided to embrace the Event system and created a FlickrEvent class which extends the flash.events.Event class. I made the constructor of my class take 4 arguments and made 3 of them have default values:
package com.macromedia.flickr {
class FlickrEvent extends flash.events.Event {
public function FlickrEvent( type:String, data:Object=null, code:Number=-1, msg:String=null ) {
super(type); // there are other arguments to this which I'm sure I will use in the near future.
this.data = data;
this.code = code;
this.msg = msg;
}
// data definitions here
}
Now I can easily dispatch events from the Flickr class:
dispatchEvent( new FlickrEvent("authorized") );
dispatchEvent( new FlickrEvent("photos",photos) );
dispatchEvent( new FlickrEvent("fail", null, rsp.code, rsp.msg) );
HTTPService
Along with events was how to use HTTPService in Flex 2. Turns out there isn't much difference, but there is a new way to use it. First, you can still use the MXML tag. But now you can create instances of HTTPService from ActionScript. This is really how I wanted to use it in the Flickr class anyway. In the Flex 1.5 version, it was awkward to declare the HTTPService as an MXML tag and then pass a reference to it to the Flickr class which is the place it was being used. Now in the Flex 2 version, the Flickr class creates an instance of HTTPService and everything is nicely encapsulated.
getURL
If you didn't know it, getURL is history in Flex 2. The Flex 2 documentation tells you why. All I had to do was change over to navigateToURL and include an import statement to find it. Oh, and use the URLRequest on the URL string. But that was all.
Flex Builder 2 Quirks
I've found Flex Builder 2 to be quite stable and pretty quick. Except for the following things:
- It can take a long time to build the project when you save a file. I eventually turned the automatic build off. Now I realize Flex Builder is not optimized or tuned yet, so I'm not complaining.
- Double-clicking does not select what I want. For example, if I have
event.typeand I double-click the mouse over "type" it selects "event.type". If I have_styleand I double-click the mouse over it, Flex Builder selects just "style" and not the preceeding underscore. May be there is a settng for this, but any code editor ought to know the rules of variable names. - Not everything is documented. Again, Flex 2 is now in Alpha, so I shouldn't complain. But it is a little frustrating trying to figure out where some class is and how to use it.
Conclusion
In this phase of the port, the Flickr class has undergone the most change. It now contains its own instance of HTTPService, throws its own events, and should work like a charm. I hope.
That's all I have for this entry. At this point the base classes have been ported and compile cleanly. Now it is time to build the UI.
Posted by pent at 01:21 PM | Comments (3)