Friday, June 12, 2009

TcozTwitter: A Twitter Client using Adobe Air and the Flex Mate Framework - Day 5 : Multiple views, control bar, and Mate wishlist item

This is a continuation of a series of articles on my experiments with writing a Twitter desktop client using the Twitter API, the Mate Framework for Flex, and Adobe Air. Previous blog posts are here at Day 1, Day 2, Day 3, and Day 4.

This time around, I decided to get two views working: the public timeline, and my own @tcoz_tweet timeline, the latter of which is made up of my own tweets, and tweets by the members I follow.

To accomplish this, clearly I needed a way to organize different list views, and a way to switch between them. From a UI perspective, naturally a control bar seemed to be the way to go. Anticipating this, I'd already left some space at the top of the application above where tweets appear, and it turned out to be just enough; 35 pixels to be precise.

A quick look at the current state:



Starting to finally look like something isn't it? This might not be my final design, but it serves the current purpose, and doesn't look all that bad. Notice the "next" button, which currently doesn't do anything--there's nothing to go "Next" to yet--but from a forward-looking perspective, I plan on integrating search, profile views, that sort of thing. It seemed like a good idea to assume I'll need more room than what's in that one strip.

If you're curious how I'm doing the skinning, note that there are no embedded or static images of any kind being used as backgrounds for the tweets, the buttons, or any other such thing; everything is done with the AS3 drawing API. The matrix object makes gradient fills very easy--well, compared to how they used to be--with the createGradientBox utility method. Here's a sample of how I put together gradient fills:


var matrix : Matrix = new Matrix ( );
matrix.createGradientBox ( width, height, Math.PI / 2 );
graphics.beginGradientFill ( GradientType.LINEAR, Model_Config.getInstance ( )._tweetColors, [ 1, 1 ], [ 128, 255 ], matrix );
graphics.drawRect ( 0, 0, width, height );
graphics.endFill ( );


Note that I'm pulling the colors for the gradient off of my Model_Config object and applying it to the graphics object of my view. In my coding style, Models are frequently singletons, and if you recall from a previous article, I populate this model with data from an XML file, which the user can edit to alter the appearance of certain UI elements, like the background colors of tweets (again, thanks for the inadvertent feature request iJustine). Some may say that I should be accessing the data through a proxy, and that accessing Models directly is evil, and in a larger effort, I'd tend to agree. But the deeper I get into this app, the simpler I think it is. Writing a twitter client with even some semi-advanced features isn't all that difficult.

Now, you notice that I have two buttons that matter at this point: My Timeline, and Public Timeline. Click one, see one, click the other, see the other. Again, from a previous article, you know that I'm pulling collections of objects from the Twitter API, and binding them into lists. To toggle these list views, I decided to use a flex ViewStack, and it works like a charm.

If you're not familiar with ViewStack, it's a component that makes it easy to swap the currently visible view. All you do is set the selectedItem property on it to bring the desired view to the top: 0 is the first item in the stack, and so on. Here's the code:


<mx:ViewStack id="timelineViewStack" width="100%" height="100%" creationPolicy="all">

<mx:Canvas label="My Timeline" width="100%" height="100%" backgroundAlpha="0">
<mx:TileList id="userAndFriendsList" backgroundAlpha="0" borderStyle="none"
height="100%" width="100%"
itemRenderer="com.tcoz.twitterclient.renderers.Renderer_Tweetr"
paddingBottom="0" paddingTop="0" paddingLeft="0" paddingRight="0" />
</mx:Canvas>

<mx:Canvas label="Public Timeline" width="100%" height="100%" backgroundAlpha="0">
<mx:TileList id="publicList" backgroundAlpha="0" borderStyle="none"
height="100%" width="100%"
itemRenderer="com.tcoz.twitterclient.renderers.Renderer_Tweet"
paddingBottom="0" paddingTop="0" paddingLeft="0" paddingRight="0" />
</mx:Canvas>

</mx:ViewStack>


Pretty simple; the ViewStack is a top-level MXML wrapper. In it, I've got a stack of canvases which act as the swappable views. Inside those canvases, I have my TileList components, which is what I bind my data object arrays to. The TileList takes that array of objects, creates a specified ItemRenderer for each one, and passes it one of the data objects, which has properties that map to a tweet, like name, thumbnail url, etc. The specified item renderer receives that object, which is automatically available as instance name "data", and binds the properties to the UI elements where I specify. Here's the internal of my ItemRenderer, which will power all of the TileLists that display tweets (properties like x/y and such omitted for clarity):


<mx:Image source="{ data.user.profileImageUrl }" />
<mx:Text text="{ data.user.name }" />
<mx:TextArea text="{ data.text }" />


That's it! The Flex framework makes this pretty easy. It's completely extensible: I make a new API call, like search for tweets, I get back an array of objects from the Tweetr, or my own (I'm currently using both) library. I drop another TileList into the ViewStack, and bind the data to it. I put up a button, which when clicked, sets the selectedItem property of the viewstack, which makes the desired view appear.

Now, onto something I discovered about Mate. First of all, I have found myself really liking that when I need to dispatch an event from anywhere at all, all I need to do is either do it from a view or from a Mate GlobalEventDispatcher, set it to bubble, and add the handler to my EventMap, which directs it as needed.

But...there's something missing. Take a look at this code from my evolving EventMap:


<mate:EventHandlers type="{ Event_ControlBar.SELECTED_MY_TIMELINE }" debug="true">
<mate:MethodInvoker generator="{ Manager_States }" method="changeTimelineState" arguments="{ someComputedArg }" />
</mate:EventHandlers>

<mate:EventHandlers type="{ Event_ControlBar.SELECTED_PUBLIC_TIMELINE }" debug="true">
<mate:MethodInvoker generator="{ Manager_States }" method="changeTimelineState" arguments="{ someComputedArg }" />
</mate:EventHandlers>


Note that both of these event handlers call the same method on the same Manager object.

I would very much like to have been able to do something like this:


<mate:EventHandlers type="{ Event_ControlBar.SELECTED_PUBLIC_TIMELINE | Event.ControlBar.SELECTED_PUBLIC_TIMELINE }" debug="true">
<mate:MethodInvoker generator="{ Manager_States }" method="changeTimelineState" arguments="{ ( someComputedArg ) }" />
</mate:EventHandlers>


The difference here is, I've set the event handler to accept two events as the trigger with a bitwise OR. So, if either of those events are received, the argument can be determined and passed along to the Manager. This would also be useful for bitwise exclusions, and so forth.

As far as I can tell, there is no way to do this directly in the EventMap in Mate. Every time you need to receive a different kind of event, even if it's the same arguments, same Manager, same method, etc., you have to create an entirely new MXML fragment and place it in the EventMap.

In AS3, I'd typically do something like this:


myObj.addEventListener ( MyEventClass.EVENTONE, onMyEventClassEvent );
myObj.addEventListener ( MyEventClass.EVENTTWO, onMyEventClassEvent );

private function onMyEventClassEvent ( event : MyEventClass ) : void
{
if ( event.type == MyEventClass.EVENTONE )
// do something
else if ( event.type == MyEventClass.EVENTTWO )
// do something else
}


I'm looking further into this, but so far, after searching through the docs and such, I've found no indication that you can use a common handler for multiple event types. It might be possible with a standard if statement or some such, but I was really hoping for the elegance of bitwise operators.

This is one of my misgivings about Mate, and MXML in general; it's VERBOSE. WAY more verbose than straight code. I suppose that might just be a point of view; some people may prefer the hierarchy of nested markup. At this point in time though, I don't.

Anyway, that's it this time around. As always, thanks for visiting.

Next up: refreshing Tweet lists, ensuring I only add the most recent tweets, which means, either issuing a request based on the time of the last received tweet, or just receiving the bulk and trimming off the ones that I've already got in my list.

No comments:

Post a Comment