36
Seeking the truth from MOBILE ANALYTICS Mouhcine El Amine @moxy85

Seeking the truth from mobile analytics

Embed Size (px)

Citation preview

Seeking the truth from

MOBILE ANALYTICSMouhcine El Amine

@moxy85

PAGE VIEW COUNTING<script> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-12345678-9', 'auto'); ga('send', 'pageview');</script>

PAGE VIEW COUNTING MODEL ISOBSOLETE

EVENT BASED ANALYTICS

TOOLBOXNSDictionary *parameters = @{@"Source": @"List"};[analyticsProvider logEvent:@"Premium feature button tapped" parameters:parameters];

Experiment & MEASURE

The purpose of an experiment is to find the truth.

Not to prove that you are right.— Joel Marsh (UX Architect)

WELL DESIGNED ANALYTICS1. Outline business and UX goals

2. Decide the questions you want to answer3. Map the events to answer your questions

4. Build user paths and conversion funnels from collected data

EXAMPLE EXPERIMENT

1. Outline business and UX goalsIncrease premium features selling

2. Decide the questions you want to answerIs the "Gallery" label an efficient selling driver ?

3.Map the events to answer your questions

Gallery button tap (List|Grid|Large)Gallery premium feature invite refusal

Gallery premium feature invite acceptancePremium feature button tap (List|Detail)

Premium feature buying intent (x€)Funnel abort (No Ads|User not logged)

4.Build user paths and conversion funnels from collected data

EXPERIMENTS cannot FAIL!

The only way an experiment can fail is if the result teaches you nothing.

— Joel Marsh (UX Architect)

BUILDING A ROBUSTTRACKING SOLUTION

SETUP

Objective CKiwi + KIF

Private CocoapodsFlurry

@interface SBTAnalyticsEvent : NSObject

@property (nonatomic, copy, readonly) NSString *name;

@property (nonatomic, copy, readonly) NSDictionary *parameters;

- (instancetype)initWithName:(NSString *)name parameters:(NSDictionary *)parameters;

//...@end

NSDictionary *parameters = @{SBTEventParameterLayoutKey: SBTEventParameterLayoutGrid};SBTAnalyticsEvent *event = [[SBTAnalyticsEvent alloc] initWithName:SBTEventGalleryButtonTap parameters:parameters];

@interface SBTAnalyticsTracker : NSObject

//...

- (void)trackEvent:(SBTAnalyticsEvent *)event;

//...

@end

@implementation SBTAnalyticsTracker

//...

- (void)trackEvent:(SBTAnalyticsEvent *)event{ NSCParameterAssert(event);#ifndef DEBUG [Flurry logEvent:event.name withParameters:event.parameters];#endif}

//...

@end

FASTFEEDBACK

TAXILOG()Xcode colors plugin

C Macro#define XCODE_COLORS_ESCAPE @"\033["

// Clear any background and foreground color#define XCODE_COLORS_RESET XCODE_COLORS_ESCAPE @";" #define TAXI_COLOR_ESCAPE \ XCODE_COLORS_ESCAPE @"fg0,0,0;"\ XCODE_COLORS_ESCAPE @"bg255,204,0;"#ifdef DEBUG #define TAXILog(fmt, ...)\ NSLog((TAXI_COLOR_ESCAPE fmt XCODE_COLORS_RESET), ##__VA_ARGS__);#else #define TAXILog(...)#endif

BONJOURLOG()NSLogger

NSLogger Mac clientC Macro

#ifdef DEBUG #define LOGGER_DEFAULT_OPTIONS(kLoggerOption_BufferLogsUntilConnection | \ kLoggerOption_BrowseBonjour | \ kLoggerOption_BrowseOnlyLocalDomain | \ kLoggerOption_UseSSL) #define BONJOURLog(fmt, ...) \ LogMessageTo(LoggerGetDefaultLogger(), @"Analytics", 0, fmt, ##__VA_ARGS__);#else #define BONJOURLog(...)#endif

- (void)trackEvent:(SBTAnalyticsEvent *)event{ NSCParameterAssert(event);#ifndef DEBUG [Flurry logEvent:event.name withParameters:event.parameters];#endif#ifdef DEBUG NSString *log = [self logStringForTrackedEvent:event]; TAXILog(@"%@", log); BONJOURLog(@"%@", log);#endif}

AVOIDING REGRESSION WITHAUTOMATED TESTING

INTEGRATION TESTING WITH KIF[tester tapViewWithAccessibilityLabel:@"Gallery"];[tester waitForViewWithAccessibilityLabel:@"This ad is featured"];[tester tapViewWithAccessibilityLabel:@"No, continue"];

TRACKING EXTENSION FOR KIFNSDictionary *parameters = @{@"Layout": @"Grid"};SBTAnalyticsEvent *event = [[SBTAnalyticsEvent alloc] initWithName:@"Gallery button tap" parameters:@{@"Layout": @"Grid"}];[tester tapViewWithAccessibilityLabel:@"Gallery"];[tester checkTrackedAnalyticsEvent:event];

TRACKING EXTENSION FOR KIF1. Proxy events to a stack

2. Intercept events before test suite begins3. Answer if an event is in the stack with YES or NO

4. Transform the boolean answer in a test result

1. Proxy events to a stack

@protocol SBTAnalyticsTrackerDelegate <NSObject>

- (void)analyticsTracker:(SBTAnalyticsTracker *)tracker didTrackEvent:(SBTAnalyticsEvent *)event;

@end

@interface SBTAnalyticsTracker : NSObject

@property (nonatomic, weak) id<SBTAnalyticsTrackerDelegate> delegate;

- (void)trackEvent:(SBTAnalyticsEvent *)event;

//...

@end

- (void)trackEvent:(SBTAnalyticsEvent *)event{ NSCParameterAssert(event);#ifndef DEBUG [Flurry logEvent:event.name withParameters:event.parameters];#endif [self.delegate analyticsTracker:self didTrackEvent:event];#ifdef DEBUG NSString *log = [self logStringForTrackedEvent:event]; TAXILog(@"%@", log); BONJOURLog(@"%@", log);#endif}

2.Intercept events before test suite begins

CONFIG_START

static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{ SBTAnalyticsEventsStack *stack = [SBTAnalyticsEventsStack sharedAnalyticsEventsStack]; [[SBTAnalyticsTracker sharedTracker] setDelegate:stack];});

CONFIG_END

3.Answer if an event is in the stack with YES or NO

- (BOOL)popAnalyticsEvent:(SBTAnalyticsEvent *)event{ __block SBTAnalyticsEvent *expectedEvent; [self.events enumerateObjectsWithOptions:NSEnumerationReverse usingBlock: ^(SBTAnalyticsEvent *stackEvent, NSUInteger idx, BOOL *stop) { if ([stackEvent isEqualToEvent:event]) { expectedEvent = stackEvent; *stop = YES; } }]; if (!expectedEvent) { return NO; } [self.events removeObject:expectedEvent]; return YES;}

4.Transform the boolean answer in a test result@implementation KIFUITestActor (SBTAnalyticsTracking)

- (void)checkTrackedAnalyticsEvent:(SBTAnalyticsEvent *)event{ [self runBlock:^KIFTestStepResult(NSError *__autoreleasing *error) { SBTAnalyticsEventsStack *stack = [SBTAnalyticsEventsStack sharedAnalyticsEventsStack]; BOOL eventWasTracked = [stack popAnalyticsEvent:event]; if (eventWasTracked) { return KIFTestStepResultSuccess; } *error = [NSError KIFErrorWithFormat:@"Event not tracked %@", event]; return KIFTestStepResultFailure; } timeout:1.0];}

/...

@end

Q&A

THANKSMouhcine El Amine - @[email protected]