Android Impression Tracking

In this tutorial, I will be teaching you about android impression tracking in a recycler view. Tracking impressions allows you to create data that can be used to analyze a user’s behavior e.g., screenviews, clicks, and impressions.

Pre-requisites:

  • Understanding of recycler view
  • Understanding of ViewHolder pattern
  • Basic understanding of handlers

Overview:

  • Visibility Tracker
  • RecyclerView
  • Visibility Tracker (Revisited)
  • Registering A Listener
  • Demo!

Here is the finished version of the project if you’d like to download the code. There are tags that mirror the tutorial. https://github.com/ColeMurray/Android-Impression-Tracking-Tutorial

Visibility Tracker

The visibility tracker is responsible for calculating all visible and invisible views.

Let’s break it down a bit more.
Create a class VisibilityTracker.java:

In this snippet, we’ve

  • created a map that will hold tracked views,
  • created a listener interface
  • set a PreDraw listener to our activity’s rootview

Now create a member variable for our listener.

public class VisibilityTracker {
private VisibilityTrackerListener mVisibilityTrackerListener;

We’ll create a method, addView, in our VisibilityTracker class that will create/update the tracked views and schedule a visibility check if it has no tracking info. We’ve also added methods for our listener.

public void addView(@NonNull View view, int minVisiblePercentageViewed) {
TrackingInfo trackingInfo = mTrackedViews.get(view);
if (trackingInfo == null) {
// view is not yet being tracked
trackingInfo = new TrackingInfo();
mTrackedViews.put(view, trackingInfo);
scheduleVisibilityCheck();
}
trackingInfo.mRootView = view;
trackingInfo.mMinVisiblePercent = minVisiblePercentageViewed;
}
public void setVisibilityTrackerListener(VisibilityTrackerListener listener) {
mVisibilityTrackerListener = listener;
}
public void removeVisibilityTrackerListener() {
mVisibilityTrackerListener = null;
}

We’ll come back to how to calculate the visibility later on in the tutorial.

Complete code up to this point can be found here: Milestone 0


RecyclerView

Our next few code snippets will focus on setting up our RecyclerView. We’ll soon have a glimpse of success!

Getting Started

Add the recyclerView library into your build.gradle file.

compile 'com.android.support:recyclerview-v7:23.1.1'

Layout XMLs

First let’s create our recycler view layout in fragment_main.xml:

Now create our item view for our recyclerView product_item_layout.xml:

ProductViewHolder

This viewholder will be used in our recyclerView. It will hold the view of product_item.layout.xml.

In our main project package, create ProductViewHolder.java.

public class ProductViewHolder extends RecyclerView.ViewHolder {
public final TextView mTitleTextView;
public ProductViewHolder(View itemView) {
super(itemView);
mTitleTextView = (TextView) itemView.findViewById(R.id.title_textview);
}
}

ImpressionAdapter

Lastly, Create ImpressionAdapter.java:

In this class, we take in an activity (we’ll use this later), a list of data and inflate our ProductViewHolder for each item in the list.

In our onBindViewHolder method, we take a title from our dataset according to the position in the recycler view and set the title to textview in the view holder. I added in the alternate background to make the view distinction more prominent; it is not necessary.

MainActivityFragment

We need to find our recycler view in the view hierarchy, and set our adapter to it. In MainActivityFragment.java:

Build and run the project and you should see this:

At this point, we’ve got a basic recycler view setup that’s binding our data to views, Sweet. Let’s start wiring up our tracking.

Tracking

Still in our ImpressionAdapter class, add a member variable for our visibilityTracker and viewPositionMap. We’ll also need to take in our activity as a parameter in the constructor to initialize our tracker.

private List<String> mDataSet;
private final VisibilityTracker mVisibilityTracker;
private final WeakHashMap<View, Integer> mViewPositionMap = new WeakHashMap<>();
public ImpressionAdapter(Activity activity, List<String> dataSet) {
mDataSet = dataSet;
mVisibilityTracker = new VisibilityTracker(activity);
}

In our onBindViewHolder we’ll add two lines. Our first line is creating a mapping of our view to it’s position in the recyclerView.We’ll use this later on in the tutorial. Our second line adds the view to mVisibilityTracker’s tracked views.

@Override
public void onBindViewHolder(ProductViewHolder productViewHolder, int position) {
String title = mDataSet.get(position);
//alternate view background color
productViewHolder.itemView.setBackgroundResource(position % 2 == 0 ? android.R.color.white : android.R.color.darker_gray);
productViewHolder.mTitleTextView.setText(title);
mViewPositionMap.put(productViewHolder.itemView, position);
mVisibilityTracker.addView(productViewHolder.itemView, 0);
}
That’s it for now in these files. Let’s jump back to our VisibilityTracker. Complete code to this point can be found here: Milestone 1

VisibilityTracker (Revisited)

Now that we have the basic foundation, we’re moving back to our tracker. Here we’re creating the class that actually calculates if the view is ‘impressed’.

Create an inner class VisibilityChecker within VisibilityTracker.java:

As this is our most important file, let’s break it down a bit.

if (!view.getGlobalVisibleRect(mClipRect)) {
return false;
}
final long visibleArea = (long) mClipRect.height() * mClipRect.width();
final long totalViewArea = (long) view.getHeight() * view.getWidth();
return totalViewArea > 0 && 100 * visibleArea >= minPercentageViewed * totalViewArea;

Our ‘if’ statement is the key to the rest of this method. view.getGlobalVisibleRect doesn’t return a Rectangle like we’d expect. It returns true if any part of our view is visible within its parent. Additionally, it places the visible dx and dy coordinates into mClipRect. Confusing, to say the least.

In our last line, minPercentageViewed * totalViewArea represents the area required for a view to be an qualified as impression.

Two valid impressions at 100% minPercentageViewed. Our titleView occupies all but 5px*1px of our screen.

Here’s an example of two valid impressions if our minPercentageViewed was 100%.

In both examples, our visible area is 5px*9px, and we require 100% of the view to be visible for impression. With this, we can see the two max positions for this view to be registered as an impression.

Cool, now we have a way to calculate if a view is visible, time to start scheduling some checks! Since we don’t need/want to be continuously checking our views, we’ll use a handler and runnable to throttle the checks.

VisibilityRunnable

First, create a boolean member field and VisibilityChecker member field:

private boolean mIsVisibilityCheckScheduled;
private VisibilityChecker mVisibilityChecker;

Create an inner class VisibilityRunnable within VisibilityTracker:

Here our runnable iterates over our list of tracked views, determines the status of each view and sends the results to our listener.

Our last step is to implement scheduleVisibilityCheck: Here we add a few member variables and instantiate them within our constructor.

And we’re done! Almost…… Let’s see it in action.

Registering a Listener

We’ve built:

  • a recycler view
  • an impression adapter that adds views to a visibility tracker in our onBindViewHolder method,
  • a visibility tracker that can determine if a view is visible and will notify a listener of any visible / invisible views.

Our final step is to register a listener to be notified when our views visibility changes. In this example, we’ll print the titles of Any (we set the value as zero earlier) visible view. Jump back into our Impression Adapter:

In this snippet we’ve created a listener that will be notified and call handleVisibleViews. In handleVisibleViews, we access our view position map we placed our views in earlier. We access the data for that view from our dataset and print the title to log.

Demo:

Final Words:

I hope you enjoyed the adventure through learning android impression tracking on a RecyclerView.

In the next tutorial, I’ll focus on how to visualize our new impression tracker with Mixpanel.


If you liked this tutorial, share and recommend to others. Problems or suggestions, reach out on Twitter : ColeMurray or in the comments below