StateMachine Configure_views_complete question

brian.m.thomas's Avatar

brian.m.thomas

05 Nov, 2010 03:10 PM

Hi,

I am trying to use the stateMachine and premise discussed in this post. I am having trouble with firing my CONFIGURE_VIEWS_COMPLETE event. Dispatching the event at the end of the ConfigureViewsCommand does not seem to work, I have mediators that are map and added to the stage and their onRegister events still fire after the ConfigureViewsCommand fires. I have added a CONFIGURING_USER event after the CONFIGURING_VIEWS event inside the AppConfigStateConstants so the LoginUserCommand should fire on the transition action of CONFIGURE_VIEWS_COMPLETE.

ConfigureViewsCommand:

 mediatorMap.mapView(TestApplication, ApplicationMediator);
 mediatorMap.mapView(ToolbarView, ToolbarViewMediator);
 mediatorMap.mapView(MainView, MainViewMediator);
 eventDispatcher.dispatchEvent( new StateEvent(StateEvent.ACTION, AppConfigStateConstants.CONFIGURE_VIEWS_COMPLETE));

This gives me a trace of

 ApplicationMediator onRegister()
 LoadUserCommand execute()
 MainViewMediator onRegister()
 MainToolbarMediator onRegister()

In my TestApplication.mxml file:

 <components:MainToolbar />
 <components:MainView />

dispatching the CONFIGURE_VIEWS_COMPLETE event after the mediatorMap appears to be the wrong place as views are configured, they have yet to call onRegister. If both the MainToolbar and MainView mediators are listening for a USER_LOADED event which is fired from a service called from the LoginUserCommand, where should I fire the CONFIGURE_VIEWS_COMPLETE event?

I am really confused and struggling, where should I send an event to call my LoginUserCommand? I am thinking I have to wait until all mediators have registered but I don't see a RobotLegs way to capture this event.

Thanks in advance!

  1. Support Staff 2 Posted by Joel Hooks on 05 Nov, 2010 03:21 PM

    Joel Hooks's Avatar

    You are configuring the views (mapping them to the framework) which I don't think makes the assumption that all of the views will be mediated at that time. If you wanted to make sure a set of view components was ready to roll, then I would probably set up some sort of model that captured that data and dispatched the appropriate event when the desired state has occurred.

    In the above, your views are mapped (configured), but they aren't ready for input. You need to get to the state where they are ready for input, which will need to be captured and defined somehow.

  2. 3 Posted by brian.m.thomas on 05 Nov, 2010 03:46 PM

    brian.m.thomas's Avatar

    Thanks for the quick response. I put a dummy StateModel inside each view and fired a trace on it's PostConstruct function and it is constructed before the onRegister events of the mediators. I am confused on how the model would capture a set of view component events, could you possibly give me more details on that please?

    The only way I have seen to ensure the onRegister event has fired for every mediator is to wait for the applicationComplete event but I'd rather not resort to that unless necessary. Any further details/psuedocode is much appreciated, I have been struggling with this for a couple days and nights.

  3. Support Staff 4 Posted by Joel Hooks on 05 Nov, 2010 04:19 PM

    Joel Hooks's Avatar

    What is you you actually need configured on the views for the login command to work? Maybe if you called this state "MAP_VIEW_MEDIATORS" - because it isn't fully configuring the views.

    Do you need a specific view to be ready?
    A specific model to be ready?

    If yes, how do you know when they are ready? That is the transition you need. Do you need all of the views to be ready?

  4. 5 Posted by brian.m.thomas on 05 Nov, 2010 05:10 PM

    brian.m.thomas's Avatar

    The mediators are listening for user_loaded events in order to request their user specific data such as tasks. The mediators need the userModel to be ready, validated and loaded with current information.

    Since the user_loaded event is happening asynchronously from each mediator onRegister call my onRegister functions look like the following:

    override public function onRegister():void
    {
         if (user.loaded)
                 eventDispatcher.dispatchEvent( new LoadUserSpecificTaskInformation())
         else
                 eventMap.mapListenener(eventDispatcher, UserEvent.USER_LOADED, handleLoginSuccess);
    }
    
    protected function handleLoginSuccess(userEvent:UserEvent):void
    {
             eventDispatcher.dispatchEvent( new LoadUserSpecificTaskInformation());
    }

    I am thinking this is not the most elegant/best practices solution? I was thinking it would make more sense to wait for all my mediators to register and then request the user login information. All the mediators would then respond to a USER_LOADED event and request their appropriate data.

    One thing I probably should have pointed out before is that the user credentials are received from a certificate, not a user prompt so authentication can occur at any point during RobotLegs initialization. I am trying to find the best location for this to occur. The more I think about this, I think I am over thinking the situation and the above code sample should be sufficient. Would forcing all Mediators to respond on user_loaded events be wrong?

    Thanks

  5. 6 Posted by brian.m.thomas on 05 Nov, 2010 05:43 PM

    brian.m.thomas's Avatar

    After taking some time and re-thinking everything I figured it out. I am going to change up my StateMachine Events:

     ConfigureCommandsCommand
     ConfigureServicesCommand
     ConfigureModelsCommand
     LoginUserCommand
     ConfigureViewsCommand

    On successful user authentication a command will dispatch the appConfigStateConstants.LOGIN_USER_COMMAND_COMPLETE. The statemachine will then fire the appConfigStateConstants.CONFIGURE_VIEWS event. Then the ConfigureViewsCommand will function as normal. All mediators and views in this command will have access to user information. This is my first project using Robot Legs so I am a bit shaky. Btw, the statemachine is really cool :) Thanks!!!

  6. 7 Posted by Stray on 05 Nov, 2010 05:59 PM

    Stray's Avatar

    Hi Brian,

    I actually have something kind of similar in the project I'm working on at the moment.

    It's an admin tool with multiple screens. Each screen needs a certain combination of data - if it has everything it needs then it goes ahead and displays, and if it still needs stuff then the user is presented with a 'download data for this screen' button which triggers loading of everything still missing.

    At first the way I was managing it is that each screen has a screenDataManager that holds on to any data that has arrived, and when it has everything it needs it refires the events that the data was sent on. For the screen that is actively downloading, that works great.

    But if screenB needs a subset of screenA's data, and screenA has already loaded, then the screenB data events have fired before all the screenB bits are on stage - and the various nested mediators haven't run their onRegisters yet, so the view isn't updated with the data. Empty views... *sob*

    Annoying!

    So - what I have is a hybrid approach using signals. I tend to use something similar whenever the data arrives before the onRegister has run.

    The screenDataManagers and the mediators have a Signalton injected into them. That Signal is a request signal - meaning that it requires a response signal to be sent as the first parameter.

    I dispatch the request in the onRegister - dispatching the data type I'd like and the responseSignal to send it back on. The response signals are created locally so they're specific to each mediator. I use the event handler that otherwise picks the event up async as the signal handler.

    Could be a mile away from what you need, but it's not really that complex and it's working nicely.

    It kind of feels weird to me to be using events AND signals for the same purpose, but it's doing the job and I don't feel the need to refactor yet. I need to keep the event side as well because this isn't a one-time event, updates can happen later too.

    Anyway - I've shoved the key parts in this gist: https://gist.github.com/664521

    Of course if you're only waiting on one data type you can simplify this by using the SignalCommandMap instead. Map the request signal to a command that grabs the data from the model and pushes it back down the response signal. Only respond if the data has arrived. If it hasn't then the onRegister will have a chance to register for the event before it's fired anyway.

    Might be helpful,

    Stray

  7. 8 Posted by brian.m.thomas on 05 Nov, 2010 06:13 PM

    brian.m.thomas's Avatar

    Thanks for the information, Stray. I'm glad to hear I'm not alone with regards to this issue, I was surprised to find a lack of information regarding this on the discussion board (again, I am new and may have not found the correct discussions). Have you tried using the stateMachine for your data loading/initialization concurrency? I am currently going down the stateMachine path I explained in my last comment and if you chose to not use stateMachine for some reason please let me know. You seem to be doing quite well with your current implementation and it seems slightly different than mine but any information regarding stateMachine is appreciated.

    A portion of my application sounds very similar to your signal implementation and I will take a closer look shortly.

    Thanks for the detailed information!

  8. 9 Posted by Stray on 05 Nov, 2010 06:44 PM

    Stray's Avatar

    Hi Brian,

    I'm not using StateMachine - the admin system does a whole bunch of different things and the administrator might only have logged in to do a single task, so we're loading data as-needed rather than loading it all in one bunch. The complexity is that there's a fair bit of overlap between the data required for each task-focussed screen.

    That said, I could have implemented a statemachine in each individual 'screen', but actually the display list + RL mediators provide you with a pretty nice state-machine-ish thing on their own.

    My set up is:

    1. Each screen is a module - it has its own context etc. It is initially in a state of 'needing data' - it shows the user only a 'download data for this screen' button.
    2. Each screen has a ScreenDataManager which has been told that it needs a certain combination of data types.
    3. Data is loaded through another module and arrives on events on the ModuleEventDispatcher.
    4. When each type of data arrives, a one-shot command receives that data for each screen that is interested.
    5. This command basically just hands the data event to the ScreenDataManager.
    6. The ScreenDataManager picks up the payload, and shoves it in a dictionary according to type.
    7. The ScreenDataManager also holds on to the event that sent the data.
    8. After each receipt, the ScreenDataManager checks if it has everything it needs (as was set up in 2).
    9. When it does have all the data wanted for this screen, it fires an ALL_DATA_READY event.
    10. The screen mediator activates the screen - putting the various view parts on stage.
    11. The ScreenDataManager re-dispatches all the data events it received.

    In most cases, this is enough. But if a screen only needs a subset of data loaded by other screens already, then the dispatch of all the data can happen when the screen isn't yet ready - the user is looking at a different screen. I *do* want the screen to transition state at this point - so that if the user goes to a screen that has its data they don't ever see a 'download data' button, even though this would be a heck of a lot easier for me to build - I could just fire the events when they've pressed the button.

    So - that's why I'm using the signals approach to pull the data if it's available.

    It's possible that if I was starting again from scratch I might look at a state machine implementation more formally - I hadn't anticipated this problem as the first three screens I built all needed at least one piece of data that was unique to them. Screens that need a subset of data that can be loaded by other screens are a minority, hence my workaround rather than reconfigure the whole deal.

    On the whole I do like the request/response signal approach for any occasion where onRegister might run after the relevant data event fires.

    I should confess that I can't actually figure out how StateMachine could help me in this situation. I'd have to some how know when the most deeply nested onRegister had run, and that feels a lot like a wrong approach.

    I did toy with just forcing a delay and then asking my ScreenDataManager to fire the data again... but ick. Ick ick. Or having the mediators respond when they've got their data and somehow pick that up and and and...

    in comparison the signal approach feels much neater! Yes, there are 2 slightly different paths through the logic, but that's because there are 2 paths through the user experience.

    I could be way off though!

  9. 10 Posted by brian.m.thomas on 05 Nov, 2010 08:19 PM

    brian.m.thomas's Avatar

    Stray,

    Thanks again for the information. I've been looking into your modular approach for robot legs. I've also read Joel Hooks v1.1 adaptation (both, very nice reads!). I'm almost thinking I should use a modular approach in this instance, the shell would validate the user and contain all the user credential information. Upon successful authentication then the application could be loaded as a module. I will most likely have more screens so I am very interested in the idea of modular development.

    My one question going this route, how will the modules know the user information in the shell? Can I inject the AuthenticatedUserModel into each individual module? I admit I have just begun to research this modular development idea and that answer may be very simple.

    My modules in my setup, opposed to yours, will request data. The TaskModule will send a REQUEST_USER_TASKS event where the shell will have to have injected the user data or catch the event prior to being sent to the db to add the user data. Each module should not have to worry about requesting any user credentials. Is it easy to inject shell models into each module?

    Thanks!

  10. 11 Posted by Stray on 05 Nov, 2010 09:33 PM

    Stray's Avatar

    With something like user credentials I just dispatch an event for each module to catch which carries a VO.

    Actually, more accurately, most of the modules are only interested in an MD5 hashed key that gets passed to scripts that need to authenticate to get their data. The data retrieval is done through one data-focussed module, but other modules do things like open reports in the browser etc all of which require this user-creds authentication.

    So - I have an IFingerprint interface, and when the user has logged in to the shell the concrete AdminFingerprint gets fired on the moduleEventDispatcher and a command in each module (same command) just maps it as a value:

    injector.mapValue(IFingerprint, adminFingerprint);

    Then that IFingerprint is injected where needed.

    But that could just as easily be a vo:

    injector.mapValue(UserCredVO, userCredVO);

    The main difference between my implementation and Joels is that mine is compatible with RL 1.0 - Joel's requires a newer version of SwiftSuspenders and robotlegs. I have mine still going because I started this project about a year ago and my users don't have admin rights so we're tied in to the version that was first installed on their machines.

    I believe Joel's supports injection through parent injectors, so if something has been mapped in the parent injector (the shell) it should be available to the child modules. The child injector tries to fulfil the injection itself and if it can't then it looks to the parent.

    Try just shoving an

    [Inject]
    public var authenticatedUserModel:AuthenticatedUserModel;

    somewhere where you can find out if it magically already works... in modular.

    Generally I like to stick to the 'least possible knowledge' implementation though - it seems likely you could get a VO out of the authUserModel that would give the child modules what they need without exposing the API of the model?

    Anyway - I'd definitely recommend the modular approach, it really makes testing and separation of responsibilities straightforward.

  11. 12 Posted by brian.m.thomas on 09 Nov, 2010 04:52 AM

    brian.m.thomas's Avatar

    Stray,

    Thanks again for all the information. I ended up using Joel's implementation of modules. I agree with you regarding not wanting to expose the API of the model. I created 3 separate modules
    1. shell
    2. tasks
    3. schedules

    I have a common package which houses shared widgets, events, interfaces, etc. I have a common\model\user\IUser.as . The implementation of User is defined in the shell and each module is injected via injector.mapSingletonOf(IUser, User), this mapping sits in my ShellContext.as file. Every sub module is oblivious to the shell and should never reference that package. I may possibly pull each module out into separate projects in the future. So far everything seems to work, I am using the stateMachine to load the shell module in order and then authenticate the user. Then, based on the user credentials certain modules/features are loaded. Everything is going great! Thank you both for your help and I apologize for being so spoon-fed in my first couple of posts, this is my first robotlegs experience (PureMVC convert) and I was stressed due to work schedules ;). I'm enjoying Robotlegs, statemachine and the module utilities! I will try to post a sample of my application once I have fine-tuned a couple of things.

    thanks again!

  12. 13 Posted by Stray on 09 Nov, 2010 08:09 AM

    Stray's Avatar

    Brilliant! Great to hear it's working out, and it sounds like you've got a really solid implementation here.

    Enjoying it is such a good sign I always think. I've been working on this RL based app suite for about a year yet and I still enjoy it - previously there was about a six week window between writing code and starting to hate the code I'd written, so RL is breaking new ground.

    Glad we could help - a sample would be fantastic when you get a chance,

    Stray

  13. Stray closed this discussion on 10 Feb, 2011 06:08 PM.

Comments are currently closed for this discussion. You can start a new one.

Keyboard shortcuts

Generic

? Show this help
ESC Blurs the current field

Comment Form

r Focus the comment reply box
^ + ↩ Submit the comment

You can use Command ⌘ instead of Control ^ on Mac