Building a Flex Application: Chapter 22 - Programming Flex 3
Pages: 1, 2, 3

Abstracting Common Patterns

Like all software, Flex applications are complex. This requires that developers building Flex applications are able to solve architectural and design problems rather than simply throwing together a bunch of off-the-shelf components. Using a microarchitecture as described in the preceding section can help in this regard. However, when using a microarchitecture you can solve only the problems that the microarchitecture is specifically designed to address. If a problem falls outside that scope, the problem remains unsolved. In this manner, it’s perhaps more useful to at least understand some of the common patterns that are used to solve problems. You can then utilize these patterns either as part of or apart from a microarchitecture.



Many papers, articles, and books have been written about design and architectural patterns used in software development. The patterns that you could use when building a Flex application are too numerous to list and detail in this chapter. However, a few patterns are common enough that we would be remiss not to describe them. Notably, nearly every Flex application could profit from the use of the Model-View-Controller pattern and the Business Delegate pattern, both of which we’ll discuss in more detail in the following sections. Additionally, we’ll start by looking at a pattern for dealing with browser integration, which is a problem that is unique to web-based applications.

Understanding Browser Integration

Shortly we’ll look at much of the code in more detail. However, before we do so, you’ll need to understand how the FlickrFlex application handles browser integration (deep linking and the back and forward buttons). The principle is rather simple: all significant state change requests are routed through the BrowserManager class by updating the address fragment. For example, when the user clicks on a Search button, the FlickrFlex application does not directly change the state to the search results screen or immediately make the request to the search method on the Flickr service. Instead, the application updates the address fragment via BrowserManager and waits for BrowserManager to dispatch an event notifying the application to update as necessary. We’ll see a few specific examples of this later on. However, the basic way in which the application manages this behavior depends on a simple infrastructure provided by a few methods in a few of the .mxml files, which we’ll look at now.

The main application .mxml file is Flickr.mxml. This file contains little more than a Style tag and an instance of FlickrController. The FlickrController class is where we start to set up browser integration. Within the initializeHandler() method, we tell the application to listen for changes to the URL via the BrowserManager:

_browserManager.addEventListener(BrowserChangeEvent.APPLICATION_URL_CHANGE,
                                 urlChangeHandler);
_browserManager.addEventListener(BrowserChangeEvent.BROWSER_URL_CHANGE,
                                 urlChangeHandler);

When the URL changes, the first thing we do is parse the fragment into an array, using a forward slash as a delimiter:

var fragments:Array = _browserManager.fragment.split("/");

We then remove the first element from the array and use that to determine what state to set on the view. If, for example, the fragment is search/nature (which is what the fragment would be if the user runs a search using the term nature), the first element in the array will be search, which means we want to tell the view to change to the search screen. In every case, we pass along the remaining fragment to the view:

var topFragment:String = fragments.shift();
if(topFragment == "search") {
    view.setMode(FlickrView.MODE_SEARCH_SCREEN, fragments.join("/"));
}
else if(topFragment == "") {
    view.setMode(FlickrView.MODE_HOME, fragments.join("/"));
}
else if(topFragment == "details") {
    view.setMode(FlickrView.MODE_PHOTO_DETAILS, fragments.join("/"));
}

Within FlickrView’s setMode() method, we change the state and then assign the remaining fragment to the screenFragment property of the corresponding screen if appropriate:

public function setMode(mode:String, fragment:String = null):void {
    currentState = mode;
    if(fragment != null && fragment.length > 0) {
        if(mode == MODE_SEARCH_SCREEN) {
            searchScreen.screenFragment = fragment;
        }
        else if(mode == MODE_PHOTO_DETAILS) {
            photoDetailsScreen.screenFragment = fragment;
        }
    }
}

If the entire fragment was search/nature, the setMode() method would change the state to the search screen state and assign nature to the screenFragment property of searchScreen. Later in the chapter, when we talk about views and controllers, we’ll see how the search screen handles the fragment. However, at this point the specifics of how fragments are ultimately utilized in specific views and controllers aren’t as important as the overall pattern. To summarize and restate: for FlickrFlex we have a hierarchical structure of objects (FlickrController contains FlickrView, FlickrView contains HomeController, etc.). FlickrController listens for URL changes, and when the fragment changes, FlickrController starts to trickle down the fragment updates by notifying FlickrView. FlickrView uses the fragment to decide how to change state, and it then passes along the remainder of the fragment to the controller corresponding to the new state. This pattern could theoretically be continued for as many levels of hierarchy that exist.

The Business Delegate Pattern

The majority of Flex applications utilize a service of one sort or another, whether the services are SOAP services, REST services, AMF (Remoting) services, or any other sort. Elements of the Flex application need to interact with these services. For example, a user may request from a service a list of states or provinces for a given country, or a user may fill out a form in Flex and then submit the form to a service method.

In a naïve approach, the actual implementation that a Flex application uses to interact with a service may be exposed directly to all elements within the Flex application. For example, if a part of the application needs to query a SOAP service for a list of states or provinces, it is possible to allow that part of the application to create an instance of the WebService component with the appropriate operation, and to bind the results of the operation directly to a UI component. This may work, but it is a shortsighted solution that creates fragility.

Instead of the aforementioned solution, you can use a common pattern known as the Business Delegate pattern. A business delegate is a class that serves as a proxy to a remote service, defining an API that the rest of the application can use without having to know anything about the implementation details. That way, the rest of the application only has to know about its contract with the business delegate. If the implementation details change, the rest of the application doesn’t have to change as long as the business delegate API remains the same. For example, an application may initially utilize a set of SOAP web services. Later the services may be migrated over to use Remoting (AMF) instead. If the application doesn’t use a business delegate, many parts of the application may need updating to work with the new services. However, if the application uses a business delegate, only the business delegate needs to change.

FlickrFlex uses the Business Delegate pattern. In this application, we define a class called com.oreilly.pf3.flickr.services.FlickrService. This class allows us to define a simple API for the rest of the FlickrFlex application to use without having to know anything about the implementation details. The FlickrService class defines only three methods: searchPhotosByText(), getPhotoDetails(), and getPhotoSizes(). The rest of the application can call these methods without having to know whether FlickrFlex is actually making requests to local XML files or a remote service. However, it turns out that in this example we are going to have the application connect to a remote service.

In the FlickrFlex application, we are connecting to only one service: the Flickr service. We don’t want to get too bogged down in the details of the Flickr service since our primary focus in this chapter is to better understand common Flex application design and development problems and solutions. However, to make sense of the code for the sample application we’ll need to explain just a few things about working with the Flickr service.

For the purposes of the sample application, we’re necessarily using only a small subset of the Flickr API. We’re calling only three Flickr service methods: one to search photos, one to get information about a specific photo, and one to get the different sizes of a specific photo.

Note

You can always read more about the Flickr API, including all the available methods and their signatures, at http://www.flickr.com/services/api.

The Flickr service uses what is known as representational state transfer, more commonly known as REST. REST uses URLs to access resources. The Flickr service allows us to call methods via resources. In the case of the sample application, there are three methods that we relate to business delegate methods: flickr.photos.search, flickr.photos.getInfo, and flickr.photos.getSizes. Next we need to look at how to access these methods.

You must use the same pattern to access any Flickr resources. You must make an HTTP request to the REST resource along with a query string indicating the method and parameters. The REST resource is http://api.flickr.com/services/rest/. Therefore, you can see that in FlickrService we define a constant called REST_URL as follows:

static private const REST_URL:String = "http://api.flickr.com/services/rest/?";

Note

The trailing ? is there to precede the requisite query string that we will look at next.

The query string for Flickr service requests must always include the method name along with the required parameters for that method. The Flickr API documentation lists the required parameters for each method. All methods require the API key. You can see that in FlickrService we define three methods that call REST service methods, each method calling a different service method with different parameters, but each following the same basic pattern. We’ll look at searchPhotosByText(). This method calls the flickr.photos.search method, which requires a text parameter, which is a comma-delimited list of words for which to search.

public function searchPhotosByText(text:String):PendingOperation {
    text = text.split(" ").join(",");
    var method:String = "flickr.photos.search";
    var parameters:Array = new Array();
    parameters.push(new NameValue("api_key", _apiKey));
    parameters.push(new NameValue("method", method));
    parameters.push(new NameValue("text", text));
    var url:String = REST_URL + createQueryString(parameters);
    var pendingOperation:PendingOperation = new PendingOperation(url);
    return pendingOperation;
}

You can see that this method creates an array of NameValue objects, which are simply objects with name and value properties, and then passes that array to createQueryString() to create the query string, which it appends to the REST URL.

If we look next at createQueryString(), we can see that it simply takes all the names and values and strings them together with = and & delimiters:

private function createQueryString(parameters:Array):String {
    var queryString:String = "";
    var i:int;
    for(i = 0; i < parameters.length; i++) {
        queryString += parameters[i].name + "=" + parameters[i].value + "&";
    }
    return queryString;
}

The searchPhotosByText() method then returns a new PendingOperation object, passing it the newly constructed URL. The PendingOperation class simply makes an HTTP request to the specified URL, and it proxies the response, dispatching an event when the response is returned.

package com.oreilly.pf3.flickr.services {

    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.HTTPStatusEvent;
    import flash.net.URLLoader;
    import flash.net.URLRequest;

    import mx.rpc.events.FaultEvent;
    import mx.rpc.events.ResultEvent;

    public class PendingOperation extends EventDispatcher {

        static public const RESULT:String = "result";
        static public const ERROR:String = "error";

        private var _loader:URLLoader;
        private var _vo:*;

        public function PendingOperation(url:String, vo:* = null) {
            _loader = new URLLoader();
            _loader.addEventListener("complete", resultHandler);
            _loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, errorHandler);
            _loader.load(new URLRequest(url));
            _vo = vo;
        }

        private function resultHandler(event:Event):void {
            dispatchEvent(new ResultEvent(RESULT, false, true,
ParsingUtility.parse(new XML(_loader.data), _vo)));
        }

        private function errorHandler(event:HTTPStatusEvent):void {
            dispatchEvent(new FaultEvent(ERROR));
        }
    }
}

You can see that the PendingOperation class relies on a method of the ParsingUtility class. We’re using this custom ParsingUtility class to determine what type of response the service has returned and convert it to the correct type. For example, when the PendingOperation object is handling the response from a flickr.photos.search method, the ParsingUtility class detects the response as such and converts the XML into an ArrayCollection of Photo objects, which is exactly what the rest of the FlickrFlex application expects.

We’ve designed the FlickrService and PendingOperation classes in this way so that the rest of the application needs to know nothing about the implementation details. As far as the controllers and views and the rest of the application are concerned, it makes no difference whether the Flickr service is a REST service. If Flickr decided to change to an AMF service at some point, we could adapt the application with little to no changes to any of the code outside the services package.

The Model-View-Controller Pattern

The Model-View-Controller (MVC) architectural pattern is utilized across many languages and platforms. If you have experience building software of any sort, chances are MVC is familiar to you. Volumes have been written about this pattern, and we won’t try to repeat much of what has already been said. Instead, we’re primarily concerned with the usefulness of the pattern in Flex applications and the nuances of how to implement it practically in Flex. However, if you aren’t already familiar with this pattern, we’ll outline it briefly.

The principle concern of MVC is to effectively separate the business logic and the user interface code such that they interact with one another only through well-defined programming interfaces. The idea is that this approach reduces fragility because you can make changes to one without having to necessarily make changes to the other.

As the name implies, this pattern typically uses three parts—model, view, and controller:

  • The model is the way in which data is represented programmatically.

  • The view is the user interface. Typically, a view does not directly modify the model it represents. Instead, it allows the controller to modify the model, and the view updates itself when the model changes. Additionally, usually a view does not call methods of its controller, nor does it even have a reference to a controller. Instead, a view usually merely dispatches events that a controller can handle.

  • The controller is the business logic. A controller typically has a reference to a model and a view, and it uses these references to modify the model and call methods of the view directly.

Although it’s possible to write models, views, and controllers in a 1:1:1 ratio, it’s also possible that a controller could be used with many different views and that a model could likewise be used by many different views and controllers.

You can implement the MVC pattern in many different ways at many different levels. At one level, it is possible to create one model and one controller for an entire application (which uses many different views). It is also possible to implement the pattern at a more granular level, creating controllers, models, and views for all components. How you implement the pattern will depend on the complexity of the application and your preferences as a developer. In the FlickrFlex application, we have opted for a more granular approach.

Next we’ll look at creating models, views, and controllers in Flex applications.

Models

Although there are variations on how to implement a model in a Flex application, those variations are relatively narrow in scope, and for the most part Flex developers tend to build models in a very similar fashion. At the core of the model implementation is something called a business object (or sometimes more specifically a value object). A business object is a type that represents entities (logical groupings of data) within the business domain (application). A value object is a more specific type of business object that has no additional logic applied to it, and merely stores data. In the case of FlickrFlex, we have only one business object, a class called com.oreilly.pf3.flickr.model.data.Photo. The Photo class is a fairly standard implementation. It models the same basic data as is returned by the various photo-related service methods, defining private properties for all of them:

private var _id:String;
private var _owner:String;
private var _secret:String;
private var _server:String;
private var _farm:String;
private var _title:String;
private var _thumbnailUrl:String;
private var _description:String;
private var _tags:ArrayCollection;
private var _imageMediumUrl:String;
private var _imageLargeUrl:String;

The class then defines accessor methods for most of the properties, as in the following:

public function get id():String {
    return _id;
}

Also, a few properties can be updated after the initial creation of the object. Therefore, we create mutator methods as well, and because we want user interface elements (views) to be able to update themselves based on the changes, we set the properties as bindable. The description property is an example:

[Bindable(event="descriptionChanged")]
public function set description(value:String):void {
    _description = value;
    dispatchEvent(new Event("descriptionChanged"));
}

public function get description():String {
    return _description;
}

Various developers have different perspectives on how a business object should be created. Some developers are of the opinion that business objects should not know anything about the manner in which they are created. Other developers prefer to make business objects responsible for creating themselves, given specific input. For Photo, we’ve opted for the latter approach, creating a static parseFromXml() method that accepts the return XML from a Flickr photo search, and constructs and returns a corresponding Photo object:

static public function parseFromXml(xml:XML):Photo {
    var id:String = xml.@id;
    var owner:String = xml.@owner;
    var secret:String = xml.@secret;
    var server:String = xml.@server;
    var farm:String = xml.@farm;
    var title:String = xml.@title;
    return new Photo(id, owner, secret, server, farm, title);
}

Frequently, applications require complex models, and it can be useful to maintain an application-wide reference to the models used by various parts of the application. For this purpose, a model locator can be a valuable part of the model. The model locator is typically implemented using the Singleton design pattern, and it stores references to various models in use. For the FlickrFlex application, we’ve defined a model locator called com.oreilly.pf3.flickr.model.ApplicationModel.

The model locator for FlickrFlex is an application-wide repository for stateful model data, including the following:

  • The current search term

  • The current search results

  • The selected photo

  • The index of the selected photo in the search results

Controllers and views

The implementation of controllers within Flex applications differs among developers much more widely than does the implementation of models. As we indicated earlier, it is possible to build an application that uses one application-wide controller. This is an approach advocated by some microarchitectures such as MVCS. This is certainly a workable approach, and it has its advantages. However, one drawback of this approach is that the one controller is generalized, and the larger and more complex the application becomes the larger and more complex the controller becomes. Therefore, for many applications it is advantageous to use more granular controllers that are specific to components of the application. This is the approach we use with the FlickrFlex application.

Implementing controllers in Flex can be challenging because of the way Flex is designed. Typically, views are user interface elements and controllers are not. As a result, it may initially seem to make sense to have components create and maintain a reference to their own controller. However, this approach has serious drawbacks; most notably it reverses the typical relationship between views and controllers. A controller should provide the programmatic interface to a component, and it should be able to theoretically control any view that implements the interface the controller expects. By having a view construct a controller, these roles and behaviors are no longer possible.

With the FlickrFlex application we’ve taken a slightly unconventional approach. We’ve decided to define controllers using MXML, making controllers extend UIComponent. This enables controllers to be added as children of other containers, and it allows controllers to then add views to themselves. If you look in Flickr.mxml, you’ll notice that it is very simple. In this document, we create an instance of FlickrController:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
xmlns:views="com.oreilly.pf3.flickr.views.*" xmlns:controllers="com.oreilly.
pf3.flickr.
controllers.*" backgroundColor="#4F5050">
    <mx:Style source="assets_embed/css/FlickrFlex.css"/>
    <controllers:FlickrController/>
</mx:Application>

In FlickrController we create only one component instance: an instance of FlickrView. FlickrController is the control for FlickrView.

Inside FlickrView you can see that in the various states we create instances of different controllers: HomeController, SearchController, and PhotoDetailsController. In each controller document we create just one component instance: HomeScreen, SearchScreen, and PhotoDetailsScreen, respectively. If you look at the code for these views, you’ll notice that they don’t reference the controllers. Instead, if any communication is necessary with the controllers, it is achieved by dispatching events.

Next we’ll look specifically at the SearchScreen and SearchController to better understand how these two classes are designed and how they work both together and in the context of the application.

In the com.oreilly.pf3.flickr.views.screens package you’ll see SearchScreen.mxml, the view for the search form/search results in the FlickrFlex application. SearchScreen is fairly representative of the rest of the views in the application and how we typically build views in general. The key points to notice about SearchScreen are as follows:

  • The view defines an API to grant access to anything related to its own state, including changes to components within it. You’ll notice that SearchScreen defines getters and/or setters for the following: dataProvider, searchTerm, isSearching, and selectedPhoto. We’ll talk more about each in just a moment.

  • The view does not have any reference to a controller. You can see that SearchScreen doesn’t know anything about SearchController. However, there are a few instances where SearchScreen needs to communicate with its controller. This is achieved through dispatching events. You can see that when the user clicks on an item in the tile list the view dispatches an event, and it dispatches a different event when the user clicks on the Search button.

The SearchScreen component, as with all views, is primarily responsible for user interface layout and for relaying user interaction to a controller via events. The SearchScreen consists of a search form (a text input and a button) and a tile list, as shown in the following snippet from the code:

<mx:HBox width="100%" height="45" backgroundColor="#212122" verticalAlign="middle">
    <mx:HBox styleName="green" height="100%">
        <mx:Label text="tags" />
        <mx:TextInput id="searchTermInput" text="{_searchTerm}" />
        <mx:Spacer width="10" />
        <mx:Button id="searchButton" label="Search for Photos"
            styleName="green" height="100%"
            click="dispatchEvent(new PhotoSearchEvent(searchTermInput.text));" />
    </mx:HBox>
</mx:HBox>
<mx:TileList id="photoList" width="100%" height="100%"
    itemRenderer="com.oreilly.pf3.flickr.views.renderers.PhotoRenderer"
    dataProvider="{_dataProvider}"
    change="dispatchEvent(new PhotoSelectEvent(photoList.selectedItem.id));" />

You can see that in the two cases where there is user interactivity (the user clicking on the Search button or selecting an item from the tile list), the view merely dispatches an event.

As mentioned earlier, the view defines an API for reading and writing values and/or state on the view. One of the key getters/setters is dataProvider, which is fairly standard for most views. The dataProvider getter/setter merely gets/sets the bindable _dataProvider property, which is an ArrayCollection in the case of SearchScreen. The tile list is bound to the _dataProvider property such that whenever the property changes, the tile list reflects those changes. We’ll look at when and how this property changes when we look at the controller code in just a minute.

The other two getters/setters are searchTerm and isSearching. The searchTerm getter/setter is used to get and set the keyword(s) displayed in the search form, when applicable. The isSearching getter/setter is used to change the state to disable the view when a search is running or when the result is returned from the service.

The selectedPhoto getter is designed to return a reference to the Photo object corresponding to the selected tile list item. This provides a well-defined API such that the controller can access that information without having to know anything about how the view is implemented.

The SearchController class is designed to handle all the business logic for searching, displaying results, and navigating to photo details. The SearchController has an instance of SearchScreen with an ID of view:

<screens:SearchScreen id="view" photoSearch="photoSearchHandler(event);"
photoSelect="photoSelectHandler(event);" />

When the photoSearch or photoSelect event occurs (dispatched by the view), the controller handles the event. In both cases, the controller handles the event by updating the browser address fragment using BrowserManager:

private function photoSearchHandler(event:PhotoSearchEvent):void {
    BrowserManager.getInstance().setFragment("search/" + event.searchTerm);
}

private function photoSelectHandler(event:PhotoSelectEvent):void {
    BrowserManager.getInstance().setFragment("details/" + event.photoId);
}

You’ll recall from earlier in the chapter that state changes are often managed by changes to the browser address (via BrowserManager). You may recall from that discussion that when changes are detected in the URL, FlickrController tells FlickrView to change its state, and FlickrView may then pass along part of the address fragment to the corresponding controller via a screenFragment property. Therefore, when the fragment is changed to search/search term, the search term gets assigned to the screenFragment property of SearchController. Therefore, we define a screenFragment setter as follows:

public function set screenFragment(value:String):void {
    value = unescape(value);
    ApplicationModel.getInstance().searchTerm = value;
    if(_searchTerm != value) {
        view.searchTerm  = value;
        searchForPhotos(value);
    }
}

You can see that if the search term is the same as the existing search term, no further action is necessary. However, if the search term is different, the controller updates the search term for the view and runs a method (searchForPhotos()) that uses the FlickrService instance to query the Flickr service for photos with that search term:

private function searchForPhotos(searchTerm:String):void {
    _searchTerm = searchTerm;
    view.isSearching = true;
    var pendingOperation:PendingOperation = _service.searchPhotosByText(_searchTerm);
    pendingOperation.addEventListener(PendingOperation.RESULT,
                                      searchForPhotosResultHandler);
}

You can see that the controller sets isSearching to true. When the result is returned, not only does the handler method update the dataProvider of the view, but it also sets isSearching to false:

private function searchForPhotosResultHandler(event:ResultEvent):void {
    var photos:ArrayCollection = event.result as ArrayCollection;
    ApplicationModel.getInstance().searchResults = photos;
    view.dataProvider = photos;
    view.isSearching = false;
}

Summary

Throughout this book, you learned a great many things about Flex 3. In this chapter, we tied much of that together with a sample application and looked at a set of simple best practices. In the course of this chapter, you had the opportunity to download a complete Flex 3 sample application that uses the Flickr service to search photos and display results. We then discussed some of the key architectural decisions in the application.

This excerpt is from Programming Flex 3. If you want to try your hand at developing rich Internet applications with Adobe's Flex 3, and already have experience with frameworks such as .NET or Java, this is the ideal book to get you started. Programming Flex 3 gives you a solid understanding of Flex 3's core concepts, and valuable insight into how, why, and when to use specific Flex features. Learn to get the most from this amazing and sophisticated technology.

buy button