Tuesday, July 26, 2011

View.data, it exists and why it's important

One thing I've discovered in working with the view based mobile application is a little property called data.  I didn't know it existed, and I don't know how many other people don't know it exists, but I can't be the only one that glazes documentation, right?

I got started building a small mobile app shortly after going to a couple of days worth of classes internally at work where my boss was showing us how to build Air for Android apps.  It's just a little check book register type application.  You define some accounts and add transactions to the accounts.

I wanted to use RobotLegs building the app, because it's familiar, I've used it on the last few apps I've written and I like it.  I'm not onto using signals yet, I get them, they look great, but I'm a sucker for having some strict typing still and seeing everything defined.  So, those of you familiar with RL understand that views generally are wired to mediators to handle event listening and model injection.  So the code below won't be a surprise, this is from my AccountMediator.as and Account.mxml files.

AccountMediator.as:
[Inject]
  public var view:Accounts;
  
  [Inject]
  public var accountModel:AccountsModel;
  
  public function AccountsMediator()
  {
   super();
  }
  
  protected function handleAccountsLoaded(event:accountEvent):void {
   view.accounts = accountModel.Accounts;
  }

Accounts.mxml
<s:HGroup width="100%" height="95%" horizontalCenter="0" verticalCenter="0">
  <s:Scroller width="100%" height="100%" verticalScrollPolicy="on">
   <s:DataGroup itemRenderer="views.renderers.accountGroupRenderer"
       dataProvider="{accounts}"
       alternatingItemColors="[white,#CCFFCC]"
       height="100%"
       width="100%"
       >
    <s:layout>
     <s:VerticalLayout paddingBottom="2" paddingLeft="2" paddingRight="2" paddingTop="2" gap="0"/>
    </s:layout>
   </s:DataGroup>
  </s:Scroller>
 </s:HGroup>

What's this doing? On the preRegister, I've got a listener for data being loaded, when it is, the data that was loaded into my account model singleton will trigger it to be updated into the accounts array collection on the view. This array collection is used in a the data group and displays a simple little list based on a renderer. The result is below.

This works for the initial view, but click on that add button, then add an account or even hit cancel.   The code then dictates a popView() on the viewNavigator to go back to the Accounts view.  At that point, your greeted by a nice big BLANK list.  Didn't make sense to me because I looked through the debugger and it's all still there getting injected from RL.

So what happened?  Why did I suddenly get no data even though my models were being updated by my commands and services correctly through my control layer?

The answer is in 2 places.  The first is how the view class in viewNavigator works.  As you push and pop views, Flash is continually creating and destroying the classes/views/mediators as needed.  So pushing destroys the last view, invalidates it from the stack and instantiates the new.  When the new view is popped and the stack goes back to the first view, Flash is reinstantiating that original view.

I figured this the scenario and had assumed that it would log through the preRegister method again, making all of the setters etc work right.  What's wrong with that, is my dataLoaded event is only coming in when data is being loaded from the database.  When a single element is changed, the code is only adding or updating an element to the array collection in the model.

So, one solution is to have any change to my model's array collection dispatch an event, listen for it and respond by updating the view every time.  But, why should I do that?  The array collections are bindable and should be linking properly so I only have to put in some minimal effort to change one piece of data and have that parade down the rest of my views.

Enter the data property on the view class.  This is key.  In a view based Air mobile application, each screen is defined by the View class (spark.components.View).  This gets you the actionContent, navigationContent etc.  A lot of really easy to use bits and bobs to make a great looking mobile app that feels like native apps.  Along with that is data.  The data property is a nice binding point, you can put any sort of class in there, and Flash will hold it whenever you push and pop views.

So when we push a new view, it's saving the data property to be used again when we popView to go back and Flash reinstantiates the view.  It saves the linking too by what I see when testing so that the data comes right back from my injected model by way of the mediator.  All I did was changed the code from Accounts.mxml and AccountsMediator.as from above to below:


AccountMediator.as:
[Inject]
  public var view:Accounts;
  
  [Inject]
  public var accountModel:AccountsModel;
  
  public function AccountsMediator()
  {
   super();
  }
  
  protected function handleAccountsLoaded(event:accountEvent):void {
   view.data = accountModel.Accounts;
  }

Accounts.mxml
<s:HGroup width="100%" height="95%" horizontalCenter="0" verticalCenter="0">
  <s:Scroller width="100%" height="100%" verticalScrollPolicy="on">
   <s:DataGroup itemRenderer="views.renderers.accountGroupRenderer"
       dataProvider="{data}"
       alternatingItemColors="[white,#CCFFCC]"
       height="100%"
       width="100%"
       >
    <s:layout>
     <s:VerticalLayout paddingBottom="2" paddingLeft="2" paddingRight="2" paddingTop="2" gap="0"/>
    </s:layout>
   </s:DataGroup>
  </s:Scroller>
 </s:HGroup>

Easy enough. Now my data repopulates correctly coming back from screen to screen of views, and it runs much faster. Before stumbling on this solution, I had been trying a round robin approach of pushing the data back in each time focus came back to that view. It was a pretty decent delay, the screen sat blank for a good second. Changing to just using that data property as the bind point, made it pretty much instantaneous.

Hope that helps someone else. Happy sailing through the AS3.

My first blog

I'm new to the whole sport of blogging, but thought I should keep it a bit business casual.  This blog will generally be about Flex/AS3 coding, I also do some CF work so I may post on that from time to time.  May even post some db stuff if I get so interested.

I may curse from time to time, not as bad as a sailor, but hell what's life without some lexical extenders.  I also will probably be spending the first few posts discussing RobotLegs (RL) and Flex mobile development, AKA Air for Android.  Reason being, my current side project outside of work and my side contracts, NDAs binding, is a little Air app checkbook register that uses RL.  So, they all play together, eh?  No, I'm not canadian.