Abstracting Analytics: RxJava Observers in TV Apps

Sometimes analytics are nice to have. Sometimes they're critical, like in our Fire TV apps we just published on Amazon.

Content providers and distributors are always negotiating who can show what content (and for how much). However, I imagine it's much harder to turn down the content that we, the viewers, find most valuable. Analytics are one way to find out what shows we're really watching; without analytics, it's possible that some of our favorites, like Firefly, could be cancelled after only one season!

Analytics is a broad term for many forms of data collection. In our TV apps, for instance, we might use analytics for:

  • Bug/Crash Logging
  • Screen View Tracking
  • Audience Measurement
  • Quality of Experience Measurement
  • Advertising

Here's how we approach such a broad set of requirements.

Our Goals

Abstractly speaking, we'll consider two goals:

  1. We want to handle screen views and events asynchronously with multiple subscribers.
  2. We want a simple, central location to which we can publish our screen views and events.

In Java, we define a simple interface to meet our two goals.

An RxJava Observable

ReactiveX, or Rx for short, is a library built specifically for composing and consuming asynchronous data. Rx includes an Observable, which emits a sequence of items. We can create Observers which subscribe to those sequences. In our case, we want an Observable which emits screen views as they occur and Observers which react to those screen views.

For our implementation, we like the RxJava PublishSubject, which makes it easy to emit screen views and subscribe to them via a single object.

addObserver lets us create a new subscription to our PublishSubject. sendScreenView lets us emit a new value to our PublishSubject.

While it's convenient to have onNext and subscribe available in the same object, we also want to be diligent about the number of subscribers we have running in our app, so we're careful to instantiate our AnalyticsService as a singleton. Be sure to check your particular ReactiveX implementation or conventions when choosing your Rx objects.

Our First Observer

Perhaps we'll start with a really simple screen view handler: printing the screen name.

Our IAnalyticsService interface expects Observer<String> objects, so we'll implement one in a LoggingAnalyticsObserver.

Although we might have onSubscribe, onError, and onComplete implementations in our final product, for the sake of this post we'll just add a println into onNext and leave empty implementations for the rest.

The Google Analytics Observer

Perhaps the println was a little too simple, so we should hook up something more meaningful, like Google Analytics.

It's actually not much more difficult, however, because a Fire TV app is really just an Android app, and Google has a package for Android. To get started, we added that package to our build.gradle file:

compile 'com.google.android.gms:play-services-analytics:10.2.1'

Here's a GoogleAnalyticsObserver implementation of Observer<String>.

It's not too different from the LoggingAnalyticsObserver, except:

  • We don't really want to block the rest of our app when we're sending our screen views, so we use an RxJava Completable to defer all of that HTTP traffic.
  • We pass a reference to our Google Analytics Tracker in the constructor for our Observer instance.

Hooking It All Up

Now that we have two different Observers for our screen views, we just need to instantiate them and pass them to our AnalyticsService implementation. Considering we'll have a few more of these Observers, we'll need to refactor later on, but for now we'll just add calls to addObserver at the end of our app's onCreate.

Because we send a lot of analytics events throughout our app, we also created the sendScreenView static method. Any time we need to send a screen view, it's a simple call to FireTvApp.sendScreenView(currentShowName); from the views themselves or (in our case) the navigation between them.

Is this working?

Taking a look at our logcat output, we find the output from our LoggingAnalyticsObserver:

I/System.out( 6555): Analytics Screen View: Firefly

With Google Analytics debug logging enabled (adb shell setprop log.tag.GAv4 DEBUG) we also see our Google Analytics screen view:

D/GAv4    ( 6555): Hit delivery requested: aid=com.infernored.firetv, an=IRT TV, cd=Firefly, t=screenview, …

Or if you're not in a command-line mood, you could always check your real-time analytics.

Just like that, we're set up with screen view tracking for logging and Google Analytics, and it's on to all the other Observers!