A better analytics tracking for your app
Migrating from Google Analytics to Google Tag Manager to future proof analytics tracking logic

  Mar 14, 2015 -   read
  google-analytics, google-tagmanager, android, patterns

Google Analytics is great. I love it! People at my company love it! It tells you so many things about how your audience uses your app. But once your feet are deep into the analytics game, your application will probably end up with a bunch of clunky analytics tracking code as below:

Toggle code

GoogleAnalytics.getInstance(context).send(new HitBuilders.EventBuilder()
        .setCategory("story")
        .setAction("view")
        .setLabel("1")
        .build());
GoogleAnalytics.getInstance(context).send(new HitBuilders.EventBuilder()
        .setCategory("story")
        .setAction("share")
        .setLabel("1")
        .build());

Don’t get me wrong, the Google Analytics API is in no way bad, but the direct application of ‘analytics tracking’ may backfire you in the future, let’s say when you switch to another provider, or when you simply want to change the name of a category/action. This calls for a layer of abstraction to future proof your tracking methods.

Coincidentally (or not!), Google Tag Manager comes into the picture to help the abstraction of analytics tracking in a (not so) intuitive way. The idea behind Google Tag Manager is excellent, although it takes even an experienced engineer a while to logically put all its concepts in the correct place. That may explain why it does not get as popular as Google Analytics.

Google Tag Manager explained

Let’s take an example to explain how things work in Google Tag Manager. Assuming you want to repeat the Google Analytics logic above: send a request to Google Analytics, with category story, action view or share, whenever an object of type story encounters event view, or an object of type story encounters event share. So trivial!

In ‘Google Tag Manager language’, using its 3 main concepts: macro, rule and tag, this can be specified as follows:

macro object_type provide object type, e.g. story
macro event provide event, e.g. view or share
rule any story view object_type=story & event=view [& object_id=*]
rule any story share object_type=story & event=share [& object_id=*]
tag track story view Universal Analytics track, category=story, action=view
when any story view is triggered
tag track story share Universal Analytics track, category=story, action=share
when any story share is triggered

In simple words, a tag connects to your analytics service, whenever rules that compare data sent from your app - retrieved via macros - are matched.

With this setup, an equivalent logic to the original Google Analytics example is as follows:

Toggle code

TagManager.getInstance(context).getDataLayer().pushEvent("view",
        DataLayer.mapOf("object_type", "story", "object_id", "1"));
TagManager.getInstance(context).getDataLayer().pushEvent("share",
        DataLayer.mapOf("object_type", "story", "object_id", "1"));

Tip

If you have multiple Google Analytics trackers, you can also send the tracker ID you want to communicate with as a data variable that can be retrieved via macro

Abstraction, Abtraction, Abstraction!

So now we have made some progress, converting our Google Analytics logic into Google Tag Manager logic, adding tons of complexity. But for what?

At a glance, the two code blocks before and after look almost identical. But looking closer, you can notice that we have managed to put an abstraction layer between our ‘interaction definition’ and Google Analytics ‘event definition’.

We don’t push an event to the tracking service anymore. We push our data model and its interaction to a ‘data sink’, that will decide how this information will be processed and sent to the tracking service. In Google Tag Manager, ‘data model’ is defined in the form of DataLayer.

If we generalize our data model and its interaction definition, treating anything that can be interacted with as a Trackable object, then we can just simply dump any interaction with a Trackable to Google Tag Manager, or an in-house data sink, delaying the decision of what to track for another day, and move forward with the project, without having to worry about fixing the code later to change tracking logic.

Toggle code

Trackable.java
public interface Trackable {
    enum ObjectType { story, comment, ... }
    String getTrackedObjectId();
    ObjectType getTrackedObjectType();
}
Analytics.java
public final class Analytics {
    public enum Event { view, share, ... }

    public static class Builder {
        private final Trackable mTrackable;
        private final List<Event> mEvents = new ArrayList<>();

        public static Builder createInstance(Trackable trackable) {
            return new Builder(trackable);
        }

        private Builder(Trackable trackable) {
            mTrackable = trackable;
        }

        public Builder add(Event event) {
            if (event != null) {
                mEvents.add(event);
            }

            return this;
        }

        public void send(Context context) {
            if (mTrackable == null) {
                return;
            }

            for (Event event : mEvents) {
                TagManager.getInstance(context).getDataLayer().pushEvent(
                        event.name(),
                        DataLayer.mapOf(
                                "object_type", mTrackable.getTrackedObjectType(),
                                "object_id", mTrackable.getTrackObjectId()
                        ));
            }

            // some other in-house data sink dumping
        }
    }
}

Using the above Trackable interface and the fluent-API Analytic.Builder class, one can dump interaction data to Google Tag Manager as follows:

Toggle code

Analytics.Builder.createInstance(new Trackable() {
            @Override
            public String getTrackObjectId() {
                return "1";
            }

            @Override
            public ObjectType getTrackedObjectType() {
                return ObjectType.story;
            }
        })
        .add(Analytics.Event.view)
        .add(Analytics.Event.share)
        .send(context);

Of course, generalizing things and introducing abstraction layers will make things less flexible for some special cases. But in general, I always find that with some smart combinations of Google Tag Manager’s tag and rule, the same can be achieved without losing the generality. And even better: the real tracking logic is now essentially broken free from our app!

Note

It is not recommended to use both Google Analytics and Google Tag Manager at the same time. However, in case you must, I notice that Google Tag Manager will 'take over' all Google Analytics calls once it's initiated anyway.
Ha Duy Trung
Currently cooking, brewing and building Android stuffs.