Flex widget communication PoC

May 29
2009

Problem : How do you get several distinct Flex widgets on a page to communicate?

By distinct here I mean that each widget is a separate embedded SWF file, i.e. they don’t share the same Application.

There are actually several ways to do this, and an appropriate choice really depends on the kind of data you want to share and when. What I needed was real-time communication; say I have one widget that searches for a particular resource (in my case they’re bonds), when the user selects a resource, that widget should propagate the selection to all the others on the page. It was also a requirement that the number and identity of the widgets on the page be an unknown – i.e. I can’t assume that there will always be exactly three widgets called Fred, Bob and Jane.

Not being a Flex expert, my first thought was to use a SharedObject. This seemed like a good idea since apparently they “offer real-time data sharing between multiple client SWF files”, which is exactly what I want. However, this is only possible if you’re using Flash Media Server. I don’t really need that round-trip to the server each time and I don’t need to communicate between multiple clients, just among SWF files on the same page at a single client. I also don’t have Flash Media Server. I did toy around briefly with a polling solution using local SharedObjects though. The server SWF would contain something like this :

public function syncSharedObject(sharedText:String):void
{
    var so:SharedObject = SharedObject.getLocal("test", "/", false);
    so.data.sharedText= sharedText;
    so.flush();
}

And the client SWF would contain code to poll the SharedObject periodically to check for updates (I’m not going to show the boilerplate code that sets up the Timer instance):

public function syncSharedText(event:TimerEvent):void
{
    var so:SharedObject = SharedObject.getLocal("test", "/", false);
    sharedText = so.data.sharedText;
}

Simple! But also fairly braindead. This is a pull architecture – the clients aren’t notified when the data has changed, so they have to keep looking at it (say every half second), which creates some annoying overhead – each widget interested in “sharedText” would have to poll the SharedObject and that could be a lot of widgets looking at one piece of data. Of course, this gets worse if we need to pass around lots of different pieces of data!

So, back to the drawing board and a little more reading, after which I discovered LocalConnection. This seems to do pretty much everything I need, but it’s definitely more complex to get up and running with. What I want is one widget to act as a server and notify all the other widgets about changes to relevant data, however I can’t designate a specific widget as the server because I don’t know if it will always be present. A simple solution would be to let the first widget to load grab the publisher/server functionality and all subsequently loaded widgets would then be subscribers/clients (I decided to call the class MessageManager, and, since it will need to raise events, it extends EventDispatcher) :

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//constructor (I'm not going to write out all the class definition code)
public function MessageManager(target:IEventDispatcher=null)
{
    //Generate a locally unique ID.
    this.subscriberID= UIDUtil.createUID();
 
    try
    {
        //First we try to connect as the server.
        conn = new LocalConnection();
        conn.connect("Server");
        conn.client = this;
        isServer = true;
 
        //initialise the subscriber cache.
        subscribers = new Object();
    }
    catch (e:Error)
    {
        //The server probably already exists; register ourselves with it.
        isServer = false;
        conn = new LocalConnection();
        conn.connect(subscriberID);
        conn.client = this;
        conn.send("Server", "registerSubscriber", this.subscriberID);
    }
 
    super(target);
}
 
public function registerSubscriber(subscriberID:String):void
{
    if (!subscribers[subscriberID])
         subscribers[subscriberID] = true;
}

There are a fair few assumptions in this code (there’s no real error handling, for a start), but it works well enough for a PoC. It may seem a little confusing to use the same class for both publisher and subscriber interfaces, and it would be entirely feasible to separate them out, but I didn’t consider that necessary for such a small scale test.

With the publisher and subscribers loaded and registered, I now need to be able to send a message. I created a MessageManagerEvent class which extends Event with an additional data property, this contains the “message” to be passed around (a fairly common pattern – the Flex store sample application uses something similar but calls it ObjectDataEvent). For a widget to then update all other widgets, it just needs to create a MessageManagerEvent and call the MessageManager.sendMessage method. sendMessage determines whether the current instance of MessageManager is the server or a subscriber and either sends a single message to notify the server or iterates over the subscribers collection notifying each one in turn. I created a simple application to demonstrate this in action and included two of them below (you should be able to view source on either) :

Just enter some text into either box and you should see it copied across to the other application.

If you refresh the page a few times, you may notice the “SERVER” and “SUBSCRIBER” labels flip back and forth between the widgets, depending on which one loads first. If you open a new copy of this entire page, the new instances should both be saying “SUBSCRIBER” (assuming you keep this page open). Even more interesting is that if you open a copy of this page in another browser entirely, you may find that those say “SUBSCRIBER” as well, and that text is propagated to them from the original browser instance! (I’ve noticed this works between FireFox and IE so far, YMMV, of course).

A few points to note :

  • You can use more complex objects as messages, but each widget must have a reference to that type compiled in, otherwise it won’t deserialise correctly.
  • Any type used as a message must have a default constructor, or at least a constructor with default parameters.
  • This is far from being a usable implementation, since it doesn’t account for the possibility of the server or any subscriber dying. Ideally there would be a mechanism for a subscriber to take over the server role if the server is removed, though this would probably require use of a SharedObject to persist the subscriber list.

Update

What I’ve done here was for my benefit really – a learning exercise. What Blitz Agency has done here is something on another level altogether. Under the hood it’s all still LocalConnection based though.

Leave a Reply

Visit Our Friends!

A few highly recommended friends...

Archives

All entries, chronologically...

Pages List

General info about this blog...