Using Google Analytics iOS SDK in Shared Library Projects

Recently I added Google Analytics to an iOS project that included several shared libraries that I use for common, reusable code.  This presented an organizational challenge as I needed to send events to Google Analytics from the shared libraries when they were used in some applications, but not in others.  Below I describe how I set up Google Analytics and tracked events and screens from shared library code only when the application supported it.

The first thing we need to do is to make sure that we are signed up for Google Analytics and we have created a profile for our application to send information to.  Head over to the Google Analytics site and login or create an account if you don’t already have one.

Google Analytics Login Screen

Click on the “Admin” selection near the upper right and add an account with the “+ New Account” button if you don’t already have one.  Once you have the account, click on its name to open it and create a new property with the “+ New Property (web or app)” button.

Google Analytics New Account Screen

Choose to create an App property and fill in the app name, industry category and reporting time zone.  When you are finished, press the “Get Tracking ID” button new the bottom of the screen.  The Tracking ID number will be displayed for you.  It looks like “UA-XXXXXXXX-Y” where XXXXXXXX-Y is unique to your application profile.  Save this value, we will need it to initialize Google Analytics in our application.

Google Analytics New Profile Screen

Being able to create a profile specifically for app tracking is a feature that is part of the new 2.0beta release of the Google Analytics SDK.  Even though it has the obligatory Google “beta” tag, it is the version that Google recommends you use and it seems to be solid.  Previous versions of the SDK overlaid your app analytics onto web profiles in Google Analytics so you were still counting things like “page views”, which really isn’t intuitive for apps.  This update makes the terminology fit.

Now we can start setting up Google Analytics in our XCode project.  The Google Analytics iOS SDK Overview is a great starting point and I recommend having it open in another window for easy reference.  If you haven’t already, download the latest version of the SDK.

Unzip the archive and copy all of the .h and the libGoogleAnalytics.a  files from the Library directory into your project.  If you need a version of the library with debugging symbols for troubleshooting, copy libGoogleAnalytics_debug.a instead of libGoogleAnalytics.a.  I usually create a group called GoogleAnalytics right beside my app delegate class.  Make sure that you check the “copy files to destination” option and have the target set to your application rather than any shared libraries that you may include.

Copying Google Analytics SDK to Your XCode Project

Under newer versions of XCode, dragging the .a file into the project should have already added it to your Build Phases entry for the app, but you should check anyway, just to make sure.  Select your app in the project explorer pane and choose your app target.  Click on the Build Phases tab.  You should see the libGoogleAnalytics.a in the “Link Binary With Libraries” section as shown below.  If you don’t, add it with the “+” button down at the bottom.  Now is also the time to add the CoreData and SystemConfiguration frameworks to your project if you don’t already use them.  They are required by the Google Analytics SDK, so just go ahead and add them now if you need to.

Google Analytics Build Phase Setup

Now its time to start adding some code!  Open up your AppDelegate.m file and add an import for GAI.h.

#import "GAI.h"

Then we need to add the code to configure and start Google Analytics to our application:didFinishLaunchingWithOptions: method.  I’ll show the code below and then talk about what each one does.

- (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
 
    // Set up Google Analytics
    // Optional: automatically track uncaught exceptions with Google Analytics.
    [GAI sharedInstance].trackUncaughtExceptions = YES;
    // Optional: set Google Analytics dispatch interval.
    [GAI sharedInstance].dispatchInterval = 30;
    // Optional: set debug to YES for extra debugging information.
    [GAI sharedInstance].debug = NO;
    // Create tracker instance.
    id tracker = [[GAI sharedInstance] trackerWithTrackingId:@"UA-XXXXXXXX-Y"];
    // This removes the last octet of the reporting IP address for privacy.
    [tracker setAnonymize:YES];
 
    // The rest of your method
 
}

OK, let’s step through the setup one line at a time.  First we see a new option in version 2 of the SDK.  The ability to record uncaught exceptions (i.e. crashes) and to report them.  This is extremely nice to have rolled into the Google Analytics package.  While there are several other alternatives like QuincyKit available, if you are using Google Analytics anyway, having it in a single package makes things easier.

Next, you can set the dispatch interval at which the SDK tries to send hits to Google Analytics.  You want to try for a balance between collecting the data you need and performance to your app.  30 seconds is actually the default, adjust accordingly.

You can also configure a debug property to cause the SDK to log information about the events that it is recording and its dispatches to Google Analytics.  This is helpful for troubleshooting.

Finally(!), we are ready to create our tracker instance.  Remember the UA-XXXXXXXX-Y number we got when we created the new profile?  Here is where we use it.

And lastly, once we have the tracker, if we choose, we can set it to anonymize the IP addresses of the devices by removing the last octet of the address.  There is a potential downside to this, however.  By removing the last octet, the geolocation reporting in Google Analytics reports will be less accurate.  The upside is improved privacy for your users.  As an aside, you should always notify your users that your are collecting analytics and allow them the option to opt-out.  There are more details on the Advanced Configuration page but basically you can check and set application level opt-out with the code snippets shown below.

if ([GAI sharedInstance].optOut) {
  ... // Alert the user they have opted out.
}
 
// To set the app level opt-out flag
[[GAI sharedInstance].setOptOut = YES];

Normally at this point, you would then just go through your code and start inserting calls to the default tracker or extend your view controllers for automatic screen tracking by having your view controllers extend GAITrackedViewController rather than UIViewController, but we want to be able to track screen views or events even from our shared libraries, and we may not want to do this in all applications that we use the libraries in.

To solve this, I decided to dispatch the views and events that I wanted to send to Google Analytics as notifications.  This way, I could place the code in the shared libraries, and if the application didn’t support Google Analytics, the notification would do nothing. To do this, I registered to handle notifications for the types of Google Analytic data that I wanted to track.  At this point, I track screen views and events, where events are actions that are usually initiated by a UI control such as a button, or a gesture from the user.  Normally, I don’t advocate adding a lot of extra code to the application delegate, but in this case, it works out well to do so, since it isn’t a great deal of code. Note that we also need a dealloc in order to remove ourselves as an observer.

- (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
 
    // Set up Google Analytics
    // Optional: automatically track uncaught exceptions with Google Analytics.
    [GAI sharedInstance].trackUncaughtExceptions = YES;
    // Optional: set Google Analytics dispatch interval.
    [GAI sharedInstance].dispatchInterval = 30;
    // Optional: set debug to YES for extra debugging information.
    [GAI sharedInstance].debug = NO;
    // Create tracker instance.
    id tracker = [[GAI sharedInstance] trackerWithTrackingId:@"UA-XXXXXXXX-Y"];
    // This removes the last octet of the reporting IP address for privacy.
    [tracker setAnonymize:YES];
 
    // The rest of your method
 
    // Register for the notifications I want to receive
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleGAScreenViewNotification:) name:@"gaScreenViewedNotification" object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleGAEventNotification:) name:@"gaEventGeneratedNotification" object:nil];
 
}
 
- (void)dealloc {
    // Even in the ARC world we sometimes need dealloc.
    // Stop observing all notifications we've signed up for.
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

Next, we need to fill in the handler methods for our notifications.  We’re able to pass an NSDictionary in the notification’s userInfo member, so that is where we will store the information we want to pass along to Google Analytics. There could be additional error checking added, but I left it out for clarity in the example.

- (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
 
    // Set up Google Analytics
    // Optional: automatically track uncaught exceptions with Google Analytics.
    [GAI sharedInstance].trackUncaughtExceptions = YES;
    // Optional: set Google Analytics dispatch interval.
    [GAI sharedInstance].dispatchInterval = 30;
    // Optional: set debug to YES for extra debugging information.
    [GAI sharedInstance].debug = NO;
    // Create tracker instance.
    id tracker = [[GAI sharedInstance] trackerWithTrackingId:@"UA-XXXXXXXX-Y"];
    // This removes the last octet of the reporting IP address for privacy.
    [tracker setAnonymize:YES];
 
    // The rest of your method
 
    // Register for the notifications I want to receive
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleGAScreenViewNotification:) name:@"gaScreenViewedNotification" object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleGAEventNotification:) name:@"gaEventGeneratedNotification" object:nil];
 
}
 
- (void)dealloc {
    // Even in the ARC world we sometimes need dealloc.
    // Stop observing all notifications we've signed up for.
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
 
- (void) handleGAScreenViewNotification:(NSNotification *)notification {
    NSDictionary *nInfo = notification.userInfo;
 
    if (nInfo) {
        // Get the default tracker instance
        id tracker = [[GAI sharedInstance] defaultTracker];
        // Screen views are only tracked by their name
        [tracker trackView:(NSString *)[nInfo objectForKey:@"screenName"]];
    } else {
        NSLog(@"handleGAScreenViewNotification: No userInfo dictionary specified.");
    }
}
 
- (void) handleGAEventNotification:(NSNotification *)notification {
    NSDictionary *nInfo = notification.userInfo;
 
    if (nInfo) {
        id tracker = [[GAI sharedInstance] defaultTracker];
        [tracker trackEventWithCategory:(NSString *)[nInfo objectForKey:@"eventCategory"]
                             withAction:(NSString *)[nInfo objectForKey:@"eventAction"]
                              withLabel:(NSString *)[nInfo objectForKey:@"eventLabel"]
                              withValue:(NSNumber *)[nInfo objectForKey:@"eventValue"];
    } else {
        NSLog(@"handleGAEventNotification: No userInfo dictionary specified.");
    }
 
}

At this point, we can now start dispatching notifications from our code, even the shared libraries, and have them picked up and sent to Google Analytics.  To help with this, I have a category on NSNotificationCenter that I use.

//
//  NSNotificationCenter+MainThread.h
//
 
#import 
 
@interface NSNotificationCenter (MainThread)
 
- (void)postNotificationOnMainThread:(NSNotification *)notification;
- (void)postNotificationOnMainThreadName:(NSString *)aName object:(id)anObject;
- (void)postNotificationOnMainThreadName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;
 
@end
 
//
//  NSNotificationCenter+MainThread.m
//
 
#import "NSNotificationCenter+MainThread.h"
 
@implementation NSNotificationCenter (MainThread)
 
- (void)postNotificationOnMainThread:(NSNotification *)notification
{
	[self performSelectorOnMainThread:@selector(postNotification:) withObject:notification waitUntilDone:YES];
}
 
- (void)postNotificationOnMainThreadName:(NSString *)aName object:(id)anObject
{
	NSNotification *notification = [NSNotification notificationWithName:aName object:anObject];
	[self postNotificationOnMainThread:notification];
}
 
- (void)postNotificationOnMainThreadName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo
{
	NSNotification *notification = [NSNotification notificationWithName:aName object:anObject userInfo:aUserInfo];
	[self postNotificationOnMainThread:notification];
}
 
@end

Now if I want to track a screen view, I can open up one of my UIViewControllers and add the following to viewWillAppear:

- (void) viewWillAppear:(BOOL)animated {
 
    [[NSNotificationCenter defaultCenter]
        postNotificationOnMainThreadName:@"gaScreenViewedNotification"
        object:self
        userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"About", @"screenName", nil]];
 
        // The rest of your viewWillAppear, if necessary
 
	// Call super last
	[super viewWillAppear:animated];
 
}

Tracking an event is handled similarly. Let’s generate an event for searching that includes the search terms and the number of results that we got back.

- (void)searchBarSearchButtonClicked:(UISearchBar *)aSearchBar {
    NSString *searchString = aSearchBar.text;
    // Insert your search here
    NSArray *searchResults = [[[ContentLookup sharedInstance] searchProducts:searchString];
 
    // Let interested parties know about the search.  This is mostly for analytics but
    // could be used for other purposes.
    NSNumber *searchHits = [NSNumber numberWithInt:(searchResults) ? [searchResults count] : 0];
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setValue:@"Search" forKey:@"eventCategory"];
    [dict setValue:@"ProductSearchExecuted" forKey:@"eventAction"];
    [dict setValue:searchString forKey:@"eventLabel"];
    [dict setValue:searchHits forKey:@"eventValue"];
    [[NSNotificationCenter defaultCenter]
      postNotificationOnMainThreadName:@"gaEventGeneratedNotification"
      object:self
      userInfo:[NSDictionary dictionaryWithDictionary:dict]];
 
      // Display search results, etc.
}

You can follow the same pattern to implement other Google Analytic types of calls including, ECommerce, Session Tracking, Social Interactions and User Timings.  This is a simple pattern but it has proven very helpful for me.

So to review, we’ve covered the basics of setting up and starting to use Google Analytics with your app and a way to make tracking calls from other shared library components you may use.  You can also leave the calls in your common shared library code even when you use them in an app that doesn’t support Google Analytics.  Having insight into how clients are using your apps can be extremely beneficial, hopefully this article will help you get started.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*