RobotLegs Example – Clock App

20-09-2010 I’m currently re-factoring the Clockapp App as after submitting it to the RobotLegs Knowledge base I got back a list of things to fix, like I shouldn’t add views to other views in a command :) . There is always stuff to learn!
If you are interested the re-factored app is in the BlockageReview branch here.

There is a lot of talking about RobotLegs Micro-Architecture Framework in the Flash community and, as it is derived from PureMVC Framework with which I had a really good relationship and because I would like to know if we can use it for our current project at work, I thought I give it a try.

To test it out I thought to implement one of the most simple yet clear example of a MVC application:

A clock which can display the time in different ways (e.g. Analog, Digital, or a TokyoFlash way check it out here ).

Clock MVC

So lets have a look at what can be the Model, Views and Controllers for a clock:

  • Model

    from Wikipedia: “The model is the domain-specific representation of the data upon which the application operates.”

    So for a clock we can say that the Model is the time itself.

  • View

    from Wikipedia: “The view renders the model into a form suitable for interaction, typically a user interface element.”

    In our case the Views are the representation of the time as Digital or Analog clocks.

  • Controller

    from Wikipedia: “The controller receives input and initiates a response by making calls on model objects.”

    For a clock a controller could be the tick mechanism that defines the change in the time.

But lets see some flashy things! Here is the final result for the Clock demo:

Get Adobe Flash player

The code for this demo can be found here on gitHub:


feel free to clone it test it change it fork it do whatever you want with it!

Here is a diagram to describe the application:

ClockApp:

Main class of the application. This is where we initialize the RobotLegs Context to wire up all the components of the application.

ClockContext:

From the RobotLegs best practice

Framework actors live within a context and communicate with one another within the scope of that context

The context is where we map commands to events, map mediators to views and define model/service actors.

Mapping commands:

The context object has two important phases STARTUP and SHUTDOWN. In the clock demo I’ve mapped some commands to the STARTUP_COMPLETE event:


        override public function startup():void
        {
            commandMap.mapEvent(ContextEvent.STARTUP_COMPLETE, SetupStage);
            commandMap.mapEvent(ContextEvent.STARTUP_COMPLETE, AddClocks);
            commandMap.mapEvent(ContextEvent.STARTUP_COMPLETE, AlignClocksOnStage);
            commandMap.mapEvent(ContextEvent.STARTUP_COMPLETE, StartClock);

            ...

those commands will be executed in the sequence they are mapped, when the context fires the STARTUP_COMPLETE event.

Defining Model/Service actors:

Next we define our ClockModel as a Singleton


    injector.mapSingleton(ClockModel);

this means that we will have only one instance of the ClockModel class through all the execution of our application.
We should also notice that we are not creating manually a new instance of the ClockModel using the new keyword and we also haven’t structured the ClockModel class to be a Singleton, we leave RobotLegs to handle it. RobotLegs is clever enough to automatically instantiate the ClockModel class the first time that an Actor asks for it and then it keeps a copy of it to be able to inject it to the other Actors when they need it.

In the clock demo application the ClockModel will be instantiated when the StartClock command is executed, why?
Let’s take a look at the code for the StartClock command:


    public class StartClock extends Command
    {
        [Inject]
        public var clockModel:ClockModel;

        override public function execute():void
        {
            clockModel.start();
        }
    }

I bet you have noticed the Metatag [Inject] just before the declaration of the class member clockModel. That is what RobotLegs is looking for. What RobotLegs does (by using SwiftSuspender) is recognizing that this command wants to use an instance of the ClockModel and so it will check into its own mapping tables to find out first, the type of mapping and second, what it needs to inject into that variable.

Depending on the type of mapping RobotLegs will act in different ways. In our case the mapping is a mapSingleton so what RobotLegs is going to do is check if it already has got an instance of the ClockModel class, if not (that is our case) create a new instance and injects it into the command variable.

So that is how the ClockModel instance is created by RobotLegs.

Next a little trick to be able to map a Mediator to the main application Sprite.
This is useful when you wish to have control on the main application Sprite in the same way as all the other Views using RobotLegs.
What you do is mapping the main application class to a mediator and then force the creation of the mediator by calling the createMediator method passing the contextView which is the instance of the main application class.
Now ClockAppMediator can control the main application Sprite.


mediatorMap.mapView(ClockApp, ClockAppMediator);
mediatorMap.createMediator(contextView);

I did this in the clock demo because I needed to listen to the Event.RESIZE event to then be able to re-align the clock Views on the stage while resizing, and I thought that they were responsibilities of the main application class.

Ok now we have the model, we need to display the model data. What we do is creating the Views.


mediatorMap.mapView(AnalogClock, ClockMediator, Clock);
mediatorMap.mapView(DigitalClock, ClockMediator, Clock);

AnalogClock and DigitalClock are the two views we want to use, they extend the Sprite class and implement the Clock interface (we’ll look at this later on).
As for RobotLegs each view needs a Mediator and here are the Mediator responsibilities so we can understand why they are needed:

A Mediator listens for Events on its View Component, and accesses data directly on the View Component via its exposed API. A Mediators acts on behalf of other framework actors by responding to their Events and modifying its View Component accordingly. A Mediator notifies other framework actors of Events created by the View Component by relaying those Events, or dispatching appropriate Events to the framework.

So the mediators are the point of connection between views and all the other RobotLegs Actors.

When are they instantiated in our application?
When the AddClocks command is executed. Let’s see the AddClocks command in detail:


    public class AddClocks extends Command
    {
        override public function execute():void
        {
            contextView.addChild(new AnalogClock());
            contextView.addChild(new DigitalClock());
        }
    }

You can see that the two views are created and added to the contextView which is the main DisplayObjectContainer created by RobotLegs. In this case, because already mapped, RobotLegs will automatically create the Mediators for each View and also inject in each Mediator a reference of the View itself.
How? Let’s have a look at the ClockMediator Class.

    public class ClockMediator extends Mediator
    {
        [Inject]
        public var clockModel:ClockModel;

        [Inject]
        public var clockView:Clock;

        public function ClockMediator()
        {

        }

        ...

This is just part of the class but it’s where we can find how RobotLegs knows what to inject into the Mediator when created.
In this case RobotLegs will inject the ClockModel reference (that is a Singleton and already instantiated) and the relative View instance.
From now on the Mediator will have access to both Model and View and it will be able to mediate between them.

That’s it we can now let RobotLegs execute its startup procedure:

super.startup();

One thing I haven’t explained above is how the views get updated. To find out we need to look at both ClockModel and ClockMediator.

ClockModel:

    public class ClockModel extends Actor
    {
        private var clockTimer:Timer;

        public function ClockModel()
        {
            super();
            createTime();
            createTimer();
        }

        private function createTime():void
        {
        }

        private function createTimer():void
        {
            clockTimer = new Timer(1000);
            clockTimer.addEventListener(TimerEvent.TIMER, onClockTimer);
        }

        private function onClockTimer(event:TimerEvent):void
        {
            dispatch(new ClockEvent(ClockEvent.TICK, new Date()));
        }

        public function start():void
        {
            clockTimer.start();
        }

        public function get time():Date
        {
            return new Date();
        }
    }

We can see that the ClockModel class has a Timer object in it, and every second the ClockModel dispatches a ClockEvent.TICK, so our views need to be updated every time the event is fired.
This is a responsibility of the ClockMediator:

    public class ClockMediator extends Mediator
    {
        [Inject]
        public var clockModel:ClockModel;

        [Inject]
        public var clockView:Clock;

        public function ClockMediator()
        {

        }

        override public function onRegister():void
        {
            super.onRegister();
            eventMap.mapListener(eventDispatcher, ClockEvent.TICK, onClockTick);
            eventMap.mapListener(clockView, Event.ADDED_TO_STAGE, onViewAddedToStage);
        }

        private function onViewAddedToStage(event:Event):void
        {
            updateView(clockModel.time);
        }

        private function updateView(newTime:Date):void
        {
            clockView.time = newTime;
        }

        private function onClockTick(event:ClockEvent):void
        {
            var newTime:Date = event.time;
            updateView(newTime);
        }
    }

In the onRegister method we map a listener to the ClockEvent.TICK so every time the event is fired the mediator will update the view.

We can notice one of the advantages of using the MVC architectural pattern, the model is not tightly coupled with the views, business logic is separate from the view representation.

Another aspect that I like about RobotLegs ( and also PureMVC ) is that it almost forces you to separate responsibilities. This could lead to a growing number of classes but I can confirm that having a lot of small classes with specific responsibilities is better than having a big class that does everything. It is more flexible, scalable and maintainable.

So what are the advantages of using RobotLegs (or shall we say the MVC pattern)?
Let’s go back to the beginning where we set the target for the Clock Application. We wanted to display the time in different ways. So far we’ve implemented two ways Analog and Digital, what if we want to add another view?
We will just need to create a new View which implements the Clock interface and then map it to the ClockMediator in the startup method of the ClockContext class.
Then we also need to change the AddClocks command so that we add the new View to the main container.
That’s it!
You can notice that we have just added our new view and changed a command, we haven’t touched the model at all.

Another scenario : We want to display the time of a different time zone, what we do is changing just the ClockModel, nothing else.

Hope this example helps you understand a bit more about RobotLegs and the MVC architectural pattern so if you have any thoughts, questions or if you’ve spotted an error please feel free to leave a message.

Vizio

This entry was posted in AS3, Flash, RobotLegs and tagged , , , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.
  • http://alecmce.com Alec McEachran

    This is awesome @vizio360, I’m going to point anyone starting out using RobotLegs at this!

  • http://alecmce.com Alec McEachran

    This is awesome @vizio360, I’m going to point anyone starting out using RobotLegs at this!

  • http://www.ubervu.com/conversations/blog.vizio360.co.uk/2010/04/robotlegs-example-clock-app/ uberVU – social comments

    Social comments and analytics for this post…

    This post was mentioned on Twitter by vizio360: new blog post : #RobotLegs example – Clock App http://blog.vizio360.co.uk/2010/04/robotlegs-example-clock-app/...

  • http://joelhooks.com Joel Hooks

    “those commands will be executed in the sequence they are mapped”

    I don’t know if this is something that can be absolutely relied on. Not that it doesn’t obviously work, but in a “real” application I’d want to chain them more logically. I’m a control freak about bootstrapping though.

    This article is great. I love the analog clock. I want to hang it on my wall!

  • http://joelhooks.com Joel Hooks

    “those commands will be executed in the sequence they are mapped”

    I don’t know if this is something that can be absolutely relied on. Not that it doesn’t obviously work, but in a “real” application I’d want to chain them more logically. I’m a control freak about bootstrapping though.

    This article is great. I love the analog clock. I want to hang it on my wall!

  • Anonymous

    Hi Joel,

    I think I read it somewhere in the RobotLegs documentation, but maybe I’m wrong. I’m going to find it out and update the blog post if necessary.

    Thanks for the feedback! :) really appreciated it.

  • vizio

    Hi Joel,

    I think I read it somewhere in the RobotLegs documentation, but maybe I’m wrong. I’m going to find it out and update the blog post if necessary.

    Thanks for the feedback! :) really appreciated it.

  • Anonymous

    Cheers @alecmce.

    Hope it can also help you in your mission to convert your co-workers to use RobotLegs :)

  • vizio

    Cheers @alecmce.

    Hope it can also help you in your mission to convert your co-workers to use RobotLegs :)

  • http://twitter.com/markfoxisadj Mark Fox

    I’m a little late to the convo but just adding kudos, on the great RL intro. I agree with Alec that this will be my go-to reference for anyone interested in getting an nice snapshot of how the framework works.

blog comments powered by Disqus