PureMVC Architects Lounge

PureMVC Manifold => Standard Version => Topic started by: Deus on November 23, 2010, 11:12:22



Title: Flex Mobile ViewNavigator and PureMVC
Post by: Deus on November 23, 2010, 11:12:22
Hi all,

I'm quite new to PureMVC, having learnt other framework before, I'm quite excited to develop a mobile app using the new Flex Mobile (Flash Builder Burrito).

I understood that the View must not know or aware of the PureVMC apparatus going on and it should communicate to it's Mediator via Event. The only exception to this rule is the Root whereby it has to initiate the PureMVC apparatus by calling ApplicationFacade.getInstance().startup(this) and passing its reference.

In normal Flex app, I would do this to instantiate my View's mediator.

:
override public function onRegister():void
        {
            // Create and register Mediators
            // components that were instantiated by the mxml application
            facade.registerMediator( new SplashScreenMediator( app.splashScreen ) );
            facade.registerMediator( new MainScreenMediator( app.mainScreen ) );
        }

But with the new ViewNavigator in Flex Mobile, how do i go around this? and the navigation is handle by this object. And how do i get the View's reference into my Mediator?

:
http://help.adobe.com/en_US/FlashPlatform/beta/reference/actionscript/3/spark/components/ViewNavigator.html


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: Deus on November 28, 2010, 07:45:56
Pretty quite here. No takers?  ???


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: puremvc on November 28, 2010, 05:44:34
Sadly, I haven't yet gotten to Hero yet, which I assume this is part of, it's still in preview.

So, please feed back into this thread what you find. Is ViewNavigator the top level object of a Hero mobile app? In that case, you've probably got to just have a mediator for it, but the children's instantiation might be deferred as is the case with the Slacker AS3 Demo.

-=Cliff>


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: mikebritton on January 07, 2011, 07:57:26
My approach was to start the facade with my first view, then register the main mxml file's Mediator.  I have a Command for this with Notifications that start up each view's Mediator if it doesn't exist.

Now, however, I end up losing my Mediators unexpectedly.  Research continues...


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: TonySykes on February 01, 2011, 04:36:16
Hi All,
It does not appear to be just a deferred instantiation problem - I had a look at Slacker but it does not help. With these mobile apps create your first screen via -
<s:MobileApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark"
    firstView="views.MobileTestDriveCFHome">
(which could be your splash screen for example) and then you jump to other screens via -
navigator.pushView(AddEditView). So the problem is getting the reference of the screen/view to pass to the mediator (via deferred instantiation) - facade.registerMediator(???????)

- Tony


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: puremvc on February 03, 2011, 08:14:48
So in ...

:
navigator.pushView(AddEditView)
Is AddEditView a classname? Is pushView responsible for instantiation?

-=Cliff>


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: TonySykes on February 04, 2011, 12:36:51
Hi Cliff,

Is AddEditView a classname? - Yes. Here is the help for pushView -

spark.components.ViewNavigator.pushView(factory:Class, data:Object=null, transition:ViewTransition=null):void
Pushes a new view to the top of the navigation stack.
Parameters:
factory The class used to create the view
data The data object to pass to the view
transition The view transition to play

Is pushView responsible for instantiation? - Yes for instantiating the view.

Here is a simple app (thanks Arjan) showing a list of people and when you click on a person the details screen displays.

The main file has a firstView which calls views\ViewNavigationHome.mxml and the PeopeDetails view is then created via the click handle of the List peopleList

It is at this time we would want to do the delayed instantiation of a mediator for the PeopeDetails view. The question is not sure how to do this - I think I am missing something.


main.xml

:
<?xml version="1.0" encoding="utf-8"?>
<s:MobileApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
firstView="views.ViewNavigationHome">
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>

</s:MobileApplication>

views\ViewNavigationHome.mxml

:
<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
title="Home" initialize="init()">


<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;

[Bindable]
private var people:ArrayCollection;

private function init():void
{
people = new ArrayCollection();
var somebody:Object = new Object();
// TODO
}

private function handleClick():void
{
navigator.pushView( PeopleDetails, peopleList.selectedItem );
}



]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>



<s:List id="peopleList" click="handleClick()" dataProvider="{people}"
labelField="name" width="100%" height="100%"/>

</s:View>

PeopleDetails.xml

:
<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
title="Person Details">

<fx:Script>
<![CDATA[
private function gotoHome():void
{
navigator.popToFirstView();
}
]]>
</fx:Script>
<s:layout>
<s:VerticalLayout paddingBottom="5" paddingLeft="5" paddingRight="5" paddingTop="5"/>
</s:layout>

<s:navigationContent>
<s:Button label="Home" click="gotoHome()"/>
</s:navigationContent>

<s:Form width="100%" height="100%">
<s:FormItem label="Name:" width="100%">
<s:Label text="{data.name}"/>
</s:FormItem>
</s:Form>

</s:View>

-Tony


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: shredding on June 12, 2011, 01:36:11
Hello all,

I'm reactivating the thread, 'cause I'm getting started with Flex Mobile too and am lacking a great idea of integrate it with pureMVC.

May I ask you to bring us up to date your research?

I'm playing around with a few ideas. Most of them are build this way:

I'm registering a navigatorMediator as MainMediator & it holds a reference to the Navigator and a <vector> with all views. When the user navigates, the navigatorMediator is called, registeres a dependent mediator and so on.

This is somewhat counterintuitive and I'm wondering if there's a better solution.



Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: puremvc on June 12, 2011, 04:02:33
I'm registering a navigatorMediator as MainMediator & it holds a reference to the Navigator and a <vector> with all views. When the user navigates, the navigatorMediator is called, registers a dependent mediator and so on.

This is somewhat counterintuitive and I'm wondering if there's a better solution.

The NavigatorMediator should not need a vector of the child Views, only a reference to the ViewNavigator. It should listen for the ViewNavigatorEvent.VIEW_ADD event on its view component (the ViewNavigator). The ViewNavigatorEvent's view property will contain the View that was added.

Two possibilities at this point:
1) Have the ViewNavigatorMediator register the appropriate mediator for the newly added View,

OR

2) Send off a notification with the View in the body, and place the logic to register the appropriate mediator in the command that handles that notification.

Either way is perfectly valid.

#1 is fewer files, less complex, easier to maintain since fewer actors and steps are involved.

#2 is closer to a strict interpretation of the actors' intended roles: The Mediator merely mediates communication; takes an event and sends off a corresponding note. The Command handles logic, including organization of the view hierarchy.

-=Cliff>


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: mikebritton on June 12, 2011, 04:41:33
#2 is the way I chose to deal with it.  I wanted to centralize this with a Command and a Notification for all removal / instantiation.

It feels laborious to remove / instantiate new Mediators every time a view is pushed, so my next step is to encapsulate it somehow.  A plugin?  Dunno yet...

I stopped there, when I had a decent boilerplate to use for projects, then went and did the same thing for the Java port.


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: puremvc on June 13, 2011, 07:40:39
It feels laborious to remove / instantiate new Mediators every time a view is pushed
If the View's creation is deferred until the time it's pushed into the ViewNavigator, then you don't have much choice but to wait until they exist before mediating them.

However, if you create all the views up front, before they're pushed into the ViewNavigator, then you'll probably incur a bit of perceived startup lag, but it might not be that bad if the Views aren't very complex.

If you took this latter approach, then you could create your views and mediate them in the traditional view prep phase of the startup process. Then when a specific view is needed, you could send a note that its mediator will hear and respond to by sending off a PUSH_VIEW note to the ViewNavigatorMediator. It in turn takes the View from the body of the note and pushes it on its view component, the ViewNavigator.

-=Cliff>


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: shredding on June 13, 2011, 12:48:00
Thanks for your answers.

I'll dive in & report :)


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: shredding on June 14, 2011, 02:03:47
Well, thanks. I got it up and running. Since I'm fairly new, would you mind take a look if this is ok?

Thank you all!

The Event is not ViewNavigatorEvent.VIEW_ADD, but ElementExistenceEvent.ELEMENT_ADD!

See: http://opensource.adobe.com/wiki/display/flexsdk/View+and+ViewNavigator

I'm booting the framework with the ViewNavigator like this:

:
<?xml version="1.0" encoding="utf-8"?>
<s:ViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
firstView="views.TemplateHomeView"
applicationDPI="240"
initialize="onInitalization(event)">
<fx:Script>
<![CDATA[
import com.digitaleavantgarde.ApplicationFacade;

import mx.events.FlexEvent;

private var facade:ApplicationFacade = ApplicationFacade.getInstance();


protected function onInitalization(event:FlexEvent):void
{

// Booting Framework
facade.startup(this.navigator);

}
]]>
</fx:Script>
</s:ViewNavigatorApplication>

The rest is pretty straighforward, I have an application mediator and can handle the events.

Proof of concept:
:
public function ApplicationMediator(viewNavigator:Object=null)
{
super( NAME, viewNavigator );

viewNavigator.addEventListener(ElementExistenceEvent.ELEMENT_ADD, function(evt:ElementExistenceEvent):void {

var activeView:View = evt.element as View;
trace(activeView.title); // traces the title
});
}


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: puremvc on June 14, 2011, 09:53:55
@shredding Good work! Thanks for pointing out the proper event name and doc location 1. I was looking at an old version 2 of Adobe's documentation. I'm a little surprised that they just dropped VIEW_ADD instead of deprecating it, but I suppose it was in beta at the time and all bets are off on the final shape of things.

Anyway your bootstrapping looks ok. I'd change the ApplicationMediator to take a strict type on the constructor and not allow a null default. If you're immediately going to work with the object passed in, then you can't allow null.

Also I would set the listeners in onRegister. The argument for this as a best practice is that if the mediator receives an event from its view component before being registered, it can still send notes, but logically cannot participate in any conversation with other actors that might ensue, since it could not yet hear any return notes destined for it.

:
public function ViewNavigatorMediator(viewComponent:MyHeroApplication)
{
super( NAME, viewComponent );
}

private function get viewNavigator():MyHeroApplication
{
return viewComponent as MyHeroApplication
}

override public function onRegister():void
{
viewNavigator.addEventListener(ElementExistenceEvent.ELEMENT_ADD, onElementAdd)
}

private function onElementAdd(event:ElementExistenceEvent):void
{
var activeView:View = event.element as View;
trace(activeView.title); // traces the title

// now, let a command figure out which mediator to register...
sendNotification( MyAppConstants.MEDIATE_VIEW, activeView );
}

-=Cliff>

1 This is the right API page to look at currently: http://opensource.adobe.com/wiki/display/flexsdk/View+and+ViewNavigator

2 Adobe, does this still need to be up there? http://opensource.adobe.com/wiki/pages/viewpage.action?pageId=51839406#ViewandViewNavigator-APIDescription


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: shredding on June 14, 2011, 11:42:25
Thanks for pointing that out.

I've adjusted my ApplicationMediator. I had a discussion in the german flashforum. Someone gave me the interesting opinion, that the Mediator are upstreamed Controllers (while I was thinking there are downstreamed views ...).

Thus, most of the time I have to main Heroes in my Apps: The ApplicationMediator & the ApplicationProxy: The ApplicationProxy hosts all neccessary data & Proxies that are coupled to a concrete source (e.g. XMLProxy, DatabaseProxy ...) report to him & the VO. Thus, other proxies are more or less plugins to the Main ApplicationProxy.

On the other hand, the ApplicationMediator hosts a reference to the BaseView & handles some stuff like listening for Events that all Mediators care about.

Thus, I'm registering Mediators directly from the ApplicationMediator (and am using Commands rarely). Guess that's bad practice, huh?

Here's my final version.

E.g. the ApplicationFacade.LOG notification is logged always when something bad happens (Error Events). If I want to handle it via AlertBoxes or redirect it to the Javascript console or whatever, I can do it here.

The  ResourceLanguage Change (Basically it makes the IResourceManager available from none flex-based as3 files & manages access to language classes) should result in update the language in all Mediators - (setLang() is defined by the interface). Thus, an ApplicationMediator comes in handy for me.

Another Problem on the ViewNavigator: The element property of the ElementExistenceEvent returns a View - thus I cannot access the MyMediator.NAME const, as best practise would recommend without casting every single Mediator.

That's why I set the id == MyMediator.NAME on initalize & use it as synonym in the AppMediator ...

:
protected function onInitalization(event:FlexEvent):void
{
this.id = NAME;
                        }

:
package com.digitaleavantgarde.view
{

import com.digitaleavantgarde.ApplicationFacade;
import com.digitaleavantgarde.model.LocalStorageProxy;
import com.digitaleavantgarde.utils.lang.ResourceLanguage;

import flash.net.registerClassAlias;

import org.puremvc.as3.interfaces.IMediator;
import org.puremvc.as3.interfaces.INotification;
import org.puremvc.as3.patterns.mediator.Mediator;

import spark.components.View;
import spark.components.ViewNavigator;
import spark.events.ElementExistenceEvent;

import views.AnotherView;
import views.HomeView;

public class ApplicationMediator extends Mediator implements IMediator
{
public static const NAME:String = 'ApplicationMediator';

private var viewNavigator:ViewNavigator;

private var activeView:View;

public function ApplicationMediator(viewNavigator:ViewNavigator)
{
                        this.viewNavigator = viewNavigator;
super( NAME, viewNavigator );
}

override public function onRegister():void
{
viewNavigator.addEventListener(ElementExistenceEvent.ELEMENT_ADD, switchView);
}

override public function listNotificationInterests():Array {
return [
ApplicationFacade.LOG,
LocalStorageProxy.LANGUAGE
];
}

override public function handleNotification(notification:INotification):void {

var name:String = notification.getName();

switch (name)
{
case ApplicationFacade.LOG:
trace(notification.getBody());
break;

case LocalStorageProxy.LANGUAGE:

ResourceLanguage.getInstance().setLang(notification.getBody() as String);

if(activeView!=null) {
var viewToUpdate:ILanguageMediator = this.facade.retrieveMediator(activeView.id) as ILanguageMediator;
viewToUpdate.setLang();
}
break;
}
}

private function switchView(evt:ElementExistenceEvent):void {

if(activeView!=null) {
this.facade.removeMediator(activeView.id + 'Mediator');
}

activeView = evt.element as View;

switch(activeView.id) {
case(HomeView.NAME):
this.facade.registerMediator(new HomeViewMediator(activeView));
break;

case (AnotherView.NAME):
this.facade.registerMediator(new AnotherViewMediator(activeView));
break;
}
}

}
}

This works great for me, but I'm bit afraid of programming cross the purpose of the framework :)


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: shredding on June 14, 2011, 11:55:15
... after reviewing my upper post, I saw, that I broke my own approach  ::)

The LocalStorageProxy does not report to the ApplicationProxy, thus the LocalStorage is coupled to the ApplicationMediator. I'll fix that :)


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: puremvc on June 14, 2011, 04:42:02
Someone gave me the interesting opinion, that the Mediator are upstreamed Controllers
Wrong. This leads to the idea that they should house logic. That is not the purpose of a Mediator at all. It's role is to mediate, that's why it's called a Mediator. It mediates communication between a view component and the rest of the application. Commands house logic, you should really move all the logic out of the Mediator and into a command, triggered in the way I showed previously by just sending off a note.

Sure, you can do logic in the mediator, but that overloads it with responsibilities. You end up with these huge bloated Mediators when you keep finding logic to stuff in there for different things. Keep the mediator's role simple and each command's work simple. All your classes should end up being as simple as possible. You're not striving for the fewest possible number of classes, you're striving for a system of clear roles, responsibilities and collaborations.

Also, your view should not know your mediator, even in order to know its NAME. We're also striving for loose-coupling. The view component is more portable if it doesn't know anything about its mediator. If the view has to import the mediator in order to give itself an id, then you're tightly-coupled. That component cannot be used without the mediator. In a properly written system you could take all the view components and use them with another framework, without having to take any of the PureMVC code along. (Why you'd be migrating away from PureMVC is a mystery, but you could do it. That's portability).

With this code:
:
if(activeView!=null) {
this.facade.removeMediator(activeView.id + 'Mediator');
}
         
why do you want to re-mediate the view component? if it is already mediated then there's no need to remove the mediator and then register it again.

-=Cliff>


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: puremvc on June 14, 2011, 04:48:42
The LocalStorageProxy does not report to the ApplicationProxy, thus the LocalStorage is coupled to the ApplicationMediator. I'll fix that

Actually it's fine for any mediator to know any proxy, just not the other way round. Here again, the direction of the coupling is key. If anything's truly reusable in your app it'll be the Model region; the proxies.

Thus the Proxy should know absolutely nothing about the rest of the app (no imports from the View or Controller regions). On the other hand, the View region has no reason in life but to present the data from the Model and allow the user to interact with it. Therefore, it is understandable that the Mediators will have some intimate knowledge about the shape of the domain model, and the Proxies that allow interaction with it.

So for simply taking data from the view in response to an event and passing it to a method on a proxy for dispatch to the server, or for telling a proxy to load a certain selected piece of data, the mediator may retrieve and interact directly with the Proxy. Coupling in that direction (view to model) is fine.

-=Cliff>


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: shredding on June 16, 2011, 12:39:02
Thank you so much for your detailed explaination. I've adjusted my Code.

But I do not understand, why this is a better approach then mine "all proxies report to a major proxy":


Actually it's fine for any mediator to know any proxy, just not the other way round. Here again, the direction of the coupling is key. If anything's truly reusable in your app it'll be the Model region; the proxies.



In my Examlpe I use a LocalStorageProxy. If in the future, the Datasource would change to an XML, I'd have to adjust the Mediator. If the LocalStorageProxy would process its data and pushes it to an ApplicationProxyVO, no one would have to care (the decoupling would work in both directions). Would you mind to drop a line bout this? Thanks!


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: puremvc on June 17, 2011, 09:20:24
But I do not understand, why this is a better approach then mine "all proxies report to a major proxy":
You can certainly build a hierarchy of Proxies, it's just that you're likely to end up with a massive Proxy at the top that is acting more like a delegate really. And a domain model that's not easy to reuse in different apps.

By having the parts of the Mediators and Commands that need to interact with the domain mode simply talk directly to the Proxies they need to, you're more likely to be able to reuse just the parts parts of the domain model you need in another program.

For instance, it is increasingly likely that you may have web, mobile, desktop and tablet versions of your application. You might not be planning them from the start, but when it does, you'll probably find yourself using subsets of the whole model package in each application. You may use certain Proxies only with the desktop and mobile apps, where you can enable an offline cache for resources that have to be pulled down over the web every time in the web or mobile apps. And all the different apps will likely have a different set of use-cases that take into account the unique form-factor and limitations of each.

That said, there is one area I've put a hierarchy of Proxies to good use; using the PureMVC/AIR XMLDatabase Utility. It is basically an abstract Proxy that can read, write and create a default XML structure on disc for a particular purpose. Rather than write a bunch of different XML files for different data that an app stores, the XMLDatabase utility lets you roll them all up into one XML file, then when it loads the file, you can override a method and take the loaded XML and 'snap off twigs' of it and feed other Proxies that tend just their twig. For instance in my PantryMouse demo application I have Items, Stores, Trips and Lists as separate elements in the XML that are managed by different Proxies. They all are really maintaining the same big structure, which gets written out periodically and when you exit the app. But the rest of the application talks to the sub-proxies, not to the main proxy.

In my Examlpe I use a LocalStorageProxy. If in the future, the Datasource would change to an XML, I'd have to adjust the Mediator.
The place to shield your view from major changes in data format is by creating value objects. And if you decide to use XML, you can always wrap it in a vo with typed, implicit getters and setters. I call this the SmartVO1.

-=Cliff>

1See an example of a 'Smart VO' here: http://forums.puremvc.org/index.php?topic=1293.msg5973#msg5973


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: dries on November 24, 2011, 05:46:25
I'm setting up a mobile flex iOS application with ViewNavigators. I'm used to the pureMVC framework, but i'm still not sure how to use it in combination with those ViewNavigators.

The views in the ViewNavigators are created/removed during the run of an application. This minimizes the used memory of the application. Memory use should be low since an iOS application keeps on running in the background if you exit the application. So i don't want to use the approach to create all view at startup and keep them in memory.

Did a small test, and it seems that views are not reused: each time a view is needed, a new instance is created. This means that mediators should be created and removed whenever a view is created/removed.

Is there a good way to do this?

Can the id be used to look what mediator should be created? In this case: how can we specify an id for the 'firstView' that will be created?

Or should we look at the class type of the view? I suppose this means views can not be reused within an application..


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: puremvc on November 26, 2011, 05:43:59
I have recently run into this problem myself in a project I'm working on for the iPad. In my spare time, I'm just putting the finishing touches on my new O'Reilly PureMVC book, or I'd already have a demo out showing how to handle this scenario. I'd thought I'd include it in the book, but ruled it out for lack of time. But because this is a really important issue, I'll elaborate here, then refine this post into a section in the Advanced View Topics chapter of the book. My final draft goes in Monday, so thanks for bringing up this issue today!

Flex Mobile presents a radically different paradigm in the way the view is constructed (and destroyed), so the previous approaches to mediation don't really fit. But fear not, PureMVC is adaptable.

Flex Web or Desktop Mediation Strategy
Normally, in a Flex browser or desktop app, you'd have an Application or WindowedApplication declaring an initial hierarchy, which you mediate once it is created. When you want to add a view component to an Application or WindowedApplication you do an addChild() to add it.

Handle the app's applicationComplete event by passing a reference to the application into your ApplicationFacade.startup() method, where you send it off in a notification to a StartupCommand where you mediate the application and any other top-level components on the display list that need mediation.

Mediators can further mediate the top level children of their view components. If view components have their creation deferred (e.g., children of a ViewStack not being created until navigated to), then your mediator should listen for Event.ADDED or FlexEvent.CREATION_COMPLETE events from the view component and mediate them in the event handler.

And at any time, you might want to dynamically create a component and add it to the display list with mediation. You just create it, register a mediator for it, and send it off in a notification that is responded to by an ApplicationMediator, who takes the component from the note and adds it as a child of the application.

Mediators are usually long-lived in a web or desktop app, as view components are rarely destroyed. except when View States are employed, but that's another story (more like Flex Mobile Views actually).

Flex Mobile Mediation Strategy
The ViewNavigator is essentially a stack that Views are pushed onto or popped off. A ViewNavigatorApplication is a top-level application for mobile that uses a ViewNavigator as the primary container.

A major difference from Application or WindowedApplication is you don't create a View before pushing it onto the ViewNavigator, you actually pass in the class name and it creates it for you. So, that means you can't use the PureMVC idiom of creating a component, mediating it, and then sending it off in a notification to be added to the Stage.

You can have a 'firstView', which it will create automatically at startup, and you may mediate that at startup. Other Views are only created when pushed onto the ViewNavigator or popped back to. All Views are destroyed when they are popped off the ViewNavigator, or when another View is popped onto the ViewNavigator on top of them, except...

There is a destructionPolicy="never" attribute that you can declare on a View. However, this only means it stays in memory when something is pushed onto the ViewNavigator in front of it. But when you pop a View with destructionPolicy="never", it is actually destroyed. So "never" really means "never destroy this View when something is pushed on top of it".

When you push a View onto the ViewNavigator, you can also supply an arbitrary Object as data to be passed to the View when it is created.

And a ViewNavigator has a property called 'context' which is an arbitrary Object. You can use this to keep track of what you're doing. I used it as a constant that told me what View I was on without referring to the active View and checking its type.

In Flex Mobile, you need to dynamically mediate your Views which are created and destroyed all the time. Your mediators only live as long as the component they mediate. You also need to manage when you push a View onto the ViewNavigator, and when you pop back to one. Finally, you need to consider which Views are important to set destructionPolicy="never" on.

Hypothetical Flex Mobile App
Let's consider an application where you can log in, view a list of your things, maintain your things, or see an screen with info like what version of the app you are running, how much free space your mobile device has, and how much space your things are currently taking up.

You have a LoginView as your first View. Once logged in, you move to a ListView, where you can see a list of your 'things'. From there, you can either log out (going back to the LoginView), view info about the app in an InfoView, or edit one of your 'things' in the FormView.

Your ListView might take awhile to render, especially once you have a lot of 'things'. So you'll want to set destructionPolicy="never" on it so that it doesn't get destroyed when you push an InfoView or FormView on top of it. But remember when you log out, the ListView will be popped off the stack and will be destroyed. Which makes sense because if you log in as someone else you don't want the previous list in memory anyway. Also, the InfoView will take an InfoVO

So, this all begins with the application itself. It needs to expose some methods for showing different Views. It will encapsulate all the push/pop logic, so the mediator doesn't need to know any thing other than what method to call to show a given View. Also, we want to mediate the initial LoginView component, so we need a method for getting the active View from the application.

Here are the important actors and their roles, in order of appearance in the startup process:

  • The IMyMobileApp Interface - An interface showing the methods the methods this hypothetical application should implement
  • The MyMobileApp Application - Based on ViewNavigatorApplication, starts up PureMVC, and implements the IMyMobileApp Interface
  • The Application Facade - Based on Facade, implements getInstance() and startup() methods
  • The Startup Command - Prepare the Controller, Model, and View in that order
  • The Application Mediator - Listen to the application for Event.ADDED, sending any Views off in a MEDIATE_VIEW Notification, listen for Notifications to change Views, calling the appropriate methods on the app in response, and mediate the firstView
  • The Mediate View Command - Create and register the appropriate Mediator for the View, removing any existing instance first
  • The List View - A typical View, except it needs destructionPolicy="never"

Now some code:


The IMyMobileApp Interface
:
package com.me.myapp.view
{
import com.me.myapp.model.vo.InfoVO;

import flash.events.IEventDispatcher;

import spark.components.View;

public interface IMyMobileApp extends IEventDispatcher
{
function getActiveView():View;
function showLoginView():void;
function showListView():void;
function showInfoView( infoVO:InfoVO ):void;
function showFormView():void;
}
}

The MyMobileApp Application
:
<?xml version="1.0" encoding="utf-8"?>
<!-- FLEX MOBILE APPLICATION-->
<s:ViewNavigatorApplication
firstView="com.me.myapp.view.component.LoginView"
implements="com.me.myapp.view.IMyMobileApp"
applicationComplete="facade.startup(this)"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:fx="http://ns.adobe.com/mxml/2009">


<fx:Script>
<![CDATA[
import com.me.myapp.ApplicationFacade;
import com.me.myapp.model.vo.InfoVO;
import com.me.myapp.view.component.InfoView;
import com.me.myapp.view.component.ListView;
import com.me.myapp.view.component.LoginView;
import com.me.myapp.view.component.FormView;

import spark.components.View;

private var facade:ApplicationFacade = ApplicationFacade.getInstance();

public static const LOGIN:String  = "LoginView";
public static const LIST:String    = "ListView";
public static const FORM:String  = "FormView";
public static const INFO:String   = "InfoView";

// Read-only context
private function get context():String
{
// if there is no context, then we are on the LoginView,
// set by firstView (we can't set a context for firstView)
return (navigator.context)?String(navigator.context):LOGIN;                  
}

//--------------------------------------------------------------------------
// Below methods satisfy IMyMobileApp interface
//--------------------------------------------------------------------------

// GET THE ACTIVE VIEW
public function getActiveView():View
{
// Since the LoginView is specified as the 'firstView'
// we need to mediate it in the traditional way, which means
// we need a way to get the active view from the mediator
return navigator.activeView;                  
}

// SHOW THE LOGIN VIEW
public function showLoginView( ):void
{
// Only return to LoginView if we're not already on it
if ( context != LOGIN ) navigator.popToFirstView();
}

// SHOW THE LIST VIEW
public function showListView( ):void
{
// Only act if we're not already on the ListView
if ( context != LIST )
{
// If returning (from Info or Form views), just pop
// otherwise push a new ListView
var returning:Boolean = ( context == INFO || context == FORM );
if ( returning ) {
navigator.popView();
} else {
navigator.pushView( ListView,  null, LIST );
}
}
}

// SHOW THE FORM VIEW
public function showFormView(  ):void
{
// Only push a FormView if we're not already on it
if ( context != FORM )
navigator.pushView( FormView, null, FORM );
}

// SHOW THE INFO VIEW (PASSING IN INFO VO)
public function showInfoView( infoVO:InfoVO ):void
{
// Only push an InfoView if we're not already on it
// Pass in the InfoVO from the note body
if ( context != INFO )
navigator.pushView( InfoView, infoVO, INFO );                  
}


]]>
</fx:Script>

</s:ViewNavigatorApplication>

The Application Facade
:
package com.me.myapp
{
import com.me.myapp.controller.command.StartupCommand;
import com.me.myapp.controller.constants.AppConstants;
import com.me.myapp.view.IMyMobileApp;

public class ApplicationFacade
{

public static function getInstance() : ApplicationFacade
{
if ( instance == null ) {
instance = new ApplicationFacade();
}
return ApplicationFacade( instance );
}

public function startup( app:IMyMobileApp ):void
{
registerCommand( AppConstants.STARTUP, StartupCommand );
sendNotification( ViewerConstants.STARTUP, app );
}
}
}

The Startup Command
:
package com.me.myapp.controller.command
{
v import com.me.myapp.model.proxy.InfoProxy;
import com.me.myapp.model.proxy.ThingProxy;
import com.me.myapp.view.IMyMobileApp;
import com.me.myapp.view.mediator.ApplicationMediator;

import org.puremvc.as3.interfaces.INotification;
import org.puremvc.as3.patterns.command.SimpleCommand;

public class StartupCommand extends SimpleCommand
{
override public function execute( note : INotification ) : void
{
// Prepare the Controller
facade.registerCommand( AppConstants.MEDIATE_VIEW, MediateViewCommand );            
// Prepare the Model
facade.registerProxy( new ThingProxy( ) );
facade.registerProxy( new InfoProxy( ) );

// Prepare the View
var app:IMyMobileApp = IMobileViewer( note.getBody() );
facade.registerMediator( new ApplicationMediator( app ) );
}
}
}


The Application Mediator
:
package com.me.myapp.view.mediator
{
import com.me.myapp.controller.constants.AppConstants;
import com.me.myapp.view.IMyMobileApp;
import com.me.myapp.model.InfoVO;

import flash.events.Event;

import org.puremvc.as3.interfaces.INotification;
import org.puremvc.as3.patterns.mediator.Mediator;

import spark.components.View;

public class ApplicationMediator extends Mediator
{
public static const NAME:String = "ApplicationMediator";

// CONSTRUCTOR - HANDLE COMPONENT BY ITS INTERFACE
public function ApplicationMediator( app:IMyMobileApp )
{
super( NAME, app );
}

// CALLED AT REGISTRATION - LIST NOTIFICATION INTERESTS
override public function listNotificationInterests():Array
{
return [ AppConstants.SHOW_LOGIN,
AppConstants.SHOW_LIST,
AppConstants.SHOW_FORM,
AppConstants.SHOW_INFO,
      ];
}

// CALLED AT REGISTRATION - LISTEN FOR EVENTS, MEDIATE FIRST VIEW
override public function onRegister():void
{
// Listen for Views to be added to the ViewNavigatorApplication
app.addEventListener( Event.ADDED, handleAddedEvent );

            
// Mediate the firstView (currently the LoginView)
sendNotification( AppConstants.MEDIATE_VIEW, app.getActiveView() );
}

// HANDLE NOTIFICATIONS TO CHANGE VIEWS
override public function handleNotification( note:INotification ):void
{
switch ( note.getName() )
{
case AppConstants.SHOW_LOGIN:
app.showLoginView();
break;

case AppConstants.SHOW_LIST:
app.showListView();
break;

case AppConstants.SHOW_FORM:
app.showFormView();
break;

case AppConstants.SHOW_INFO:
app.showInfoView( InfoVO( note.getBody() ) );
break;
}
}

// HANDLE ADDED EVENTS (Event.ADDED)
private function handleAddedEvent( event:Event ):void
{
if ( event.target is View ) {
sendNotification( AppConstants.MEDIATE_VIEW, event.target );
}
}
        
  // CAST THE VIEW COMPONENT TO THE PROPER TYPE
private function get app():IMyMobileApp
{
return viewComponent as IMyMobileApp;
}
}
}


The Mediate View Command
:
package com.me.myapp.controller.command
{
import com.me.myapp.view.component.InfoView;
import com.me.myapp.view.component.ListView;
import com.me.myapp.view.component.LoginView;
import com.me.myapp.view.component.FormView;
import com.me.myapp.view.mediator.InfoViewMediator;
import com.me.myapp.view.mediator.ListViewMediator;
import com.me.myapp.view.mediator.LoginViewMediator;
import com.me.myapp.view.mediator.FormViewMediator;

import org.puremvc.as3.interfaces.INotification;
import org.puremvc.as3.interfaces.IMediator;
import org.puremvc.as3.patterns.command.SimpleCommand;

import spark.components.View;

public class MediateViewCommand extends SimpleCommand
{
/**
* Each View is destroyed once it is moved away from,
* thus Mediators must also be transient. Any existing
* Mediator is removed, and a new one registered
* along with the new View. Any additional mediators
* associated with the children of these Views should be
* registered and removed the onRegister() and onRemove()
* methods of the View's Mediator.
*/
override public function execute( note:INotification ):void
{
var view:View = View( note.getBody() );
var mediator:IMediator;
switch ( view.className )
{
case "LoginView":
facade.removeMediator( LoginViewMediator.NAME );
mediator = new LoginViewMediator( view as LoginView );
break;

case "ListView":
facade.removeMediator( ListViewMediator.NAME );
mediator = new ListViewMediator( view as ListView );
break;

case "FormView":
facade.removeMediator( FormViewMediator.NAME );
mediator = new FormViewMediator( view as FormView );
break;

case "InfoView":
facade.removeMediator( InfoViewMediator.NAME );
fmediator = new InfoViewMediator( view as InfoView );
break;
}
if (mediator) facade.registerMediator( mediator );
}
}
}

The List View
:
<?xml version="1.0" encoding="utf-8"?>
<!-- LIST VIEW -->
<s:View xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:fx="http://ns.adobe.com/mxml/2009"
destructionPolicy="never">


<!-- LIST STUFF HAPPENS HERE -->

</s:View>


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: dries on November 28, 2011, 09:15:45
thx for the detailed explanation.

Had some time to experiment a bit and came to more or less the same solution. I'll try to highlight the things i did different:

Remove mediators

Remove mediators when a view is removed. My thought why this is important: If you keep the mediator, you keep a reference to the view. So the view can never be garbage collected until the mediator is also removed.

In your solution, you keep the current mediator until the next time a view of the same Class is created. This implies that once your application has visited all views, you use the same amount of memory as creating all views on startup and keep a fixed reference to them.

No centralized registration of mediators

I'm registering my mediators by listening to the ElementExistenceEvent.ELEMENT_ADD events of the viewNavigator and TabbedViewNavigator.

I'm removing my mediators by listening to the ElementExistenceEvent.ELEMENT_REMOVE events.

This way, you can reuse the same View Class in for example two different ViewNavigators in one TabbedViewNavigator.

Example: You have an application with a View that can represent a list of persons: PersonListView. In one Tab you display family in a family viewNavigator, in a second tab you display friends in a friends viewNavigator, .. By splitting the registration of the mediators, you can reuse the same View: When a PersonListView view is created in the family viewNavigator, you create a family mediator which populates the view with family data. When the a PersonListView is created in the friends viewNavigator, your create a friend mediator which populates the view with friends data.

When you register your mediators in one centralized command, you can not know with what data that view should be populated.. or is there an other way to do this? If you could do this in any other way, i would prefer the centralized solution.. I don't think you can use the id property, cause there is no way to set it when you call the navigator.pushView(View) method...

EDIT: Just noticed that the third parameter of the pushView method,  'context' can be used in order to reuse view components. Going for the centralized approach..


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: puremvc on November 28, 2011, 10:31:01
Remove mediators when a view is removed.
A good optimization, certainly. You can have the Mediator itself listen to the View for Event.REMOVED, call facade.removeMediator(this.getMediatorName()) in response.


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: purenewb on December 13, 2011, 03:41:58
I am just getting started with PureMVC and I have used it with a Flash project written in Flash Builder with great results. I would like to use the framework on a Flex Mobile application that I am building, but I can't figure out how to adapt it to the TabbedViewNavigatorApplication. I tried listening to ElementExistenceEvents as pointed out above, but I cannot receive the events on the application root or tabbedNavigator, etc.

Does someone have a clearer real world example outlining how to build a tabbed mobile app with PureMVC?

Thank you!


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: puremvc on December 13, 2011, 04:34:15
I tried ElementExistenceEvent and had difficulties as well. But there are lots of life cycle events that you could listen for, or you could have your Views dispatch a bubbling custom event on creationComplete of the View in question.

Actually if you look at my ViewNavigatorApplication example a couple of replies back, you'll see I'm listening for Event.ADDED and then mediating if the target of the event is a View.


In the ApplicationMediator
In onRegister():
:
// CALLED AT REGISTRATION - LISTEN FOR EVENTS, MEDIATE FIRST VIEW
override public function onRegister():void
{
// Listen for Views to be added to the ViewNavigatorApplication
app.addEventListener( Event.ADDED, handleAddedEvent );

            
// Mediate the firstView (currently the LoginView)
sendNotification( AppConstants.MEDIATE_VIEW, app.getActiveView() );
}

handleAddedEvent():
:
// HANDLE ADDED EVENTS (Event.ADDED)
private function handleAddedEvent( event:Event ):void
{
if ( event.target is View ) {
sendNotification( AppConstants.MEDIATE_VIEW, event.target );
}
}

The TabbedViewNavigator allows multiple navigators, but the ViewNavigators may not need to be mediated themselves. You can probably follow a similar pattern to the above example, just with TabbedViewNavigatorApplication instead of ViewNavigatorApplication.

BTW, a refinement of the above example appears in the ActionScript Developer's Guide to PureMVC which will be published by O'Reilly later this month.

-=Cliff>


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: purenewb on December 14, 2011, 04:00:48
Thanks Cliff. Looking forward to the book!


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: gary.paluk on January 19, 2012, 06:41:48
Hi Cliff,

I'm just trying to solidify the way that you would access the methods in MyMobileApp? The reason I ask is that if you are mediating a view, lets say you default first view; LoginView, we shouldn't directly access the MyMobileApp in order to call these methods so what is the approach here?

Regards


Title: Re: Flex Mobile ViewNavigator and PureMVC
Post by: puremvc on January 20, 2012, 09:56:52
Gary,

if you are mediating a view, lets say you default first view; LoginView, we shouldn't directly access the MyMobileApp in order to call these methods so what is the approach here?

There is no problem with the mediator calling methods on the view component it tends. Along with setting and getting its top level properties and listening to it for Events, calling methods is the recommended way to communicate with the component. Calling a method on the component helps the Mediator be able to get (or set) what it needs from the component without having to break the encapsulation of the component by 'reaching into it' and manipulating its internals directly.

In the code I posted above, you can see in the ApplicationMediator.onRegister() method:

:
  // Mediate the firstView (currently the LoginView)
   sendNotification( AppConstants.MEDIATE_VIEW, app.getActiveView() );

The application exposes a method that the ApplicationMediator can call to get the active view, which it sends off in a note handled by a command that will do the registration.

Does that answer your question? I'm not sure I was completely clear on what you were looking for...

Cheers,
-=Cliff>