This site uses cookies. Continue to use the site as normal if you are happy with this, or read more about cookies and how to manage them.

×

This site uses cookies. Continue to use the site as normal if you are happy with this, or read more about cookies and how to manage them.

×

Tracking Flex Web Services with Google Analytics

Update (with thanks to those who commented about this): There's an official Google Analytics for Flash/Flex available here with links to information about upgrading to Universal Analytics too. This blog uses similar techniques to Google's approach.

Google Analytics provides an easy to use interface that tells you which pages on your website are being accessed, how often they are accessed, and lots of other useful information about how your website is used. The problem for me on a recent project was to collect usage statistics for a Flex application. To use Google Analytics you simply include a small javascript element in each page that's served. When the browser executes the Javascript a small message is sent to google recording the page URL that was accessed and some other details about the client.

The first problem with a Flex application is that it's really just a single big(ish) download of the swf (Flash) file, so all that will get recorded is the fact that someone accessed your application. If the Flex application has several different views within a view stack none of the navigation between those views will be recorded. In addition, if the Flex application is using web services to communicate to a backend server, these requests won't be recorded either. Here's how I got my Flex application to record statistics of the navigation within the application and the web service calls made to a backend server.

When you register your website with Google analytics you're given a javascript element to include in your pages. Start by adding this javascript to your html file that loads the Flex application. It should look something like this:

<script type="text/javascript">
    var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
    document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
    var pageTracker = _gat._getTracker("XX");
    pageTracker._trackPageview();
</script>

Notice the last line is a call to a function _trackPageview() with no arguments. This is the code that sends a message to google telling them which page was viewed. The function uses the current location when called with no arguments, but supports a single string argument to allow you to specify a particular URL.

For my solution, I want to call this function passing in a URL that represents either the user navigating to a view, or a web service call. To do this I need to invent an URL structure for my site that reflects the names of the views and the names of the web service calls that are made. More on that later. First I need to change the javascript provided by Google to allow me to make an External call passing in a URL. Notice that I've wrapped the original 2 lines in a function called track(url) which takes a single URL argument and passes it into the call to _trackPageview().

<script type="text/javascript">
   var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
   document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
function track(url) {
    var pageTracker = _gat._getTracker("XX");
    pageTracker._trackPageview(url);
}
</script>

Now in my Flex application, whenever the user clicks on a link that navigates them to another view, I can add the following code to dispatch a new event that represents the user clicking on the "My Account" link. My application is using
Cairngorm events and Commands for the event handling framework, so the new event looks like this:

package uk.co.blackpepper.controller.events
{
    import com.adobe.cairngorm.control.CairngormEvent;

    public class AnalyticsEvent extends CairngormEvent
    {
        public static var EVENT_ID:String = "AnalyticsEvent";
       
        public var url:String;

        public function AnalyticsEvent(theUrl:String)
        {
            super(EVENT_ID);
            this.url = theUrl;
        }
    }
}

And it gets dispatched when a user clicks on a link.

new AnalyticsEvent("/page/My Account").dispatch();

The event is processed by a Command, as shown below, which simply passes the URL onto the javascript function using the
ExternalInterface.

package uk.co.blackpepper.controller.commands
{

    import com.adobe.cairngorm.commands.Command;
    import com.adobe.cairngorm.control.CairngormEvent;
    import uk.co.blackpepper.controller.events.AnalyticsEvent;

    import flash.external.ExternalInterface;

    public class AnalyticsCommand implements Command
    {
        public function execute(evt:CairngormEvent):void
        {
            var event:AnalyticsEvent = evt as AnalyticsEvent;
            var result:Object = ExternalInterface.call("track",event.url);
        }
    }
}

Using this design, events can be dispatched anywhere in the application using different URLs to represent each view.
With the navigation problem solved, the next step was to track the web services calls. I use the AsyncResponder to process
the web service calls, along with a helper super class to initialise the wsdl and setup the web service.

package uk.co.blackpepper.webservices
{
    import uk.co.blackpepper.model.ModelLocator;

    import mx.rpc.AsyncToken;
    import mx.rpc.events.FaultEvent;
    import mx.rpc.events.ResultEvent;
    import mx.rpc.soap.Operation;

    public class LoginWebService extends BaseWebService
    {

        public function LoginWebService()
        {
            super("LoginService");
        }

        public function getUserDetails(resultHandler:Function, faultHandler:Function):void
        {
            var token:AsyncToken = service.getUserDetails(ModelLocator.instance.credentials.userPasswordHash);
            token.addResponder(new TrackingAsyncResponder(resultHandler, faultHandler, token, serviceName + "/getUserDetails"));
        }
    }
}

In order to support the need to associate a URL with the call I created a TrackingAysncResponder that takes a URL as an additional parameter and generates the required Analytics events depending on if the call was successful or not.
All the web service calls are recorded as URLs in the form /service/[serviceName]/[methodName] and optionally, they have /fault on the end if there was an error making the call.

package uk.co.blackpepper.webservices
{
    import com.adobe.cairngorm.control.CairngormEventDispatcher;
    import uk.co.blackpepper.controller.events.AnalyticsEvent;
    import uk.co.blackpepper.model.ModelLocator;

    import mx.rpc.IResponder;
    import mx.rpc.events.FaultEvent;
    import mx.rpc.events.ResultEvent;

    public class TrackingAsyncResponder implements IResponder
    {

        private var _uri:String;
        private var _resultHandler:Function;
        private var _faultHandler:Function;
        private var _token:Object;

        public function TrackingAsyncResponder(result:Function, fault:Function, token:Object=null, uri:String=null)
        {
            _uri = uri;
            _resultHandler = result;
            _faultHandler = fault;
            _token = token;
        }

        public function result(data:Object):void
        {
            new AnalyticsEvent("/services/" + _uri)).dispatch();
            _resultHandler(data);
        }

        public function fault(info:Object):void
        {
            new AnalyticsEvent("/services/" + _uri + "/fault")).dispatch();
            _faultHandler(info);
        }
    }
}

Using this design I can easily record all the links clicked on by users and track all the web service calls made by the application. In addition, because of the way Google Analytics works, I also get lots of additional insight into the users of my application.