Developing a mobile application with Flex 4.5, AIR 2.5, Flash Builder Burrito, WCF and the Entity Framework.

After returning from the Adobe MAX 2010 conference a couple of weeks ago, I finally found some time this week to play with all the cool stuff learned at Adobe MAX 2010. Of course, the whole theme of the conference was “Multi Screen Development”, this means that this post will be about developing a mobile application with Flex 4.5 (“Hero”), Flash Builder Burrito and a .Net webservice which contacts the Adventureworks database using the latest version of the Entity Framework. For all the Flex guys that use Java or PHP or any other obscure platform, don’t fear as the bulk of this post will be about mobile Flex development and you can just skip the WCF / Entity Framework parts. Unless you of course want to see what programming on a real platform is like :). Note that this is a long post and to prevent making the application more complex, I didn’t use a framework or followed the usual best practices and design patterns.

The WCF Service

First up is the service that get’s contacted by my mobile application. The mobile application is a somewhat simple shopping application, which customers can use to put products from the Adventureworks database in their shopping cart. The service is written using C# and uses WCF and the Entity Framework, so if you’re not interested in any of those, feel free to skip to the next paragraph.

First up is the service contract:

   1: using System.ServiceModel;

   2:  

   3: namespace ProductSite

   4: {

   5:     

   6:     [ServiceContract(SessionMode=SessionMode.NotAllowed)]

   7:     public interface IProductService

   8:     {

   9:         [OperationContract]

  10:         FlexProduct[] GetAllProducts();

  11:  

  12:         [OperationContract]

  13:         FlexProduct[] GetProducts(int toSkip, int toTake);

  14:  

  15:         [OperationContract]

  16:         FlexProduct GetProductById(int id);

  17:  

  18:         [OperationContract]

  19:         void UpdateProduct(FlexProduct toUpdate);

  20:  

  21:         [OperationContract]

  22:         void DeleteProduct(int toDeleteId);

  23:  

  24:         [OperationContract]

  25:         int InsertProduct(FlexProduct toInsert);

  26:  

  27:     }

  28: }

 

If you know WCF, the contract isn’t all that exciting; it defines the needed CRUD operations just to be complete, my mobile application will only use the “GetProducts()” method, I’ve included the other methods to show how they would be implemented using the Entity Framework. Readers with knowledge of Flash Builder’s Data Centric Development features (which I’ve written a post about here), should notice that the signatures of these methods, exactly match with the requirements of Flash Builder in order to use the Data Centric Development features.

Next up: The implementing class:

   1: using System;

   2: using System.Collections.Generic;

   3: using System.Linq;

   4: using System.Runtime.Serialization;

   5: using System.ServiceModel;

   6: using System.Text;

   7: using System.Data.Objects;

   8: using System.Data;

   9:  

  10: namespace ProductSite

  11: {

  12:     [ServiceBehavior(InstanceContextMode= InstanceContextMode.PerCall, ConcurrencyMode= ConcurrencyMode.Multiple)]

  13:     public class ProductService : IProductService

  14:     {

  15:  

  16:         private AdventureWorks2008Entities GetContext()

  17:         {

  18:             AdventureWorks2008Entities temp = new AdventureWorks2008Entities();

  19:             temp.ContextOptions.LazyLoadingEnabled = false;

  20:             temp.ContextOptions.ProxyCreationEnabled = false;

  21:             temp.Products.MergeOption = MergeOption.NoTracking;

  22:             return temp;

  23:         }

  24:         

  25:  

  26:         public FlexProduct[] GetAllProducts()

  27:         {

  28:             using (AdventureWorks2008Entities ents = GetContext())

  29:             {

  30:                 return (from p in ents.Products.Include("ProductProductPhotoes.ProductPhoto")

  31:                         select p).ToFlexProducts();

  32:             }

  33:         }

  34:  

  35:         public FlexProduct[] GetProducts(int toSkip, int toTake)

  36:         {

  37:             using (AdventureWorks2008Entities ents = GetContext())

  38:             {

  39:                 return ents.Products.Include("ProductProductPhotoes.ProductPhoto").OrderBy((p) => p.ProductID).Skip(toSkip).Take(toTake).ToFlexProducts();

  40:             }

  41:         }

  42:  

  43:         public FlexProduct GetProductById(int id)

  44:         {

  45:             using (AdventureWorks2008Entities ents = GetContext())

  46:             {

  47:                 return ents.Products.Where((p) => p.ProductID == id).Single().ToFlexProduct();

  48:             }

  49:         }

  50:  

  51:         

  52:         public void UpdateProduct(FlexProduct toUpdate)

  53:         {

  54:             Product toSave = toUpdate.ToProduct();

  55:             using (AdventureWorks2008Entities ents = GetContext())

  56:             {

  57:                 ents.Products.Attach(toSave);

  58:                 ObjectStateEntry entry = ents.ObjectStateManager.GetObjectStateEntry(toSave);

  59:                 entry.SetModifiedProperty("ListPrice");

  60:                 entry.SetModifiedProperty("Name");

  61:                 ents.SaveChanges();

  62:  

  63:             }

  64:         }

  65:  

  66:         public void DeleteProduct(int toDeleteId)

  67:         {

  68:             using (AdventureWorks2008Entities ents = GetContext())

  69:             {

  70:                 Product p = ents.Products.Where((pr) => pr.ProductID == toDeleteId).SingleOrDefault();

  71:                 if (p != null)

  72:                 {

  73:                     ents.Products.Attach(p);

  74:                     ents.Products.DeleteObject(p);

  75:                     ents.SaveChanges();

  76:                 }

  77:                 

  78:  

  79:             }

  80:         }

  81:  

  82:         public int InsertProduct(FlexProduct toInsert)

  83:         {

  84:             using (AdventureWorks2008Entities ents = GetContext())

  85:             {

  86:                 ents.Products.AddObject(toInsert.ToProduct());

  87:                 ents.SaveChanges();

  88:                 return toInsert.ProductID;

  89:             }

  90:         }

  91:     }

  92: }

 

When you scroll through the code of the implementing class shown above, most if it is standard Entity Framework stuff, but you should notice a couple of things:

  • The GetContext() method on line 16. It is used by the other methods to get a preconfigured ObjectContext. This method disables proxy creation, which doesn’t work with WCF, it disables lazy loading, which is always a good practice when working with the Entity Framework and WCF and it disables change tracking, which is kind of pointless when using a stateless service since the ObjectContext is disposed of after every method call. Disabling change tracking also enhances performance.
  • The return types and the arguments of the methods are typed as “FlexProduct” and not as “Product”. While there is a Product class in the solution (it’s generated by the Entity Framework) that corresponds with the Product table in the Adventureworks database, this Product class is much to big to send over to my mobile application. Since resources are scarce on a mobile device, I decided to create a smaller FlexProduct with only four properties: ProductId, Name, ListPrice and Photo.
  • The update method on line 52. When a FlexProduct is “omgesmurfd” (converted in Dutch ;)) to a Product, all the properties that a Product has which a FlexProduct doesn’t have, will get their default values, this means that when updating the whole row in the database, all values get overwritten while they haven’t been changed by my mobile application. Any mobile application can only change the “Name” and “ListPrice” properties, so I only flag these as modified with the Entity Framework.
  • The service could be optimized, I select whole products from the database, but I only use four properties. The query methods could select an anonymous type with only the ProductId, Name, ListPrice and Photo properties to cut down on the data coming from the database.
  • The query methods “include” two other tables: “ProductProductPhotoes” which is a relationship table between the Product table and the included ProductPhoto table. This is needed in order for me to show for every product at least one picture in my mobile application.
  • The methods used to convert from and to a FlexProduct: “ToFlexProduct()” and “ToProduct()” are defined as extension methods in the following class:
   1: using System.Collections.Generic;

   2: using System.Linq;

   3:  

   4: namespace ProductSite

   5: {

   6:     public static class ProductExtensions

   7:     {

   8:  

   9:         public static FlexProduct ToFlexProduct(this Product toConvert)

  10:         {

  11:             FlexProduct toReturn = new FlexProduct();

  12:             toReturn.ProductID = toConvert.ProductID;

  13:             toReturn.Name = toConvert.Name;

  14:             toReturn.ListPrice = toConvert.ListPrice;

  15:  

  16:             ProductProductPhoto photo = toConvert.ProductProductPhotoes.SingleOrDefault();

  17:             if (photo != null)

  18:             {

  19:                 toReturn.Photo = photo.ProductPhoto.ThumbNailPhoto;

  20:             }

  21:             return toReturn;

  22:           

  23:         }

  24:  

  25:         public static FlexProduct[] ToFlexProducts(this IEnumerable<Product> toConvert)

  26:         {

  27:             return toConvert.Select((p) => p.ToFlexProduct()).ToArray();

  28:         }

  29:&nbsp; 

  30:         public static Product ToProduct(this FlexProduct toConvert)

  31:         {

  32:             Product toReturn = new Product();

  33:             toReturn.ProductID = toConvert.ProductID;

  34:             toReturn.ListPrice = toConvert.ListPrice;

  35:             toReturn.Name = toConvert.Name;

  36:             return toReturn;

  37:         }

  38:&nbsp; 

  39:     }

  40: }

&nbsp;

Next up is the Flex side of things&hellip;.

The Flex Project

My mobile application was created in Flash Builder Burrito, which is currently available on Adobe Labs here. This new version of Flash Builder contains numerous enhancements, including new productivity enhancements and support for Catalyst. Since this post isn&rsquo;t about the new version of Flash Builder, I&rsquo;ll just redirect you to another post which describes all the new features. The only new feature I&rsquo;m going to use is the new “Flex Mobile Project&rdquo; template, which you can use to create mobile applications for numerous devices. I&rsquo;m not going to guide you through this new project wizard, because it really is self explanatory. The settings I used to create my project were the following:

image

It is worth noting that the only currently available platform available is Android. These options will be expanded later by Adobe and you can already package your application as an IPhone installer with the “IOS packager&rdquo;, available from Adobe here. My finished project structure looks like this:

image

The project structure doesn&rsquo;t really look different from a normal AIR application and that&rsquo;s just the point. When you&rsquo;re developing a mobile application you can leverage all your existing Flex and AIR skills. The “MobileProductApplication-app.xml&rdquo; file should be familiar to AIR developers. For mobile (Android) development this is also a really important file, since if your application uses certain things; the camera, GPS etc., you should list all the needed permissions in this file:

   1: <android>

   2:     <manifestAdditions><![CDATA[

   3:&nbsp; 

   4:     <manifest>

   5:&nbsp; 

   6:         <!-- See the Adobe AIR documentation for more information about setting Google Android permissions -->

   7:&nbsp; 

   8:         <uses-permission android:name="android.permission.INTERNET"/>

   9:&nbsp; 

  10:     </manifest>

  11:&nbsp; 

  12: ]]></manifestAdditions>

  13:   </android>

&nbsp;

The Android specific stuff is in the Android section of this file, shown above. You can see that by default your mobile application has permission to access the internet. If you&rsquo;re not an Android developer, I can imagine that it&rsquo;s painful to figure out the different fully qualified names of the permissions that your application uses. A great help is this site. It lists all the permissions, what they are needed for and their fully qualified names.

The Application

When you run this project as a mobile application on the new mobile emulator it looks like this:

image

The application really consists of two views, defined in the package “views&rdquo;.You can immediately see how mobile Flex applications are structured. The bar above, with the shopping cart, the text “Products&rdquo; and the home button is called an ActionBar, which is also it&rsquo;s class name. For me the ActionBar can be compared with the ApplicationControlBar from the regular Flex framework. It&rsquo;s a bar which is visible in every view and stays on top. In my application the actionBar get&rsquo;s only set once in “MobileProductApplication.mxml&rdquo; which is the main application MXML file. All the views are displayed beneath it and have access to it through their actionBar property. They can override the default look or let it stay the same. In my application all the views let the home and the shoppingcart button stay, but change the text. On above image the “ProductsOverview&rdquo; view is visible, which is the first view that appears when my application starts up, so the text is changed to “Products&rdquo;. While every view can construct an ActionBar and set it&rsquo;s own actionBar property, you can also fill up the actionBar by using three other “shortcut&rdquo; properties every view has:

  • actionContent, which accepts an array of components. In the above example the actionContent only contains the home button.
  • navigationContent, which accepts an array of components. In the above example the navigationContent only contains the shoppingcart button.
  • title, which is the text displayed in the middle.
  • titleContent, which accepts an array of components that are shown in the middle, right of the title. In the above example I have no titleContent.

Note the names of these properties, they don&rsquo;t mean that you can put only buttons for example in the actionContent area, they just reflect how Adobe thinks you should use them, but you are completely free. If you want to change the whole layout of the actionBar you can skin the whole thing. If you only want to change the layout of each content area, for every xxxContent property, each view has a xxxLayout property which accepts a Spark LayoutBase class like VerticalLayout or HorizontalLayout (the default).

Let&rsquo;s take a look at the “MobileProductApplication.mxml&rdquo; file, which is the application&rsquo;s main MXML file:

   1: <?xml version="1.0" encoding="utf-8"?>

   2: <s:MobileApplication >"http://ns.adobe.com/mxml/2009" 

   3:                      >"library://ns.adobe.com/flex/spark" firstView="views.ProductsOverview" preinitialize="registerClassesOnPreInitialize(event)"

   4:                       applicationRestore="restoreCartOnSessionRestore(event)"

   5:                         creationComplete="initializeApplicationOnCreationComplete(event)" initialize="attachNavigationListenersOnInitialize(event)"

   6:                          sessionCachingEnabled="true" applicationPersisting="saveCartOnPersisting(event)">

   7:&nbsp; 

   8:     <fx:Script>

   9:         <![CDATA[

  10:             import domain.FlexProduct;

  11:             import domain.ShoppingCart;

  12:             import domain.ShoppingCartItem;

  13:             

  14:             import events.ProductEvent;

  15:             

  16:             import mx.events.FlexEvent;

  17:             

  18:             import persistence.ProductsOverViewPersistence;

  19:             

  20:             import views.ProductsOverview;

  21:             import views.ShoppingCart;

  22:&nbsp; 

  23:&nbsp; 

  24:             [Bindable]

  25:             private var _cart:domain.ShoppingCart;

  26:             

  27:             

  28:             private function returnHome(event:MouseEvent):void

  29:             {

  30:                 navigator.popToFirstView();

  31:                 

  32:             }

  33:             private function addListenersToProductsOverView() : void{

  34:                 if(navigator.activeView is ProductsOverview) {

  35:                     var overView: ProductsOverview = ProductsOverview(navigator.activeView);

  36:                     overView.addEventListener(ProductEvent.PRODUCT_ADDED,handleAddToCart);

  37:                     overView.addEventListener(ProductEvent.PRODUCT_MAIL,handleMail);

  38:                 }

  39:             }

  40:             

  41:             private function handleAddToCart(event : ProductEvent) : void {

  42:                 _cart.addToCart(event.product);

  43:             }

  44:             

  45:             private function attachNavigationListenersOnInitialize(event : FlexEvent) : void {

  46:                 navigator.addEventListener(Event.COMPLETE,attachListeners);

  47:             }

  48:             

  49:             private function handleMail(event : ProductEvent) : void {

  50:                 

  51:                 var subject : String = "Found a cool product";

  52:                 var body : String = event.product.Name + " for only: " + event.product.ListPrice + "!!";

  53:                 

  54:                 navigateToURL(new URLRequest("mailto:?subject=" + subject + "&body=" + body));

  55:             }

  56:&nbsp; 

  57:             private function registerClassesOnPreInitialize(event:FlexEvent):void

  58:             {

  59:                 

  60:                 registerClassAlias("Product",FlexProduct);

  61:                 registerClassAlias("ProductsOverViewPersistence",ProductsOverViewPersistence);

  62:                 registerClassAlias("Cart",domain.ShoppingCart);

  63:                 registerClassAlias("ShoppingCartItem",domain.ShoppingCartItem);

  64:             }

  65:&nbsp; 

  66:&nbsp; 

  67:             protected function handleGoToCart(event:MouseEvent):void

  68:             {

  69:                 navigator.pushView(views.ShoppingCart);

  70:             }

  71:&nbsp; 

  72:&nbsp; 

  73:             protected function restoreCartOnSessionRestore(event:FlexEvent):void

  74:             {

  75:                 _cart = domain.ShoppingCart(persistenceManager.getProperty("cart"));

  76:                 if(navigator.activeView is views.ShoppingCart) {

  77:                     views.ShoppingCart(navigator.activeView).cart = _cart; 

  78:                 }

  79:             }

  80:&nbsp; 

  81:&nbsp; 

  82:             protected function initializeApplicationOnCreationComplete(event:FlexEvent):void

  83:             {

  84:                 if(_cart == null) {

  85:                     _cart = new domain.ShoppingCart();

  86:                 

  87:                 }

  88:                 

  89:             }

  90:             

  91:             private function attachListeners(event : Event) : void {

  92:                 if(navigator.activeView is ProductsOverview) {

  93:                     addListenersToProductsOverView();

  94:                 } else if(navigator.activeView is views.ShoppingCart) {

  95:                     addListenersToShoppingCartAndSetCart();

  96:                 }

  97:             }

  98:             

  99:             private  function addListenersToShoppingCartAndSetCart() : void {

 100:                 var cart : views.ShoppingCart = views.ShoppingCart(navigator.activeView);

 101:                 cart.addEventListener(ProductEvent.PRODUCT_REMOVED, handleRemove);

 102:                 cart.cart = _cart;

 103:             }

 104:             

 105:             private function handleRemove(event : ProductEvent) : void {

 106:                 _cart.removeFromCart(event.product);

 107:             }

 108:&nbsp; 

 109:&nbsp; 

 110:             protected function saveCartOnPersisting(event:FlexEvent):void

 111:             {

 112:                 persistenceManager.setProperty("cart",_cart);

 113:             }

 114:&nbsp; 

 115:         ]]>

 116:     </fx:Script>

 117:     

 118:     

 119:     <fx:Declarations> 

 120:         <!-- Place non-visual elements (e.g., services, value objects) here -->

 121:     </fx:Declarations>

 122:     <s:navigationContent>

 123:         

 124:         <s:Button  label="{_cart.totalAmount}" width="100%" height="100%" icon="@Embed(source='/assets/images/shoppingCart.png')" click="handleGoToCart(event)"/>

 125:     </s:navigationContent>

 126:     <s:actionContent>

 127:         <s:Button width="100%" height="100%" icon="@Embed(source='/assets/images/home.png')" click="returnHome(event)"/>

 128:     </s:actionContent>

 129: </s:MobileApplication>

&nbsp;

Because I didn&rsquo;t want to introduce a framework which would complicate this example, this file has become quite large. Don&rsquo;t be alarmed, we&rsquo;ll go through it in different paragraphs, but you&rsquo;ll have to scroll back to the code snippet above for the remainder of this post, or follow along in the actual Flex project, which you can download here&nbsp;(you might have to change the extension to fxp in order to import it in Flash Builder).

Let&rsquo;s start on line 1. What you immediately should notice is that Flex mobile applications use a different main application class. Flex uses Application, AIR uses WindowedApplication and mobile applications generally use MobileApplication which is a sub class of Application. In essence you have three ways to create a mobile application:

  • use the normal spark s:Application as your main class. This means that you must do navigation, saving of state etc. yourself.
  • use MobileApplication, which can handle things like an ActionBar, state saving and navigation.
  • use TabbedMobileApplication, which is a sub class of MobileApplication but isn&rsquo;t documented yet, so I don&rsquo;t really know what this class offers, but I can make a good guess based on it&rsquo;s name.

Another difference between the normal Application class and MobileApplication is that the MobileApplication doesn&rsquo;t support any direct MXML content. Let&rsquo;s scroll to the line 124 to 131. As you can see, I can fill up the ActionBar by using the xxxContent properties, but there are no direct MXML children. So how does the MobileApplication know which View it should render first? Scroll back up to line 1 and you can see the “firstView&rdquo; property being set to a string which contains the fully qualified name of the first View that needs to be shown in your application. This View gets instantiated by your MobileApplication, this makes it somewhat hard to attach event listeners to it (which is often hard in MobileApplications, more about that later).

Every view including the MobileApplication has a navigator property which you can use to navigate. It behaves like a stack. You use a Push method (supplying a class object of the view, not an instance of the view) to show a new View (one at a time) and a Pop method to return to a previous view. Only one View at a time is in memory and the Navigator creates and destroys them for you. So if you push a View on top of another and the user pushes the back button on his / her mobile device, the navigator pops the current View and goes back to the previous View. This means that any state must be restored, you can do this yourself, or you can use the data property each view has. When a View is pushed on top of another View, Flex automatically saves the data property of the previous View, restoring it when it pops back.

If you look at line 1, you can see that I react to the&nbsp; “initialize&rdquo; event. On lines 45-47 is the event handler for this event. Every time the navigator completes navigation I need to attach eventhandlers to the current view. Since I don&rsquo;t supply a View to the navigator when I call the Push method but a class object, the navigator instantiates the View for me and fires the complete event. In the handler for this complete event I check the current view and attach the correct handlers. The initialize event of the application is a good place to start listening for the complete event of the navigator, since creationComplete is too late (the first navigation already took place) and preInitialize is too early (navigator property is still null). The handler for the complete event is on lines 91-97. I check which View is active and attach the correct handlers.

All the views fire custom events from the “events&rdquo; package and by using Flex&rsquo; event bubbling mechanism these events eventually get caught here in the main application class. So any communication between Views is directed by the main application class. Pushing a View, waiting for an event and then attaching the handlers feels somewhat like an indirection, but I suspect that this will be a pattern that occurs quite often in a mobile application when not using a framework. If you&rsquo;re using a framework like Parsley, you can solve this problem with an event aggregation mechanism and commands.

The main application also handles all the actionbar events; when the “Home&rdquo; button is clicked or touched, the function in line 28 is executed and when the “ShoppingCart&rdquo; button is touched the function in line 67 is executed.

Now, let&rsquo;s go through the different views in a little more detail.

&nbsp;

The Products View

First up of the views is the Products view:

   1: <?xml version="1.0" encoding="utf-8"?>

   2: <s:View >"http://ns.adobe.com/mxml/2009" 

   3:         >"library://ns.adobe.com/flex/spark" title="Products" creationComplete="loadProducts(event)"

   4:         actionBarVisible.landscape="false">

   5:     <fx:Metadata>

   6:         [Event(name="productAdded", type="events.ProductEvent")]

   7:         [Event(name="productMail", type="events.ProductEvent")]

   8:     </fx:Metadata>

   9:     

  10:     <fx:Script>

  11:         <![CDATA[

  12:             import mx.collections.ArrayCollection;

  13:             import mx.data.DataManager;

  14:             import mx.events.FlexEvent;

  15:             

  16:             import persistence.ProductsOverViewPersistence;

  17:             

  18:             import services.ProductService;

  19:             

  20:             private var _service : ProductService;

  21:             private var _manager : DataManager;

  22:             [Bindable]

  23:             private var _persistence:ProductsOverViewPersistence;

  24:             private static const PAGE_SIZE : int = 100;

  25:             

  26:             

  27:&nbsp; 

  28:             protected function loadProducts(event:FlexEvent):void

  29:             {

  30:                 _service = new ProductService();

  31:                 _manager = _service.getDataManager(_service.DATA_MANAGER_FLEXPRODUCT);

  32:                 

  33:                 if(data == null) {

  34:                     _persistence = new ProductsOverViewPersistence();

  35:                     _persistence.currentProducts = new ArrayCollection();

  36:                     data = _persistence;

  37:                     fillProducts();

  38:                 } else {

  39:                     _persistence = ProductsOverViewPersistence(data);

  40:                 }

  41:             }

  42:             

  43:             private function fillProducts(index: int = 0) : void {

  44:                 

  45:                 _manager.fill(_persistence.currentProducts,"GetProducts",_persistence.currentIndex,PAGE_SIZE);    

  46:             }

  47:&nbsp; 

  48:&nbsp; 

  49:             protected function loadPrevious(event:MouseEvent):void

  50:             {

  51:                 _manager.releaseCollection(_persistence.currentProducts,true,false);

  52:                 _persistence.currentIndex -= PAGE_SIZE;

  53:                 fillProducts(_persistence.currentIndex);

  54:             }

  55:&nbsp; 

  56:&nbsp; 

  57:             protected function loadNext(event:MouseEvent):void

  58:             {

  59:                 _manager.releaseCollection(_persistence.currentProducts,true,false);

  60:                 _persistence.currentIndex += PAGE_SIZE;

  61:                 fillProducts(_persistence.currentIndex);

  62:             }

  63:&nbsp; 

  64:         ]]>

  65:     </fx:Script>

  66:     <fx:Declarations>

  67:         <!-- Place non-visual elements (e.g., services, value objects) here -->

  68:     </fx:Declarations>

  69:     <s:states>

  70:         <s:State name="portrait"/>

  71:         <s:State name="landscape"/>

  72:     </s:states>

  73:&nbsp; 

  74:     <s:List dataProvider="{_persistence.currentProducts}" left="10" right="10" top="94" bottom="10" itemRenderer="renderers.ProductRenderer"></s:List>

  75:     <s:Button left="10" top="10" label="Previous 100" click="loadPrevious(event)" enabled="{_persistence.currentIndex != 0}"/>

  76:     <s:Button right="10" top="10" width="193" label="Next 100" click="loadNext(event)"/>

  77:     

  78: </s:View>

&nbsp;

This view is responsible for loading all the products using the earlier described webservice and it used Flash Builder&rsquo;s data centric code generation abilities to do so. I&rsquo;ve also enabled client side data management for the service. You can find more about that in this post.

When you take a look at lines 1-8, you can see that this view fires custom events when the user adds a product to the cart or they want to send a mail about a product to someone they know. The method that get&rsquo;s called in the creationComplete event, which loads all the products is on line 28. This methods is somewhat more complex than you might expect, this has to do with something that we call “session caching&rdquo;, but more about that later.

I&rsquo;ve introduced a paging mechanism in this view, because memory on mobile devices is somewhat limited, I only load 100 products at once in memory. Users can use the&nbsp; “Next&rdquo; and “Previous&rdquo; buttons to load another page of data. The functions that handle those buttons are on line 49 and 57. In these functions you can see that I call the “releaseCollection&rdquo; function of the data manager before I load in another page of data. This function ensures that all the references to products of the previous page are released and they can be properly garbage collected, I&rsquo;ve taken a look with Flash Builder&rsquo;s profiler and this is indeed the case.

Now scroll down to lines 74-76. Most of this should be familiar. What I want to point out is the item renderer used in the List, as this gave me some problems. Mobile applications use an item renderer that is especially optimized for mobile devices. This class is the “MobileIconItemRenderer&rdquo; renderer class, which you subclass using MXML or ActionScript to create your own item renderer, in this case ProductRenderer.mxml:

   1: <?xml version="1.0" encoding="utf-8"?>

   2: <renderers:EnhancedMobileIconRenderer >"http://ns.adobe.com/mxml/2009" 

   3:                           >"library://ns.adobe.com/flex/spark" fontSize="15"

   4:                            labelField="Name" messageField="ListPrice" >"renderers.*" dataChange="loadPhotoAndAttachHandlers(event)"

   5:                             decoratorClass="{ProductActionBar}"   >

   6:     <fx:Metadata>

   7:         [Event(name="productAdded", type="events.ProductEvent")]

   8:         [Event(name="productMail", type="events.ProductEvent")]

   9:     </fx:Metadata>

  10:     <fx:Script>

  11:         <![CDATA[

  12:             import components.ProductActionBar;

  13:             

  14:             import domain.FlexProduct;

  15:             

  16:             import events.ProductActionEvent;

  17:             import events.ProductEvent;

  18:&nbsp; 

  19:             protected function loadPhotoAndAttachHandlers(event :Event):void

  20:             {

  21:                 var p : FlexProduct = FlexProduct(data);

  22:                 if(p.Photo != null) {

  23:                     var loader:Loader = new Loader();

  24:                     loader.contentLoaderInfo.addEventListener(Event.COMPLETE,setIcon);

  25:                     loader.loadBytes(p.Photo);

  26:                 }

  27:                 removeEventListener(ProductActionEvent.ADD_TO_CART, handleAdd);

  28:                 removeEventListener(ProductActionEvent.MAIL, handleMail);

  29:                 

  30:                 callLater(attachListeners);

  31:             }

  32:             private function attachListeners() : void {

  33:                 var actionBar : ProductActionBar = ProductActionBar(decoratorDisplay);

  34:                 actionBar.addEventListener(ProductActionEvent.ADD_TO_CART, handleAdd);

  35:                 actionBar.addEventListener(ProductActionEvent.MAIL, handleMail);

  36:             }

  37:             

  38:             

  39:             

  40:             

  41:             private function setIcon(event : Event) : void {

  42:                 var info:LoaderInfo = LoaderInfo(event.target);

  43:                 var b : Bitmap = Bitmap(info.loader.content);

  44:                 iconData = b.bitmapData;

  45:             }

  46:             

  47:             private function handleAdd(event : ProductActionEvent)  : void{

  48:                 dispatchEvent(new ProductEvent(FlexProduct(data),ProductEvent.PRODUCT_ADDED,true,false));

  49:             }

  50:             private function handleMail(event : ProductActionEvent) : void {

  51:                 dispatchEvent(new ProductEvent(FlexProduct(data),ProductEvent.PRODUCT_MAIL,true,false));    

  52:             }

  53:             

  54:             

  55:&nbsp; 

  56:         ]]>

  57:     </fx:Script>

  58:     

  59:     <fx:Declarations>

  60:         <!-- Place non-visual elements (e.g., services, value objects) here -->

  61:     </fx:Declarations>

  62:     

  63: </renderers:EnhancedMobileIconRenderer>

&nbsp;

You should notice that this class also fires custom events and it doesn&rsquo;t support direct MXML content. This is because all the UI you&rsquo;ll probably need is already defined in the super class. The MobileItemIconRenderer supports content through the following properties:

  • decoratorClass: This can be any class object with content. I&rsquo;ve set this property to my “ProductActionBar&rdquo; MXML custom component. This is a simple component which contains two buttons (add to cart and mail) and fires events when they are clicked or touched. There is no logic in this component. Note that you don&rsquo;t instantiate this content yourself, you provide a class object and it get&rsquo;s instantiated by the item renderer. This content is shown on the right.
  • iconField: Name of a property in the data object this renderer renders which provides an URL to an icon that is loaded by the renderer. This icon is shown on the left.
  • labelField: Name of a property in the data object this renderer renders, which provides a string to display as a label. This label is displayed in the middle.
  • messageField: Name of a property in the data object this renderer renders, which provides a string to display as a message. This message is displayed in the middle beneath the label.

You should notice that there is a lot of indirection here, you don&rsquo;t instantiate any of the content, this makes it a lot harder to attach event listeners to the content of the item renderer, that&rsquo;s why there is so much ActionScript code. What&rsquo;s even more annoying is that the default MobileItemIconRenderer instantiates&nbsp; an object of your decoratorClass but there isn&rsquo;t any way to get the instantiated object and thus no way at all to attach event listeners, which I of course needed to do.

Next to that, the default item renderer wants to load the images and expects an URL or you can use an iconFunction to provide a filled BitmapData object. Problem is, I already have the actual image data as a ByteArray in the products. Converting a ByteArray to a BitmapData object happens asynchronously and simply isn&rsquo;t supported by the default MobileIconItemRenderer class. Luckily, the source of the default MobileItemIconRenderer class is available and I used it to create an enhanced MobileIconItemRenderer, which ProductRenderer.mxml subclasses. You can find it in the renderers package and I added the following properties to it:

  • decoratorDisplay: You can use this to get the created object from your decoratorClass and attach event handlers.
  • iconData: Which you can set with a filled BitmapData object at any point during the life cycle of the item renderer. It uses this data to display an icon.

Now let&rsquo;s take a look at the code. First up is the method on line 19. This function get&rsquo;s called on the dataChange event. It creates a BitmapData object from the ByteArray in the product using a Loader object and removes event handlers. This is necessary because item renderers are reused, for example when scrolling through the list. On line 30 it uses the callLater() function to attach handlers to the decoratorDisplay. It is mandatory to use callLater() here, since the itemrenderer doesn&rsquo;t instantiate a decorator from your decorator class right away, but wait&rsquo;s until it&rsquo;s commitProperties() function is called. By using callLater I&rsquo;m sure that my decorator has been instantiated and that I can safely attach event handlers to it.

That&rsquo;s it for the Products view, I won&rsquo;t be explaining the ShoppingCart view, as it&rsquo;s more of the same and it doesn&rsquo;t contain any exciting stuff.

&nbsp;

Device Integration

While AIR 2.5 contains classes to access the camera, GPS or other sensors of your mobile device, I didn&rsquo;t use any of these in this application. But there are still three places where my application makes use of the capabilities offered by a mobile device. Take a look at the products view on lines 69-72 and note the states that are defined here. These state names aren&rsquo;t randomly chosen. If you provide your views with states named “landscape&rdquo; and “portrait&rdquo;, Flex recognizes these states and automatically applies them if the orientation of your device changes. Let&rsquo;s go ahead and try it out. If you run the application on the emulator, change the orientation:

image&nbsp;

Go ahead and click “Rotate Right&rdquo;. You should see the appearance change to this with an animation:

image

You should see that the acionBar has disappeared, since there is less vertical space when in landscape mode, I decided to remove it. This is done declaratively with the Flex 4 state syntax on line 4 of the products view, which is actually very neat. Not that only the View class supports these states, the MobileApplication class doesn’t.

Another point of device integration is when the user clicks or touches the “mail&rdquo; button next to a product. The function on line 49 in the main application class is used for this. You can see that if you want to use the built in mail client on a phone, call someone or sms someone from your application, you can use the navigateToURL() method. The following schemes are supported:

  • tel: To call someone.
  • sms: To sms / text someone.
  • mailto: To send an email.
  • market: To go to a specific application in the market.
  • http and https: To open the browser on the mobile device.
  • You can see that you can use different “get&rdquo; parameters to pre populate the message body and subject for example.

    Last point of device integration is probably the most impressive. Start up my application on the emulator and populate the shopping cart with a few items. Now exit the application and start it up again. You should notice that the shopping cart still contains the items you selected during the previous run of the application. This is the new session caching feature in Flex 4.5 and it has everything to do with line 6 in the main application file. By setting “sessionCachingEnabled&rdquo;&nbsp; to true on the MobileApplication class, Flex persists the state of the application when the mobile device decides to close it, also known as a force close on Android. On a mobile device this can happen for numerous reasons, for example when being called while using an application. I&rsquo;ve already told you that when using navigation in Flex 4.5, only one View at a time is in memory, but Flex caches it&rsquo;s data property, so that when a user returns to a view the View can restore itself to it&rsquo;s previous state. When session caching is enabled and the application is closed, the data property of a View get&rsquo;s saved to the mobile device, so that&nbsp; a View can restore itself even when the application has been closed. Flex also makes the View visible that was active when the application was closed.

    If you take a look at line 28 in the loadProducts() function, the session caching feature is the reason that I check whether the data property is null or not. Because if if isn&rsquo;t, the application has been restored and I don&rsquo;t need to make a call to the webservice. Because I want to save and restore two things; the currently loaded products and the index of the current page of the products, I&rsquo;ve created a wrapper class that I can put in the data property of the view: “ProductsOverViewPersistence&rdquo;, which you find in the persistence package. This feature makes mobile development very suitable for patterns like MVVM or MVP, since the ViewModel or PresentationModel can be put in the data property and is automatically&nbsp; saved and restored, if you use data binding, the rest kind of happens automatically in the view.

    When you use your own classes in the data property like my ProductsOverViewPersistence class, you should register it before Flex saves the state of your application. Otherwise you get a generic object when Flex restores it. I&rsquo;m registering all the classes that are saved, in the main application file on lines: 57-64. This function is called on the preInitialize event, which is a pretty good place to do this I guess. If you want to perform some kind of action in the class that get’s saved before it is actually saved, you can let it implement the IExternalizable interface.

    The last thing to note about session caching is that while views have a data property which get&rsquo;s automatically saved, the MobileApplication class doesn&rsquo;t. This becomes a problem when my shopping cart needs to be saved, as it is maintained at the application level and not at the ShoppingCart view level. Luckily, Flex fires events whenever it is about to save or restore your application: applicationPersisting and applicationRestore. You can see that I define event handlers for these events on lines 4 and 6. The function for applicationPersisting is on lines 110 to 113. It uses the persistenceManager property of the main application class to explicitly tell Flex that the shopping cart needs to be saved when the application is closed. The function for applicationRestore is on lines 73 to 79. It restores the cart and checks if the current view is the ShoppingCartView, if so, the cart is set in the view and the view will update itself.

    If you find it annoying during debugging that the application restores itself, or you need to disable it for testing you can do so in the run configuration in Flash Builder:

    image

    Just check the “Clear application data on each launch&rdquo; checkbox.

    Afterthoughts

    Phew!!, I told you this was a long post :). All in all I&rsquo;m very excited about Flex 4.5 “Hero” and AIR 2.5. While the API is still a little rough around the edges, mainly while attaching event handlers to views and renderers, it takes a lot of plumbing away when developing mobile applications and integrating with the mobile device is very easy. On top of that, you get a lot of touch enabled controls (you can use a throw motion in the list of products to scroll it) and buttons etc. all get the correct size which is optimized for touch enabled devices. I can really see Flex 4.5 thriving on mobile devices especially with dashboard like applications that are more business or administrative oriented, especially with Flex&rsquo; charting components. By the way, have you ever called a soap webservice with native Android development? It isn&rsquo;t pretty&hellip;.

    I&rsquo;m also impressed by the performance of Flash 10.1 and Flex 4.5 / AIR 2.5, I&rsquo;ve tested the sample application on my Samsung Galaxy S and the Droid 2 that was handed out at Adobe MAX 2010 and on both devices it performs pretty well. But the coolest part must be (and I find it hard to express how cool this really is) that with Flex 4.5 and AIR 2.5, you can create applications that run on Android devices , BlackBerry devices (including BlackBerry PlayBook) and after using the IOS packager, on IPhone and IPad devices, all using the same code base! Absent is Windows Phone 7, which will get Flash 10.1 and thus you can use Flex 4.5, but it won&rsquo;t get AIR 2.5. This means that your are limited to browser based applications on that platform.

    If you want to play some more and add some real ordering functionality for example, you can find the sample Flex application here and the Visual Studio 2010 solution here.