Organize your analytics code

An obvious but helpful pro-tip that may save you lots of pain

Paul Danyliuk
7 min readNov 23, 2016

Being a developer, I read a lot of docs. Docs are my favorite: whether I’m learning a new technology or just need a refresher, looking through guides and examples is the fastest way to get that knowledge. But as much as docs are great for quick reference, they all have a common problem: the examples are usually focused on how to make the API calls — but much less on how to do it nicely. This is often harmful to less experienced devs who tend to pick up those examples as the only way of doing things, and stick to that despite all the pain it brings.

A perfect example of this is analytics. For instance, here’s how they teach us to track events with Google Analytics:

So far so good, eh? The example does a splendid job illustrating how to send an event hit with different parameters. Now, let’s think of a real app where you’d want to track dozens of different events — what then? For a newcomer, the first impulse may be to just copy this snippet over — after all, that’s what they’re doing in the Udacity course. But alas, this is not a short piece of code. Sprinkling these bad boys all over your Activities will already feel like a good deal of bloat. And if it’s not bad enough, just consider that 2 out of 3 apps will use more than one analytics tool — so when you decide to give Answers a try and add snippets like this:

or check out those shiny new Firebase Analytics features and also add this:

all that’s good in your project will soon drown in the mess of event tracking code.

The problem here is clear. Not only will these bulky calls clutter your methods and obscure the relevant bits, but trying out or switching to other analytics solutions will also be a coder’s nightmare. Luckily, this is easy to solve with well-organized code. Now, this will sound super obvious, but all you have to do is —

Move analytics code to a separate class

You see, that’s the idea behind good object-oriented software design. You hide away all routine and bulky stuff, and expose a clear and simple way to use it. The techniques for doing this are commonly called software design patterns. Some of those — Adapter, Builder, Singleton — must already sound familiar to you. But there are other, and it’s always a good thing to know more.

The pattern we’ll be applying here is called Facade.

Facade defines a higher-level interface that makes the subsystem easier to use. This can be used to simplify a number of complicated object interactions into a single interface.
— a quite technical definition from
here.

In simpler words, a facade is just an object that wraps the calls to the relatively complex underlying system (in our case a bunch of analytics APIs) under a set of simple, callable-in-one-line methods. It is similar to an adapter, only with a different purpose and no strict requirements for its interface. Using a facade instead of directly depending on those APIs will not only make your UI layer code shorter and cleaner, but also allow you to alter the implementation all in one place, make useful shorthand methods, and add some helpful extra logic.

Here are my steps / recommendations for implementing a well-organized analytics facade. In my case it’s an Android app using Google Analytics with a single tracker, but you can easily adapt this approach to your specific tool set.

1. Create Analytics class and make it a singleton

Okay, we’ve established that the main goal of doing all this is to make it all simpler to use. The first logical step would be making it simple to retrieve.

If you take a look at the popular analytics solutions, you’ll notice that most of them come with a singleton object you can easily obtain from any context:

Even if you’re using GA by the book and retrieve the Tracker instance from your custom Application object, that tracker is also a singleton since there’s only one Application per process. You just access it differently.

Given all this, it also makes sense to implement the analytics facade as a singleton. You can do it the usual way with a lazy-loaded static field:

This example uses double-checked locking, but you can drop it if you’re going to call it from the main thread only.

Or you can ditch the evil static and implement this logic in your Application class. It’s a bit more work, but if you need to configure analytics differently in different build flavors (e.g. having separate Application objects for dev and prod) or just want to easily mock it for testing, you should prefer this method:

If all your other Application classes are going to inherit from MyApplication, you can drop the interface too.

Now you’ll be able to retrieve your analytics facade from any Context class (e.g. Activity or Service) by simply calling Analytics.from(this). If your app also uses Fragments, you can add this for convenience:

And if you’re only using Answers or Flurry, you may not need Context at all.

2. Implement event tracking methods

The next step is to get rid of those clumsy event tracking calls. While Builders and chained methods are awesome, these ones simply take up far more space than they deserve. So let’s hide them behind something more compact:

What previously was six lines of code will now look like this:

And now you can try other platforms without having to make changes all over your code — just add the logic to your sendEvent methods and you’re done!

Of course, you can go even further and create separate methods for separate categories: sendViewEvent(), sendSocialEvent() etc. This will be especially useful if you’re going to use Firebase Analytics or Answers as your baseline solution, where different event categories require different hard-coded logic (different event classes, predefined constants, or so on).

3. Implement proper screen tracking

The next on the list is screen tracking. Now, many analytics solutions come with automatic screen tracking that usually works by detecting the currently displayed Activity. However, if your UI layer is more complex than that — e.g. if you use Fragments (or some esoteric View-based navigation instead), or if your app has a ViewPager (tabs), or you simply want to be in charge, auto screen tracking will fall short and you’ll have to resort to doing it manually.

Again, there are two approaches.

The first one is to send screen hits whenever the user does something (e.g. presses the button) that takes them to another screen. You may have seen this in random tutorials, possibly even in the early revisions of the corresponding Udacity course (that’s how I remember it). If you haven’t guessed it already, this approach is not reliable. Because of how Android works, you cannot just assume that a particular Activity is reachable in your defined way only. The system can kill and restore your app, wiping away all its global state. Or a user can leave it and return through another entry point. Both scenarios will mess up reporting, assigning wrong or empty screens to captured events.

A more bulletproof one is the second approach where you send screen hits in the onStart() methods of respective Activities and Fragments. Now this one will report screen names correctly, but can you spot the problem? Each time the screen is rotated, onStart() will be called again and again, spamming your analytics with fake screen hits.

Luckily this can be fixed with a bit of extra logic in your facade. All you have to do is remember the last reported screen and suppress repetitive hits:

You can use the same trick with other events that you want to only track once.

Sometimes there’s a need to track the events happening when the app is in the background — for example, in one of my apps I’m tracking events coming from a notification. To properly report that such events originate from outside the visible Activity, you have to set the screen name in the tracker to something different when the app is minimized. Well, except one catch: there’s no way to detect that. Instead, what you’ll have to do is assume that the app is going background each time the current Activity is gone, and set the screen name back when the new Activity comes on stage.

The facade method for going background will look like this:

Calling this won’t report the hit when the app is minimized or restored, but all events tracked between notifyGoneBackground() and sendScreenView() will now correctly appear in your dashboard as coming from “In background”. Note how you don’t touch mLastScreenName here — you still want to suppress screen hits on orientation change, remember?

Heads up! Symmetrically to calling sendScreenView() in onStart(), you may want to call this method in onStop() . Well, this won’t work. onStart() fires as soon as the next Activity is displayed , but onStop() is called only when the previous Activity is no longer visible — which comes after onStart() because of an activity transition. Placing those in onResume() and onPause() instead will guarantee proper execution order:

Since you cannot predict at which Activity the user will minimize the app, you must call notifyGoneBackground() in all activities. To avoid overriding dozens of onPause() methods, you can register a global Activity lifecycle callback listener in your Application class (API 14+) and put it there. Nifty!

4. Add whatever’s helpful

Congratulations! This pretty much concludes the essentials. Now it’s up to you to decide what else to put into the analytics facade for your convenience.

For example, you can add event tracking methods that accept models instead of generic value strings. Or a license check callback that populates custom dimensions and reports failures. Or some custom logging, if the log from your analytics solution is gibberish. Or put all the names of your events and screens there as constants. The Analytics facade is a perfect place for all of that. Take a look how I did it in my Material Cue app. (And check out the app itself too, if you like! :)

Thank you for reading! If you liked the article and want more all-you-knew-was-a-lie tips and tricks from me, don’t hesitate to share and follow! This was my first technical write-up — there’s more where that came from. Also if you have any questions or feedback, feel free to leave it in the comments below.

And now go out there and #BuildBetterApps!

--

--