diff --git a/Amplitude/AMPDatabaseHelper.h b/Amplitude/AMPDatabaseHelper.h index d140b4eb..f7bec033 100644 --- a/Amplitude/AMPDatabaseHelper.h +++ b/Amplitude/AMPDatabaseHelper.h @@ -10,8 +10,8 @@ @property (nonatomic, strong, readonly) NSString *databasePath; -+ (AMPDatabaseHelper*)getDatabaseHelper; -+ (AMPDatabaseHelper*)getDatabaseHelper:(NSString*) instanceName; ++ (AMPDatabaseHelper*)getDatabaseHelper:(NSString*) instanceName apiKey:(NSString*) apiKey; ++ (AMPDatabaseHelper*)getTestDatabaseHelper:(NSString*) instanceName; // for testing only - (BOOL)createTables; - (BOOL)dropTables; - (BOOL)upgrade:(int) oldVersion newVersion:(int) newVersion; diff --git a/Amplitude/AMPDatabaseHelper.m b/Amplitude/AMPDatabaseHelper.m index c788ff79..66bb74ff 100644 --- a/Amplitude/AMPDatabaseHelper.m +++ b/Amplitude/AMPDatabaseHelper.m @@ -31,7 +31,6 @@ @interface AMPDatabaseHelper() @implementation AMPDatabaseHelper { - BOOL _databaseCreated; sqlite3 *_database; dispatch_queue_t _queue; } @@ -70,12 +69,7 @@ @implementation AMPDatabaseHelper static NSString *const GET_VALUE = @"SELECT %@, %@ FROM %@ WHERE %@ = ?;"; -+ (AMPDatabaseHelper*)getDatabaseHelper -{ - return [AMPDatabaseHelper getDatabaseHelper:nil]; -} - -+ (AMPDatabaseHelper*)getDatabaseHelper:(NSString*) instanceName ++ (AMPDatabaseHelper*)getDatabaseHelper:(NSString*) instanceName apiKey:(NSString*) apiKey { static NSMutableDictionary *_instances = nil; static dispatch_once_t onceToken; @@ -92,7 +86,7 @@ + (AMPDatabaseHelper*)getDatabaseHelper:(NSString*) instanceName @synchronized(_instances) { dbHelper = [_instances objectForKey:instanceName]; if (dbHelper == nil) { - dbHelper = [[AMPDatabaseHelper alloc] initWithInstanceName:instanceName]; + dbHelper = [[AMPDatabaseHelper alloc] initWithInstanceName:instanceName apiKey:apiKey]; [_instances setObject:dbHelper forKey:instanceName]; SAFE_ARC_RELEASE(dbHelper); } @@ -100,24 +94,31 @@ + (AMPDatabaseHelper*)getDatabaseHelper:(NSString*) instanceName return dbHelper; } -- (id)init +// for testing only, returns an instance with legacy filename ++ (AMPDatabaseHelper*)getTestDatabaseHelper:(NSString*) instanceName { - return [self initWithInstanceName:nil]; + AMPDatabaseHelper *dbHelper = [[AMPDatabaseHelper alloc] initWithInstanceName:instanceName apiKey:nil]; + return SAFE_ARC_AUTORELEASE(dbHelper); } -- (id)initWithInstanceName:(NSString*) instanceName +// instanceName should not be null, getDatabaseHelper will guard +// apiKey should only be null for testing - Amplitude client will guard +- (id)initWithInstanceName:(NSString*) instanceName apiKey:(NSString*) apiKey { - if ([AMPUtils isEmptyString:instanceName]) { - instanceName = kAMPDefaultInstance; - } - instanceName = [instanceName lowercaseString]; - if ((self = [super init])) { NSString *databaseDirectory = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex: 0]; NSString *databasePath = [databaseDirectory stringByAppendingPathComponent:@"com.amplitude.database"]; if (![instanceName isEqualToString:kAMPDefaultInstance]) { databasePath = [NSString stringWithFormat:@"%@_%@", databasePath, instanceName]; } + + // migrate to new db filename + if (![AMPUtils isEmptyString:apiKey]) { + NSString *newDatabasePath = [NSString stringWithFormat:@"%@_%@", databasePath, apiKey]; + [AMPUtils moveFileIfNotExists:databasePath to:newDatabasePath]; + databasePath = newDatabasePath; + } + _databasePath = SAFE_ARC_RETAIN(databasePath); _queue = dispatch_queue_create([QUEUE_NAME UTF8String], NULL); dispatch_queue_set_specific(_queue, kDispatchQueueKey, (__bridge void *)self, NULL); diff --git a/Amplitude/AMPUtils.h b/Amplitude/AMPUtils.h index a80d8ff9..20c3e13b 100644 --- a/Amplitude/AMPUtils.h +++ b/Amplitude/AMPUtils.h @@ -12,5 +12,6 @@ + (id) makeJSONSerializable:(id) obj; + (BOOL) isEmptyString:(NSString*) str; + (NSDictionary*) validateGroups:(NSDictionary*) obj; ++ (BOOL) moveFileIfNotExists:(NSString*)from to:(NSString*)to; @end diff --git a/Amplitude/AMPUtils.m b/Amplitude/AMPUtils.m index 45888039..c68690ac 100644 --- a/Amplitude/AMPUtils.m +++ b/Amplitude/AMPUtils.m @@ -141,4 +141,20 @@ + (NSDictionary *) validateGroups:(NSDictionary *) obj return [NSDictionary dictionaryWithDictionary:dict]; } ++ (BOOL) moveFileIfNotExists:(NSString*)from to:(NSString*)to +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSError *error; + if (![fileManager fileExistsAtPath:to] && + [fileManager fileExistsAtPath:from]) { + if ([fileManager moveItemAtPath:from toPath:to error:&error]) { + AMPLITUDE_LOG(@"INFO: moved %@ to %@", from, to); + } else { + AMPLITUDE_LOG(@"WARN: Move from %@ to %@ failed: %@", from, to, error); + return NO; + } + } + return YES; +} + @end diff --git a/Amplitude/Amplitude.h b/Amplitude/Amplitude.h index 0c489f04..acbce9e5 100644 --- a/Amplitude/Amplitude.h +++ b/Amplitude/Amplitude.h @@ -14,14 +14,16 @@ Setup: 1. In every file that uses analytics, import Amplitude.h at the top `#import "Amplitude.h"` - 2. Be sure to initialize the API in your didFinishLaunchingWithOptions delegate `[[Amplitude instance] initializeApiKey:@"YOUR_API_KEY_HERE"];` - 3. Track an event anywhere in the app `[[Amplitude instance] logEvent:@"EVENT_IDENTIFIER_HERE"];` + 2. Be sure to set the API key and initialize the SDK in your app's didFinishLaunchingWithOptions delegate `[[[Amplitude instance] setApiKey:@"YOUR_API_KEY_HERE"] initialize];` + 3. Track an event anywhere in the app with `[[Amplitude instance] logEvent:@"EVENT_IDENTIFIER_HERE"];` 4. You can attach additional data to any event by passing a NSDictionary object: NSMutableDictionary *eventProperties = [NSMutableDictionary dictionary]; [eventProperties setValue:@"VALUE_GOES_HERE" forKey:@"KEY_GOES_HERE"]; [[Amplitude instance] logEvent:@"Compute Hash" withEventProperties:eventProperties]; + 5. You can configure the SDK via configurable properties and helper methods. You can modify the instance properties at any time, for example `[Amplitude instance].trackingSessionEvents = YES;`. If you plan to call any helper methods to configure the SDK before the first event is logged (for example `setUserId`, or `enableLocationListening`, or `userAdvertisingIdForDeviceId`), you need to do so after calling setApiKey and before calling initialize. For example `[[[[Amplitude instance] setApiKey:@"YOUR_API_KEY_HERE"] setUserId:"@userId"] initialize];`, otherwise you can call them when appropriate, for example calling `setUserId` after the user logs in, etc. + **Note:** you should call SDK methods on an Amplitude instance, for example logging events with the default instance: `[[Amplitude instance] logEvent:@"testEvent"];` **Note:** the SDK supports tracking data to multiple Amplitude apps, via separate named instances. For example: `[[Amplitude instanceWithName:@"app1"] logEvent:@"testEvent"];` See [Tracking Events to Multiple Apps](https://github.com/amplitude/amplitude-ios#tracking-events-to-multiple-amplitude-apps). @@ -58,11 +60,6 @@ @property (nonatomic, strong, readonly) NSString *instanceName; @property (nonatomic ,strong, readonly) NSString *propertyListPath; -/** - Whether or to opt the current user out of tracking. If true then this blocks the logging of any events and properties, and blocks the sending of events to Amplitude servers. - */ -@property (nonatomic, assign) BOOL optOut; - /**----------------------------------------------------------------------------- * @name Configurable SDK thresholds and parameters @@ -126,34 +123,40 @@ + (Amplitude *)instanceWithName:(NSString*) instanceName; /**----------------------------------------------------------------------------- - * @name Initialize the Amplitude SDK with your Amplitude API Key + * @name Set your Amplitude API Key * ----------------------------------------------------------------------------- */ /** - Initializes the Amplitude instance with your Amplitude API key + Set your Amplitude API key in the Amplitude instance. - We recommend you first initialize your class within your "didFinishLaunchingWithOptions" method inside your app delegate. + We recommend you set the api key in your app's "didFinishLaunchingWithOptions" method inside your app delegate. - **Note:** this is required before you can log any events. + **Note:** this is required before you can log any events as well as configure the SDK with any of the helper methods. @param apiKey Your Amplitude key obtained from your dashboard at https://amplitude.com/settings + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together like `[[[Amplitude instance] setApiKey:@"YOUR_API_KEY"] initialize];` + */ +- (Amplitude *)setApiKey:(NSString*) apiKey; + +/**----------------------------------------------------------------------------- + * @name Initialize the SDK + * ----------------------------------------------------------------------------- */ -- (void)initializeApiKey:(NSString*) apiKey; /** - Initializes the Amplitude instance with your Amplitude API key and sets a user identifier for the current user. + After you set the API key call this to enable event tracking. - We recommend you first initialize your class within your "didFinishLaunchingWithOptions" method inside your app delegate. + **Note:** If you are configuring the SDK before tracking the first event, do so before calling initialize. For example: `[[[[Amplitude instance] setApiKey:@"YOUR_API_KEY_HERE"] setUserId:"@userId"] initialize];`. **Note:** this is required before you can log any events. @param apiKey Your Amplitude key obtained from your dashboard at https://amplitude.com/settings - - @param userId If your app has its own login system that you want to track users with, you can set the userId. - -*/ -- (void)initializeApiKey:(NSString*) apiKey userId:(NSString*) userId; + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. + */ +- (Amplitude *)initialize; /**----------------------------------------------------------------------------- @@ -327,9 +330,11 @@ @param userProperties An NSDictionary containing any additional data to be tracked. + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. + @see [Setting Multiple Properties with setUserProperties](https://github.com/amplitude/Amplitude-iOS#setting-multiple-properties-with-setuserproperties) */ -- (void)setUserProperties:(NSDictionary*) userProperties; +- (Amplitude *)setUserProperties:(NSDictionary*) userProperties; /** @@ -341,20 +346,24 @@ @param userProperties An NSDictionary containing any additional data to be tracked. @param replace This is deprecated. In earlier versions of this SDK, this replaced the in-memory userProperties dictionary with the input, but now userProperties are no longer stored in memory. + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. @see [Setting Multiple Properties with setUserProperties](https://github.com/amplitude/Amplitude-iOS#setting-multiple-properties-with-setuserproperties) */ -- (void)setUserProperties:(NSDictionary*) userProperties replace:(BOOL) replace; +- (Amplitude *)setUserProperties:(NSDictionary*) userProperties replace:(BOOL) replace; /** Clears all properties that are tracked on the user level. **Note: the result is irreversible!** + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. @see [Clearing user properties](https://github.com/amplitude/Amplitude-iOS#clearing-user-properties-with-clearuserproperties) */ -- (void)clearUserProperties; +- (Amplitude *)clearUserProperties; /** Adds a user to a group or groups. You need to specify a groupType and groupName(s). @@ -366,13 +375,14 @@ **Note:** this will also set groupType: groupName as a user property. @param groupType You need to specify a group type (for example "orgId"). - @param groupName The value for the group name, can be a string or an array of strings, (for example for groupType orgId, the groupName would be the actual id number, like 15). + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. @see [Setting Groups](https://github.com/amplitude/Amplitude-iOS#setting-groups) */ -- (void)setGroup:(NSString*) groupType groupName:(NSObject*) groupName; +- (Amplitude *)setGroup:(NSString*) groupType groupName:(NSObject*) groupName; /**----------------------------------------------------------------------------- * @name Setting User and Device Identifiers @@ -383,10 +393,12 @@ Sets the userId. @param userId If your app has its own login system that you want to track users with, you can set the userId. + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. @see [Setting Custom UserIds](https://github.com/amplitude/Amplitude-iOS#setting-custom-user-ids) */ -- (void)setUserId:(NSString*) userId; +- (Amplitude *)setUserId:(NSString*) userId; /** Sets the deviceId. @@ -394,10 +406,12 @@ **NOTE: not recommended unless you know what you are doing** @param deviceId If your app has its own system for tracking devices, you can set the deviceId. + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. @see [Setting Custom Device Ids](https://github.com/amplitude/Amplitude-iOS#custom-device-ids) */ -- (void)setDeviceId:(NSString*) deviceId; +- (Amplitude *)setDeviceId:(NSString*) deviceId; /**----------------------------------------------------------------------------- * @name Configuring the SDK instance @@ -410,8 +424,10 @@ If the user wants to opt out of all tracking, use this method to enable opt out for them. Once opt out is enabled, no events will be saved locally or sent to the server. Calling this method again with enabled set to NO will turn tracking back on for the user. @param enabled Whether tracking opt out should be enabled or disabled. + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. */ -- (void)setOptOut:(BOOL)enabled; +- (Amplitude *)setOptOut:(BOOL)enabled; /** Disables sending logged events to Amplitude servers. @@ -419,22 +435,28 @@ If you want to stop logged events from being sent to Amplitude severs, use this method to set the client to offline. Once offline is enabled, logged events will not be sent to the server until offline is disabled. Calling this method again with offline set to NO will allow events to be sent to server and the client will attempt to send events that have been queued while offline. @param offline Whether logged events should be sent to Amplitude servers. + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. */ -- (void)setOffline:(BOOL)offline; +- (Amplitude *)setOffline:(BOOL)offline; /** Enables location tracking. If the user has granted your app location permissions, the SDK will also grab the location of the user. Amplitude will never prompt the user for location permissions itself, this must be done by your app. + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. + **Note:** the user's location is only fetched once per session. Use `updateLocation` to force the SDK to fetch the user's latest location. */ -- (void)enableLocationListening; +- (Amplitude *)enableLocationListening; /** Disables location tracking. If you want location tracking disabled on startup of the app, call disableLocationListening before you call initializeApiKey. + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. */ -- (void)disableLocationListening; +- (Amplitude *)disableLocationListening; /** Forces the SDK to update with the user's last known location if possible. @@ -447,16 +469,23 @@ Uses advertisingIdentifier instead of identifierForVendor as the device ID Apple prohibits the use of advertisingIdentifier if your app does not have advertising. Useful for tying together data from advertising campaigns to anlaytics data. + + @returns the same [Amplitude](#) instance, allowing you to chain multiple method calls together. - **NOTE:** Must be called before initializeApiKey: is called to function. + **NOTE:** If the current device already has a deviceId, calling useAdvertisingIdForDeviceId will override it with the advertisingIdentifier. */ -- (void)useAdvertisingIdForDeviceId; +- (Amplitude *)useAdvertisingIdForDeviceId; /**----------------------------------------------------------------------------- * @name Other Methods * ----------------------------------------------------------------------------- */ +/** + Whether or to opt the current user out of tracking. If true then this blocks the logging of any events and properties, and blocks the sending of events to Amplitude servers. + */ +- (BOOL)optOut; + /** Prints the number of events in the queue. @@ -490,14 +519,8 @@ #pragma mark - Deprecated methods -- (void)initializeApiKey:(NSString*) apiKey userId:(NSString*) userId startSession:(BOOL)startSession __attribute((deprecated())); - - (void)startSession __attribute((deprecated())); -+ (void)initializeApiKey:(NSString*) apiKey __attribute((deprecated())); - -+ (void)initializeApiKey:(NSString*) apiKey userId:(NSString*) userId __attribute((deprecated())); - + (void)logEvent:(NSString*) eventType __attribute((deprecated())); + (void)logEvent:(NSString*) eventType withEventProperties:(NSDictionary*) eventProperties __attribute((deprecated())); diff --git a/Amplitude/Amplitude.m b/Amplitude/Amplitude.m index 0cc93477..e8804162 100644 --- a/Amplitude/Amplitude.m +++ b/Amplitude/Amplitude.m @@ -49,9 +49,7 @@ @interface Amplitude () @property (nonatomic, strong) NSOperationQueue *backgroundQueue; -@property (nonatomic, strong) NSOperationQueue *initializerQueue; @property (nonatomic, strong) AMPDatabaseHelper *dbHelper; -@property (nonatomic, assign) BOOL initialized; @property (nonatomic, assign) BOOL sslPinningEnabled; @property (nonatomic, assign) long long sessionId; @@ -131,14 +129,6 @@ + (Amplitude *)instanceWithName:(NSString*)instanceName { return client; } -+ (void)initializeApiKey:(NSString*) apiKey { - [[Amplitude instance] initializeApiKey:apiKey]; -} - -+ (void)initializeApiKey:(NSString*) apiKey userId:(NSString*) userId { - [[Amplitude instance] initializeApiKey:apiKey userId:userId]; -} - + (void)logEvent:(NSString*) eventType { [[Amplitude instance] logEvent:eventType]; } @@ -217,7 +207,6 @@ - (id)initWithInstanceName:(NSString*) instanceName _sslPinningEnabled = NO; #endif - _initialized = NO; _locationListeningEnabled = YES; _sessionId = -1; _updateScheduled = NO; @@ -226,7 +215,6 @@ - (id)initWithInstanceName:(NSString*) instanceName _backoffUpload = NO; _offline = NO; _instanceName = SAFE_ARC_RETAIN(instanceName); - _dbHelper = SAFE_ARC_RETAIN([AMPDatabaseHelper getDatabaseHelper:instanceName]); self.eventUploadThreshold = kAMPEventUploadThreshold; self.eventMaxCount = kAMPEventMaxCount; @@ -235,21 +223,18 @@ - (id)initWithInstanceName:(NSString*) instanceName self.minTimeBetweenSessionsMillis = kAMPMinTimeBetweenSessionsMillis; _backoffUploadBatchSize = self.eventUploadMaxBatchSize; - _initializerQueue = [[NSOperationQueue alloc] init]; _backgroundQueue = [[NSOperationQueue alloc] init]; // Force method calls to happen in FIFO order by only allowing 1 concurrent operation [_backgroundQueue setMaxConcurrentOperationCount:1]; - // Ensure initialize finishes running asynchronously before other calls are run - [_backgroundQueue setSuspended:YES]; // Name the queue so runOnBackgroundQueue can tell which queue an operation is running _backgroundQueue.name = BACKGROUND_QUEUE_NAME; - [_initializerQueue addOperationWithBlock:^{ + [_backgroundQueue addOperationWithBlock:^{ _deviceInfo = [[AMPDeviceInfo alloc] init]; - _uploadTaskID = UIBackgroundTaskInvalid; - + + // resolve path strings NSString *eventsDataDirectory = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex: 0]; NSString *propertyListPath = [eventsDataDirectory stringByAppendingPathComponent:@"com.amplitude.plist"]; if (![_instanceName isEqualToString:kAMPDefaultInstance]) { @@ -257,9 +242,12 @@ - (id)initWithInstanceName:(NSString*) instanceName } _propertyListPath = SAFE_ARC_RETAIN(propertyListPath); _eventsDataPath = SAFE_ARC_RETAIN([eventsDataDirectory stringByAppendingPathComponent:@"com.amplitude.archiveDict"]); - [self upgradePrefs]; - // Load propertyList object + if ([_instanceName isEqualToString:kAMPDefaultInstance]) { + [self upgradePrefs]; // migrate legacy prefs file for the default instance + } + + // Load propertyList object, which contains current db version _propertyList = SAFE_ARC_RETAIN([self deserializePList:_propertyListPath]); if (!_propertyList) { _propertyList = SAFE_ARC_RETAIN([NSMutableDictionary dictionary]); @@ -271,42 +259,6 @@ - (id)initWithInstanceName:(NSString*) instanceName } else { AMPLITUDE_LOG(@"Loaded from %@", _propertyListPath); } - - // update database if necessary - int oldDBVersion = 1; - NSNumber *oldDBVersionSaved = [_propertyList objectForKey:DATABASE_VERSION]; - if (oldDBVersionSaved != nil) { - oldDBVersion = [oldDBVersionSaved intValue]; - } - - // update the database - if (oldDBVersion < kAMPDBVersion) { - if ([self.dbHelper upgrade:oldDBVersion newVersion:kAMPDBVersion]) { - [_propertyList setObject:[NSNumber numberWithInt:kAMPDBVersion] forKey:DATABASE_VERSION]; - [self savePropertyList]; - } - } - - // only on default instance, migrate all of old _eventsData object to database store if database just created - if ([_instanceName isEqualToString:kAMPDefaultInstance] && oldDBVersion < kAMPDBFirstVersion) { - if ([self migrateEventsDataToDB]) { - // delete events data so don't need to migrate next time - if ([[NSFileManager defaultManager] fileExistsAtPath:_eventsDataPath]) { - [[NSFileManager defaultManager] removeItemAtPath:_eventsDataPath error:NULL]; - } - } - } - SAFE_ARC_RELEASE(_eventsDataPath); - - // try to restore previous session - long long previousSessionId = [self previousSessionId]; - if (previousSessionId >= 0) { - _sessionId = previousSessionId; - } - - [self initializeDeviceId]; - - [_backgroundQueue setSuspended:NO]; }]; // CLLocationManager must be created on the main thread @@ -317,8 +269,6 @@ - (id)initWithInstanceName:(NSString*) instanceName SEL setDelegate = NSSelectorFromString(@"setDelegate:"); [_locationManager performSelector:setDelegate withObject:_locationManagerDelegate]; }); - - [self addObservers]; } return self; } @@ -331,7 +281,7 @@ - (BOOL) migrateEventsDataToDB return NO; } - AMPDatabaseHelper *defaultDbHelper = [AMPDatabaseHelper getDatabaseHelper]; + AMPDatabaseHelper *defaultDbHelper = [AMPDatabaseHelper getDatabaseHelper:nil apiKey:_apiKey]; BOOL success = YES; // migrate events @@ -411,12 +361,12 @@ - (void) dealloc { // Release instance variables SAFE_ARC_RELEASE(_deviceInfo); - SAFE_ARC_RELEASE(_initializerQueue); SAFE_ARC_RELEASE(_lastKnownLocation); SAFE_ARC_RELEASE(_locationManager); SAFE_ARC_RELEASE(_locationManagerDelegate); SAFE_ARC_RELEASE(_propertyList); SAFE_ARC_RELEASE(_propertyListPath); + SAFE_ARC_RELEASE(_eventsDataPath); SAFE_ARC_RELEASE(_dbHelper); SAFE_ARC_RELEASE(_instanceName); @@ -424,66 +374,95 @@ - (void) dealloc { SAFE_ARC_SUPER_DEALLOC(); } -- (void)initializeApiKey:(NSString*) apiKey -{ - [self initializeApiKey:apiKey userId:nil setUserId: NO]; -} - -/** - * Initialize Amplitude with a given apiKey and userId. - */ -- (void)initializeApiKey:(NSString*) apiKey userId:(NSString*) userId -{ - [self initializeApiKey:apiKey userId:userId setUserId: YES]; -} - -/** - * SetUserId: client explicitly initialized with a userId (can be nil). - * If setUserId is NO, then attempt to load userId from saved eventsData. - */ -- (void)initializeApiKey:(NSString*) apiKey userId:(NSString*) userId setUserId:(BOOL) setUserId +- (Amplitude *)setApiKey:(NSString*) apiKey { if (apiKey == nil) { AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil in initializeApiKey:"); - return; + return self; } if (![self isArgument:apiKey validType:[NSString class] methodName:@"initializeApiKey:"]) { - return; - } - if (userId != nil && ![self isArgument:userId validType:[NSString class] methodName:@"initializeApiKey:"]) { - return; + return self; } if ([apiKey length] == 0) { AMPLITUDE_ERROR(@"ERROR: apiKey cannot be blank in initializeApiKey:"); - return; + return self; } - if (!_initialized) { - (void) SAFE_ARC_RETAIN(apiKey); + if (!_apiKey) { + SAFE_ARC_RETAIN(apiKey); SAFE_ARC_RELEASE(_apiKey); _apiKey = apiKey; + _dbHelper = SAFE_ARC_RETAIN([AMPDatabaseHelper getDatabaseHelper:_instanceName apiKey:_apiKey]); [self runOnBackgroundQueue:^{ - if (setUserId) { - [self setUserId:userId]; - } else { - _userId = SAFE_ARC_RETAIN([self.dbHelper getValue:USER_ID]); + // update database if necessary + int oldDBVersion = 1; + NSNumber *oldDBVersionSaved = [_propertyList objectForKey:DATABASE_VERSION]; + if (oldDBVersionSaved != nil) { + oldDBVersion = [oldDBVersionSaved intValue]; + } + + // update the database + if (oldDBVersion < kAMPDBVersion) { + if ([self.dbHelper upgrade:oldDBVersion newVersion:kAMPDBVersion]) { + [_propertyList setObject:[NSNumber numberWithInt:kAMPDBVersion] forKey:DATABASE_VERSION]; + [self savePropertyList]; + } + } + + // only on default instance, migrate all of old _eventsData object to database store if database just created + if ([_instanceName isEqualToString:kAMPDefaultInstance] && oldDBVersion < kAMPDBFirstVersion) { + if ([self migrateEventsDataToDB]) { + // delete events data so don't need to migrate next time + if ([[NSFileManager defaultManager] fileExistsAtPath:_eventsDataPath]) { + [[NSFileManager defaultManager] removeItemAtPath:_eventsDataPath error:NULL]; + } + } } - }]; - UIApplication *app = [self getSharedApplication]; - if (app != nil) { - UIApplicationState state = app.applicationState; - if (state != UIApplicationStateBackground) { - // If this is called while the app is running in the background, for example - // via a push notification, don't call enterForeground - [self enterForeground]; + // try to restore previous session + long long previousSessionId = [self previousSessionId]; + if (previousSessionId >= 0) { + _sessionId = previousSessionId; } + [self initializeDeviceId]; + _userId = SAFE_ARC_RETAIN([self.dbHelper getValue:USER_ID]); + }]; + + // now we can add observers after setting apikey since enterBackground saves timestamp to DB + [self addObservers]; + } + + return self; +} + +- (BOOL)checkApiKeyForMethod:(NSString*) methodName +{ + if (_apiKey == nil) { + AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with setApiKey before calling %@", methodName); + return NO; + } + return YES; +} + +- (Amplitude *)initialize +{ + if (![self checkApiKeyForMethod:@"initialize"]) { + return self; + } + + UIApplication *app = [self getSharedApplication]; + if (app != nil) { + UIApplicationState state = app.applicationState; + if (state != UIApplicationStateBackground) { + // If this is called while the app is running in the background, for example + // via a push notification, don't call enterForeground + [self enterForeground]; } - _initialized = YES; } + return self; } - (UIApplication *)getSharedApplication @@ -495,11 +474,6 @@ - (UIApplication *)getSharedApplication return nil; } -- (void)initializeApiKey:(NSString*) apiKey userId:(NSString*) userId startSession:(BOOL)startSession -{ - [self initializeApiKey:apiKey userId:userId]; -} - /** * Run a block in the background. If already in the background, run immediately. */ @@ -545,8 +519,7 @@ - (void)logEvent:(NSString*) eventType withEventProperties:(NSDictionary*) event - (void)logEvent:(NSString*) eventType withEventProperties:(NSDictionary*) eventProperties withApiProperties:(NSDictionary*) apiProperties withUserProperties:(NSDictionary*) userProperties withGroups:(NSDictionary*) groups withTimestamp:(NSNumber*) timestamp outOfSession:(BOOL) outOfSession { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling logEvent"); + if (![self checkApiKeyForMethod:@"logEvent"]) { return; } @@ -706,8 +679,7 @@ - (void)logRevenue:(NSString*) productIdentifier quantity:(NSInteger) quantity p - (void)logRevenue:(NSString*) productIdentifier quantity:(NSInteger) quantity price:(NSNumber*) price receipt:(NSData*) receipt { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling logRevenue:"); + if (![self checkApiKeyForMethod:@"logRevenue"]) { return; } if (![self isArgument:price validType:[NSNumber class] methodName:@"logRevenue:"]) { @@ -733,8 +705,7 @@ - (void)logRevenue:(NSString*) productIdentifier quantity:(NSInteger) quantity p - (void)logRevenueV2:(AMPRevenue*) revenue { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling logRevenueV2"); + if (![self checkApiKeyForMethod:@"logRevenueV2"]) { return; } if (revenue == nil || ![revenue isValidRevenue]) { @@ -773,8 +744,7 @@ - (void)uploadEvents - (void)uploadEventsWithLimit:(int) limit { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling uploadEvents:"); + if (![self checkApiKeyForMethod:@"uploadEvents"]) { return; } @@ -1138,11 +1108,6 @@ - (void)startNewSession:(NSNumber*) timestamp - (void)sendSessionEvent:(NSString*) sessionEvent { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before sending session event"); - return; - } - if (![self inSession]) { return; } @@ -1218,6 +1183,10 @@ - (void)startSession - (void)identify:(AMPIdentify *)identify { + if (![self checkApiKeyForMethod:@"identify"]) { + return; + } + if (identify == nil || [identify.userPropertyOperations count] == 0) { return; } @@ -1226,10 +1195,14 @@ - (void)identify:(AMPIdentify *)identify #pragma mark - configurations -- (void)setUserProperties:(NSDictionary*) userProperties +- (Amplitude *)setUserProperties:(NSDictionary*) userProperties { + if (![self checkApiKeyForMethod:@"setUserProperties"]) { + return self; + } + if (userProperties == nil || ![self isArgument:userProperties validType:[NSDictionary class] methodName:@"setUserProperties:"] || [userProperties count] == 0) { - return; + return self; } NSDictionary *copy = [userProperties copy]; @@ -1241,43 +1214,51 @@ - (void)setUserProperties:(NSDictionary*) userProperties } [self identify:identify]; }]; + return self; } // maintain for legacy // replace argument is deprecated. In earlier versions of this SDK, this replaced the in-memory userProperties dictionary with the input, but now userProperties are no longer stored in memory. -- (void)setUserProperties:(NSDictionary*) userProperties replace:(BOOL) replace +- (Amplitude *)setUserProperties:(NSDictionary*) userProperties replace:(BOOL) replace { - [self setUserProperties:userProperties]; + return [self setUserProperties:userProperties]; } -- (void)clearUserProperties +- (Amplitude *)clearUserProperties { + if (![self checkApiKeyForMethod:@"clearUserProperties"]) { + return self; + } AMPIdentify *identify = [[AMPIdentify identify] clearAll]; [self identify:identify]; + return self; } -- (void)setGroup:(NSString *)groupType groupName:(NSObject *)groupName +- (Amplitude *)setGroup:(NSString *)groupType groupName:(NSObject *)groupName { - if (_apiKey == nil) { - AMPLITUDE_ERROR(@"ERROR: apiKey cannot be nil or empty, set apiKey with initializeApiKey: before calling setGroupType"); - return; + if (![self checkApiKeyForMethod:@"setGroup"]) { + return self; } if (groupType == nil || [groupType isEqualToString:@""]) { AMPLITUDE_LOG(@"ERROR: groupType cannot be nil or an empty string"); - return; + return self; } NSMutableDictionary *groups = [NSMutableDictionary dictionaryWithObjectsAndKeys:groupName, groupType, nil]; AMPIdentify *identify = [[AMPIdentify identify] set:groupType value:groupName]; [self logEvent:IDENTIFY_EVENT withEventProperties:nil withApiProperties:nil withUserProperties:identify.userPropertyOperations withGroups:groups withTimestamp:nil outOfSession:NO]; - + return self; } -- (void)setUserId:(NSString*) userId +- (Amplitude *)setUserId:(NSString*) userId { + if (![self checkApiKeyForMethod:@"setUserId"]) { + return self; + } + if (!(userId == nil || [self isArgument:userId validType:[NSString class] methodName:@"setUserId:"])) { - return; + return self; } [self runOnBackgroundQueue:^{ @@ -1286,23 +1267,30 @@ - (void)setUserId:(NSString*) userId _userId = userId; (void) [self.dbHelper insertOrReplaceKeyValue:USER_ID value:_userId]; }]; + return self; } -- (void)setOptOut:(BOOL)enabled +- (Amplitude *)setOptOut:(BOOL)enabled { + if (![self checkApiKeyForMethod:@"setOptOut"]) { + return self; + } + [self runOnBackgroundQueue:^{ NSNumber *value = [NSNumber numberWithBool:enabled]; (void) [self.dbHelper insertOrReplaceKeyLongValue:OPT_OUT value:value]; }]; + return self; } -- (void)setOffline:(BOOL)offline +- (Amplitude *)setOffline:(BOOL)offline { _offline = offline; - if (!_offline) { + if (!_offline && _apiKey != nil) { [self uploadEvents]; } + return self; } - (void)setEventUploadMaxBatchSize:(int) eventUploadMaxBatchSize @@ -1313,13 +1301,21 @@ - (void)setEventUploadMaxBatchSize:(int) eventUploadMaxBatchSize - (BOOL)optOut { + if (![self checkApiKeyForMethod:@"optOut"]) { + return NO; + } return [[self.dbHelper getLongValue:OPT_OUT] boolValue]; } -- (void)setDeviceId:(NSString*)deviceId +- (Amplitude *)setDeviceId:(NSString*)deviceId { + if (![self checkApiKeyForMethod:@"setDeviceId"]) { + return self; + } + if (![self isValidDeviceId:deviceId]) { - return; + AMPLITUDE_ERROR(@"ERROR: invalid deviceId '%@', skipping setDeviceId", deviceId); + return self; } [self runOnBackgroundQueue:^{ @@ -1328,6 +1324,7 @@ - (void)setDeviceId:(NSString*)deviceId _deviceId = deviceId; (void) [self.dbHelper insertOrReplaceKeyValue:DEVICE_ID value:deviceId]; }]; + return self; } #pragma mark - location methods @@ -1346,20 +1343,29 @@ - (void)updateLocation } } -- (void)enableLocationListening +- (Amplitude *)enableLocationListening { _locationListeningEnabled = YES; [self updateLocation]; + return self; } -- (void)disableLocationListening +- (Amplitude *)disableLocationListening { _locationListeningEnabled = NO; + return self; } -- (void)useAdvertisingIdForDeviceId +- (Amplitude *)useAdvertisingIdForDeviceId { _useAdvertisingIdForDeviceId = YES; + + // if called after setApiKey, then deviceId will already be initialized, need to overwrite with IDFA + if (_apiKey && ![AMPUtils isEmptyString:_deviceInfo.advertiserID]) { + (void) [self setDeviceId:_deviceInfo.advertiserID]; + } + + return self; } #pragma mark - Getters for device data @@ -1535,8 +1541,8 @@ - (BOOL)upgradePrefs NSString *oldEventsDataDirectory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex: 0]; NSString *oldPropertyListPath = [oldEventsDataDirectory stringByAppendingPathComponent:@"com.amplitude.plist"]; NSString *oldEventsDataPath = [oldEventsDataDirectory stringByAppendingPathComponent:@"com.amplitude.archiveDict"]; - BOOL success = [self moveFileIfNotExists:oldPropertyListPath to:_propertyListPath]; - success &= [self moveFileIfNotExists:oldEventsDataPath to:_eventsDataPath]; + BOOL success = [AMPUtils moveFileIfNotExists:oldPropertyListPath to:_propertyListPath]; + success &= [AMPUtils moveFileIfNotExists:oldEventsDataPath to:_eventsDataPath]; return success; } @@ -1621,22 +1627,5 @@ - (BOOL)archive:(id) obj toFile:(NSString*)path { return [NSKeyedArchiver archiveRootObject:obj toFile:path]; } -- (BOOL)moveFileIfNotExists:(NSString*)from to:(NSString*)to -{ - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSError *error; - if (![fileManager fileExistsAtPath:to] && - [fileManager fileExistsAtPath:from]) { - if ([fileManager copyItemAtPath:from toPath:to error:&error]) { - AMPLITUDE_LOG(@"INFO: copied %@ to %@", from, to); - [fileManager removeItemAtPath:from error:NULL]; - } else { - AMPLITUDE_LOG(@"WARN: Copy from %@ to %@ failed: %@", from, to, error); - return NO; - } - } - return YES; -} - #pragma clang diagnostic pop @end diff --git a/AmplitudeTests/AMPDatabaseHelperTests.m b/AmplitudeTests/AMPDatabaseHelperTests.m index bf5f6093..1c0f2550 100644 --- a/AmplitudeTests/AMPDatabaseHelperTests.m +++ b/AmplitudeTests/AMPDatabaseHelperTests.m @@ -17,9 +17,15 @@ @interface AMPDatabaseHelperTests : XCTestCase @implementation AMPDatabaseHelperTests {} + +NSString *const testApiKey = @"000000"; +NSString *const apiKey1 = @"111111"; +NSString *const apiKey2 = @"222222"; + + - (void)setUp { [super setUp]; - self.databaseHelper = [AMPDatabaseHelper getDatabaseHelper]; + self.databaseHelper = [AMPDatabaseHelper getDatabaseHelper:nil apiKey:testApiKey]; [self.databaseHelper resetDB:NO]; } @@ -29,40 +35,58 @@ - (void)tearDown { self.databaseHelper = nil; } +- (void)testScopeMigration { + // use separate instance/apiKey from test default to prevent overlap of data + NSString *instanceName = @"migrationInstance"; + NSString *apiKey = @"migrationApiKey"; + + // initialize dbHelper with old filename + AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getTestDatabaseHelper:instanceName]; + [dbHelper insertOrReplaceKeyValue:@"migrationTestKey" value:@"migrationTestValue"]; + + // force migration + AMPDatabaseHelper *newDbHelper = [AMPDatabaseHelper getDatabaseHelper:instanceName apiKey:apiKey]; + XCTAssertEqualObjects([newDbHelper getValue:@"migrationTestKey"], @"migrationTestValue"); + + [newDbHelper deleteDB]; +} + - (void)testGetDatabaseHelper { // test backwards compatibility on default instance - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; - XCTAssertEqual(dbHelper, [AMPDatabaseHelper getDatabaseHelper:nil]); - XCTAssertEqual(dbHelper, [AMPDatabaseHelper getDatabaseHelper:@""]); - XCTAssertEqual(dbHelper, [AMPDatabaseHelper getDatabaseHelper:kAMPDefaultInstance]); + AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper:nil apiKey:testApiKey]; + XCTAssertEqual(dbHelper, [AMPDatabaseHelper getDatabaseHelper:@"" apiKey:testApiKey]); + XCTAssertEqual(dbHelper, [AMPDatabaseHelper getDatabaseHelper:kAMPDefaultInstance apiKey:testApiKey]); - AMPDatabaseHelper *a = [AMPDatabaseHelper getDatabaseHelper:@"a"]; - AMPDatabaseHelper *b = [AMPDatabaseHelper getDatabaseHelper:@"b"]; + AMPDatabaseHelper *a = [AMPDatabaseHelper getDatabaseHelper:@"a" apiKey:apiKey1]; + AMPDatabaseHelper *b = [AMPDatabaseHelper getDatabaseHelper:@"b" apiKey:apiKey2]; XCTAssertNotEqual(dbHelper, a); XCTAssertNotEqual(dbHelper, b); XCTAssertNotEqual(a, b); - XCTAssertEqual(a, [AMPDatabaseHelper getDatabaseHelper:@"a"]); - XCTAssertEqual(b, [AMPDatabaseHelper getDatabaseHelper:@"b"]); + XCTAssertEqual(a, [AMPDatabaseHelper getDatabaseHelper:@"a" apiKey:apiKey1]); + XCTAssertEqual(b, [AMPDatabaseHelper getDatabaseHelper:@"b" apiKey:apiKey2]); // test case insensitive instance name - XCTAssertEqual(a, [AMPDatabaseHelper getDatabaseHelper:@"A"]); - XCTAssertEqual(b, [AMPDatabaseHelper getDatabaseHelper:@"B"]); - XCTAssertEqual(dbHelper, [AMPDatabaseHelper getDatabaseHelper:[kAMPDefaultInstance uppercaseString]]); + XCTAssertEqual(a, [AMPDatabaseHelper getDatabaseHelper:@"A" apiKey:apiKey1]); + XCTAssertEqual(b, [AMPDatabaseHelper getDatabaseHelper:@"B" apiKey:apiKey2]); + XCTAssertEqual(dbHelper, [AMPDatabaseHelper getDatabaseHelper:[kAMPDefaultInstance uppercaseString] apiKey:testApiKey]); // test each instance maintains separate database files XCTAssertTrue([a.databasePath rangeOfString:@"com.amplitude.database_a"].location != NSNotFound); + XCTAssertTrue([a.databasePath rangeOfString:@"com.amplitude.database_a_111111"].location != NSNotFound); XCTAssertTrue([b.databasePath rangeOfString:@"com.amplitude.database_b"].location != NSNotFound); + XCTAssertTrue([b.databasePath rangeOfString:@"com.amplitude.database_b_222222"].location != NSNotFound); XCTAssertTrue([dbHelper.databasePath rangeOfString:@"com.amplitude.database"].location != NSNotFound); - XCTAssertTrue([dbHelper.databasePath rangeOfString:@"com.amplitude.database_"].location == NSNotFound); + XCTAssertTrue([dbHelper.databasePath rangeOfString:@"com.amplitude.database_$default_instance"].location == NSNotFound); + XCTAssertTrue([dbHelper.databasePath rangeOfString:@"com.amplitude.database_000000"].location != NSNotFound); [a deleteDB]; [b deleteDB]; } - (void)testSeparateInstances { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; - AMPDatabaseHelper *a = [AMPDatabaseHelper getDatabaseHelper:@"a"]; - AMPDatabaseHelper *b = [AMPDatabaseHelper getDatabaseHelper:@"b"]; + AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper:nil apiKey:testApiKey]; + AMPDatabaseHelper *a = [AMPDatabaseHelper getDatabaseHelper:@"a" apiKey:apiKey1]; + AMPDatabaseHelper *b = [AMPDatabaseHelper getDatabaseHelper:@"b" apiKey:apiKey2]; [a resetDB:NO]; [b resetDB:NO]; diff --git a/AmplitudeTests/Amplitude+Test.h b/AmplitudeTests/Amplitude+Test.h index 94831185..019f6f23 100644 --- a/AmplitudeTests/Amplitude+Test.h +++ b/AmplitudeTests/Amplitude+Test.h @@ -7,15 +7,16 @@ // #import +#import "AMPDatabaseHelper.h" @interface Amplitude (Test) @property (nonatomic, strong) NSOperationQueue *backgroundQueue; @property (nonatomic, strong) NSOperationQueue *initializerQueue; @property (nonatomic, strong) NSMutableDictionary *eventsData; -@property (nonatomic, assign) BOOL initialized; @property (nonatomic, assign) long long sessionId; -@property (nonatomic, strong) NSNumber* lastEventTime; +@property (nonatomic, strong) NSNumber *lastEventTime; +@property (nonatomic, strong) AMPDatabaseHelper *dbHelper; - (void)flushQueue; - (void)flushQueueWithQueue:(NSOperationQueue*) queue; diff --git a/AmplitudeTests/Amplitude+Test.m b/AmplitudeTests/Amplitude+Test.m index 86a9ec71..fe46a887 100644 --- a/AmplitudeTests/Amplitude+Test.m +++ b/AmplitudeTests/Amplitude+Test.m @@ -15,9 +15,11 @@ @implementation Amplitude (Test) @dynamic backgroundQueue; @dynamic initializerQueue; @dynamic eventsData; -@dynamic initialized; @dynamic sessionId; @dynamic lastEventTime; +@dynamic dbHelper; + +NSString *const newTestApiKey = @"000000"; - (void)flushQueue { [self flushQueueWithQueue:[self backgroundQueue]]; @@ -28,22 +30,22 @@ - (void)flushQueueWithQueue:(NSOperationQueue*) queue { } - (NSDictionary *)getEvent:(NSInteger) fromEnd { - NSArray *events = [[AMPDatabaseHelper getDatabaseHelper] getEvents:-1 limit:-1]; + NSArray *events = [self.dbHelper getEvents:-1 limit:-1]; return [events objectAtIndex:[events count] - fromEnd - 1]; } - (NSDictionary *)getLastEvent { - NSArray *events = [[AMPDatabaseHelper getDatabaseHelper] getEvents:-1 limit:-1]; + NSArray *events = [self.dbHelper getEvents:-1 limit:-1]; return [events lastObject]; } - (NSDictionary *)getLastIdentify { - NSArray *identifys = [[AMPDatabaseHelper getDatabaseHelper] getIdentifys:-1 limit:-1]; + NSArray *identifys = [self.dbHelper getIdentifys:-1 limit:-1]; return [identifys lastObject]; } - (NSUInteger)queuedEventCount { - return [[AMPDatabaseHelper getDatabaseHelper] getEventCount]; + return [self.dbHelper getEventCount]; } - (void)flushUploads:(void (^)())handler { diff --git a/AmplitudeTests/AmplitudeTests.m b/AmplitudeTests/AmplitudeTests.m index 140aa26e..9fa7a304 100644 --- a/AmplitudeTests/AmplitudeTests.m +++ b/AmplitudeTests/AmplitudeTests.m @@ -36,7 +36,7 @@ - (void)setUp { [super setUp]; _connectionMock = [OCMockObject mockForClass:NSURLConnection.class]; _connectionCallCount = 0; - [self.amplitude initializeApiKey:apiKey]; + [[self.amplitude setApiKey:apiKey] initialize]; } - (void)tearDown { @@ -73,12 +73,12 @@ - (void)testInstanceWithName { - (void)testInitWithInstanceName { Amplitude *a = [Amplitude instanceWithName:@"APP1"]; - [a flushQueueWithQueue:a.initializerQueue]; + [a flushQueue]; XCTAssertEqualObjects(a.instanceName, @"app1"); XCTAssertTrue([a.propertyListPath rangeOfString:@"com.amplitude.plist_app1"].location != NSNotFound); Amplitude *b = [Amplitude instanceWithName:[kAMPDefaultInstance uppercaseString]]; - [b flushQueueWithQueue:b.initializerQueue]; + [b flushQueue]; XCTAssertEqualObjects(b.instanceName, kAMPDefaultInstance); XCTAssertTrue([b.propertyListPath rangeOfString:@"com.amplitude.plist"].location != NSNotFound); XCTAssertTrue([ b.propertyListPath rangeOfString:@"com.amplitude.plist_"].location == NSNotFound); @@ -100,9 +100,9 @@ - (void)testSeparateInstancesLogEventsSeparate { NSString *newInstance2 = @"newApp2"; NSString *newApiKey2 = @"0987654321"; - AMPDatabaseHelper *oldDbHelper = [AMPDatabaseHelper getDatabaseHelper]; - AMPDatabaseHelper *newDBHelper1 = [AMPDatabaseHelper getDatabaseHelper:newInstance1]; - AMPDatabaseHelper *newDBHelper2 = [AMPDatabaseHelper getDatabaseHelper:newInstance2]; + AMPDatabaseHelper *oldDbHelper = self.databaseHelper; + AMPDatabaseHelper *newDBHelper1 = [AMPDatabaseHelper getDatabaseHelper:newInstance1 apiKey:newApiKey1]; + AMPDatabaseHelper *newDBHelper2 = [AMPDatabaseHelper getDatabaseHelper:newInstance2 apiKey:newApiKey2]; // reset databases [oldDbHelper resetDB:NO]; @@ -110,6 +110,7 @@ - (void)testSeparateInstancesLogEventsSeparate { [newDBHelper2 resetDB:NO]; // setup existing database file, init default instance + [[[[Amplitude instance] setApiKey:apiKey] initialize] flushQueue]; [oldDbHelper insertOrReplaceKeyLongValue:@"sequence_number" value:[NSNumber numberWithLongLong:1000]]; [oldDbHelper addEvent:@"{\"event_type\":\"oldEvent\"}"]; [oldDbHelper addIdentify:@"{\"event_type\":\"$identify\"}"]; @@ -128,8 +129,7 @@ - (void)testSeparateInstancesLogEventsSeparate { XCTAssertNil([newDBHelper2 getLongValue:@"sequence_number"]); // init first new app and verify separate database - [[Amplitude instanceWithName:newInstance1] initializeApiKey:newApiKey1]; - [[Amplitude instanceWithName:newInstance1] flushQueue]; + [[[[Amplitude instanceWithName:newInstance1] setApiKey:newApiKey1] initialize] flushQueue]; XCTAssertNotEqualObjects([[Amplitude instanceWithName:newInstance1] getDeviceId], @"oldDeviceId"); XCTAssertEqualObjects([[Amplitude instanceWithName:newInstance1] getDeviceId], [newDBHelper1 getValue:@"device_id"]); XCTAssertEqual([[Amplitude instanceWithName:newInstance1] getNextSequenceNumber], 1); @@ -137,8 +137,7 @@ - (void)testSeparateInstancesLogEventsSeparate { XCTAssertEqual([newDBHelper1 getIdentifyCount], 0); // init second new app and verify separate database - [[Amplitude instanceWithName:newInstance2] initializeApiKey:newApiKey2]; - [[Amplitude instanceWithName:newInstance2] flushQueue]; + [[[[Amplitude instanceWithName:newInstance2] setApiKey:newApiKey2] initialize] flushQueue]; XCTAssertNotEqualObjects([[Amplitude instanceWithName:newInstance2] getDeviceId], @"oldDeviceId"); XCTAssertEqualObjects([[Amplitude instanceWithName:newInstance2] getDeviceId], [newDBHelper2 getValue:@"device_id"]); XCTAssertEqual([[Amplitude instanceWithName:newInstance2] getNextSequenceNumber], 1); @@ -184,10 +183,9 @@ - (void)testInitializeLoadUserIdFromEventData { XCTAssertEqual([client userId], nil); NSString *testUserId = @"testUserId"; - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper:instanceName]; + AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper:instanceName apiKey:apiKey]; [dbHelper insertOrReplaceKeyValue:@"user_id" value:testUserId]; - [client initializeApiKey:apiKey]; - [client flushQueue]; + [[[client setApiKey:apiKey] initialize] flushQueue]; XCTAssertTrue([[client userId] isEqualToString:testUserId]); } @@ -196,10 +194,9 @@ - (void)testInitializeWithNilUserId { XCTAssertEqual([self.amplitude userId], nil); NSString *nilUserId = nil; - [self.amplitude initializeApiKey:apiKey userId:nilUserId]; - [self.amplitude flushQueue]; + [[[[self.amplitude setApiKey:apiKey] setUserId:nilUserId] initialize] flushQueue]; XCTAssertEqual([self.amplitude userId], nilUserId); - XCTAssertNil([[AMPDatabaseHelper getDatabaseHelper] getValue:@"user_id"]); + XCTAssertNil([self.databaseHelper getValue:@"user_id"]); } - (void)testInitializeWithUserId { @@ -209,8 +206,7 @@ - (void)testInitializeWithUserId { XCTAssertEqual([client userId], nil); NSString *testUserId = @"testUserId"; - [client initializeApiKey:apiKey userId:testUserId]; - [client flushQueue]; + [[[[client setApiKey:apiKey] setUserId:testUserId] initialize] flushQueue]; XCTAssertEqual([client userId], testUserId); } @@ -219,9 +215,27 @@ - (void)testSkipReinitialization { XCTAssertEqual([self.amplitude userId], nil); NSString *testUserId = @"testUserId"; - [self.amplitude initializeApiKey:apiKey userId:testUserId]; - [self.amplitude flushQueue]; - XCTAssertEqual([self.amplitude userId], nil); + [[[[self.amplitude setApiKey:apiKey] setUserId:testUserId] initialize] flushQueue]; + // since setUserId is called explicitly now, will override existing value even though reinitialization skipped + XCTAssertEqualObjects([self.amplitude userId], testUserId); +} + +- (void)testDatabaseScopeMigration { + NSString *migrationInstanceName = @"testMigrationInstance"; + NSString *migrationApiKey = @"testMigrationInstanceApiKey"; + NSString *deviceId = @"testMigrationDeviceId"; + + // create old database file + AMPDatabaseHelper *oldDbHelper = [AMPDatabaseHelper getTestDatabaseHelper:migrationInstanceName]; + [oldDbHelper insertOrReplaceKeyValue:@"device_id" value:deviceId]; + + // init new client, verify migration + Amplitude *client = [Amplitude instanceWithName:migrationInstanceName]; + [[[client setApiKey:migrationApiKey] initialize] flushQueue]; + XCTAssertEqualObjects([client getDeviceId], deviceId); + + AMPDatabaseHelper *newDbHelper = [AMPDatabaseHelper getDatabaseHelper:migrationInstanceName apiKey:migrationApiKey]; + XCTAssertEqualObjects([newDbHelper getValue:@"device_id"], deviceId); } - (void)testClearUserId { @@ -229,8 +243,7 @@ - (void)testClearUserId { XCTAssertEqual([self.amplitude userId], nil); NSString *testUserId = @"testUserId"; - [self.amplitude setUserId:testUserId]; - [self.amplitude flushQueue]; + [[self.amplitude setUserId:testUserId] flushQueue]; XCTAssertEqual([self.amplitude userId], testUserId); [self.amplitude logEvent:@"test"]; [self.amplitude flushQueue]; @@ -238,8 +251,7 @@ - (void)testClearUserId { XCTAssert([[event1 objectForKey:@"user_id"] isEqualToString:testUserId]); NSString *nilUserId = nil; - [self.amplitude setUserId:nilUserId]; - [self.amplitude flushQueue]; + [[self.amplitude setUserId:nilUserId] flushQueue]; XCTAssertEqual([self.amplitude userId], nilUserId); [self.amplitude logEvent:@"test"]; [self.amplitude flushQueue]; @@ -303,7 +315,7 @@ - (void)testUUIDInEvent { [self.amplitude flushQueue]; XCTAssertEqual([self.amplitude queuedEventCount], 2); - NSArray *events = [[AMPDatabaseHelper getDatabaseHelper] getEvents:-1 limit:-1]; + NSArray *events = [self.databaseHelper getEvents:-1 limit:-1]; XCTAssertEqual(2, [[events[1] objectForKey:@"event_id"] intValue]); XCTAssertNotNil([events[0] objectForKey:@"uuid"]); XCTAssertNotNil([events[1] objectForKey:@"uuid"]); @@ -311,16 +323,15 @@ - (void)testUUIDInEvent { } - (void)testIdentify { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; [self.amplitude setEventUploadThreshold:2]; AMPIdentify *identify = [[AMPIdentify identify] set:@"key1" value:@"value1"]; [self.amplitude identify:identify]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); - XCTAssertEqual([dbHelper getIdentifyCount], 1); - XCTAssertEqual([dbHelper getTotalEventCount], 1); + XCTAssertEqual([self.databaseHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 1); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 1); NSDictionary *operations = [NSDictionary dictionaryWithObject:@"value1" forKey:@"key1"]; NSDictionary *expected = [NSDictionary dictionaryWithObject:operations forKey:@"$set"]; @@ -340,22 +351,20 @@ - (void)testIdentify { SAFE_ARC_RELEASE(identify2); [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); - XCTAssertEqual([dbHelper getIdentifyCount], 0); - XCTAssertEqual([dbHelper getTotalEventCount], 0); + XCTAssertEqual([self.databaseHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 0); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 0); } - (void)testLogRevenueV2 { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; - // ignore invalid revenue objects [self.amplitude logRevenueV2:nil]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getEventCount], 0); [self.amplitude logRevenueV2:[AMPRevenue revenue]]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getEventCount], 0); // log valid revenue object NSNumber *price = [NSNumber numberWithDouble:15.99]; @@ -368,7 +377,7 @@ - (void)testLogRevenueV2 { [self.amplitude logRevenueV2:revenue]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 1); + XCTAssertEqual([self.databaseHelper getEventCount], 1); NSDictionary *event = [self.amplitude getLastEvent]; XCTAssertEqualObjects([event objectForKey:@"event_type"], @"revenue_amount"); @@ -403,7 +412,6 @@ - (void) test{ } - (void)testMergeEventsAndIdentifys { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; [self.amplitude setEventUploadThreshold:7]; NSMutableDictionary *serverResponse = [NSMutableDictionary dictionaryWithDictionary: @{ @"response" : [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"/"] statusCode:200 HTTPVersion:nil headerFields:@{}], @@ -419,14 +427,14 @@ - (void)testMergeEventsAndIdentifys { [self.amplitude identify:[[AMPIdentify identify] set:@"gender" value:@"male"]]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 4); - XCTAssertEqual([dbHelper getIdentifyCount], 2); - XCTAssertEqual([dbHelper getTotalEventCount], 6); + XCTAssertEqual([self.databaseHelper getEventCount], 4); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 2); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 6); // verify merging - NSMutableArray *events = [dbHelper getEvents:-1 limit:-1]; - NSMutableArray *identifys = [dbHelper getIdentifys:-1 limit:-1]; - NSDictionary *merged = [self.amplitude mergeEventsAndIdentifys:events identifys:identifys numEvents:[dbHelper getTotalEventCount]]; + NSMutableArray *events = [self.databaseHelper getEvents:-1 limit:-1]; + NSMutableArray *identifys = [self.databaseHelper getIdentifys:-1 limit:-1]; + NSDictionary *merged = [self.amplitude mergeEventsAndIdentifys:events identifys:identifys numEvents:[self.databaseHelper getTotalEventCount]]; NSArray *mergedEvents = [merged objectForKey:@"events"]; XCTAssertEqual(4, [[merged objectForKey:@"max_event_id"] intValue]); @@ -462,13 +470,12 @@ - (void)testMergeEventsAndIdentifys { [self.amplitude identify:[[AMPIdentify identify] unset:@"karma"]]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); - XCTAssertEqual([dbHelper getIdentifyCount], 0); - XCTAssertEqual([dbHelper getTotalEventCount], 0); + XCTAssertEqual([self.databaseHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 0); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 0); } -(void)testMergeEventsBackwardsCompatible { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; [self.amplitude identify:[[AMPIdentify identify] unset:@"key"]]; [self.amplitude logEvent:@"test_event"]; [self.amplitude flushQueue]; @@ -477,16 +484,16 @@ -(void)testMergeEventsBackwardsCompatible { NSMutableDictionary *event = [NSMutableDictionary dictionaryWithDictionary:[self.amplitude getLastEvent]]; [event removeObjectForKey:@"sequence_number"]; long eventId = [[event objectForKey:@"event_id"] longValue]; - [dbHelper removeEvent:eventId]; + [self.databaseHelper removeEvent:eventId]; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:event options:0 error:NULL]; NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - [dbHelper addEvent:jsonString]; + [self.databaseHelper addEvent:jsonString]; SAFE_ARC_RELEASE(jsonString); // the event without sequence number should be ordered before the identify - NSMutableArray *events = [dbHelper getEvents:-1 limit:-1]; - NSMutableArray *identifys = [dbHelper getIdentifys:-1 limit:-1]; - NSDictionary *merged = [self.amplitude mergeEventsAndIdentifys:events identifys:identifys numEvents:[dbHelper getTotalEventCount]]; + NSMutableArray *events = [self.databaseHelper getEvents:-1 limit:-1]; + NSMutableArray *identifys = [self.databaseHelper getIdentifys:-1 limit:-1]; + NSDictionary *merged = [self.amplitude mergeEventsAndIdentifys:events identifys:identifys numEvents:[self.databaseHelper getTotalEventCount]]; NSArray *mergedEvents = [merged objectForKey:@"events"]; XCTAssertEqualObjects([mergedEvents[0] objectForKey:@"event_type"], @"test_event"); XCTAssertNil([mergedEvents[0] objectForKey:@"sequence_number"]); @@ -554,16 +561,14 @@ -(void)testTruncateEventAndIdentify { } -(void)testAutoIncrementSequenceNumber { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; int limit = 10; for (int i = 0; i < limit; i++) { XCTAssertEqual([self.amplitude getNextSequenceNumber], i+1); - XCTAssertEqual([[dbHelper getLongValue:@"sequence_number"] intValue], i+1); + XCTAssertEqual([[self.databaseHelper getLongValue:@"sequence_number"] intValue], i+1); } } -(void)testSetOffline { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; NSMutableDictionary *serverResponse = [NSMutableDictionary dictionaryWithDictionary: @{ @"response" : [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"/"] statusCode:200 HTTPVersion:nil headerFields:@{}], @"data" : [@"success" dataUsingEncoding:NSUTF8StringEncoding] @@ -576,23 +581,22 @@ -(void)testSetOffline { [self.amplitude identify:[[AMPIdentify identify] set:@"key" value:@"value"]]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 2); - XCTAssertEqual([dbHelper getIdentifyCount], 1); - XCTAssertEqual([dbHelper getTotalEventCount], 3); + XCTAssertEqual([self.databaseHelper getEventCount], 2); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 1); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 3); [self.amplitude setOffline:NO]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); - XCTAssertEqual([dbHelper getIdentifyCount], 0); - XCTAssertEqual([dbHelper getTotalEventCount], 0); + XCTAssertEqual([self.databaseHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 0); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 0); } -(void)testSetOfflineTruncate { int eventMaxCount = 3; self.amplitude.eventMaxCount = eventMaxCount; - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; NSMutableDictionary *serverResponse = [NSMutableDictionary dictionaryWithDictionary: @{ @"response" : [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"/"] statusCode:200 HTTPVersion:nil headerFields:@{}], @"data" : [@"success" dataUsingEncoding:NSUTF8StringEncoding] @@ -608,25 +612,25 @@ -(void)testSetOfflineTruncate { [self.amplitude identify:[[AMPIdentify identify] unset:@"key3"]]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 3); - XCTAssertEqual([dbHelper getIdentifyCount], 3); - XCTAssertEqual([dbHelper getTotalEventCount], 6); + XCTAssertEqual([self.databaseHelper getEventCount], 3); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 3); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 6); [self.amplitude logEvent:@"test4"]; [self.amplitude identify:[[AMPIdentify identify] unset:@"key4"]]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 3); - XCTAssertEqual([dbHelper getIdentifyCount], 3); - XCTAssertEqual([dbHelper getTotalEventCount], 6); + XCTAssertEqual([self.databaseHelper getEventCount], 3); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 3); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 6); - NSMutableArray *events = [dbHelper getEvents:-1 limit:-1]; + NSMutableArray *events = [self.databaseHelper getEvents:-1 limit:-1]; XCTAssertEqual([events count], 3); XCTAssertEqualObjects([events[0] objectForKey:@"event_type"], @"test2"); XCTAssertEqualObjects([events[1] objectForKey:@"event_type"], @"test3"); XCTAssertEqualObjects([events[2] objectForKey:@"event_type"], @"test4"); - NSMutableArray *identifys = [dbHelper getIdentifys:-1 limit:-1]; + NSMutableArray *identifys = [self.databaseHelper getIdentifys:-1 limit:-1]; XCTAssertEqual([identifys count], 3); XCTAssertEqualObjects([[[identifys[0] objectForKey:@"user_properties"] objectForKey:@"$unset"] objectForKey:@"key2"], @"-"); XCTAssertEqualObjects([[[identifys[1] objectForKey:@"user_properties"] objectForKey:@"$unset"] objectForKey:@"key3"], @"-"); @@ -636,9 +640,9 @@ -(void)testSetOfflineTruncate { [self.amplitude setOffline:NO]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); - XCTAssertEqual([dbHelper getIdentifyCount], 0); - XCTAssertEqual([dbHelper getTotalEventCount], 0); + XCTAssertEqual([self.databaseHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 0); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 0); } -(void)testTruncateEventsQueues { @@ -646,31 +650,29 @@ -(void)testTruncateEventsQueues { XCTAssertGreaterThanOrEqual(eventMaxCount, kAMPEventRemoveBatchSize); self.amplitude.eventMaxCount = eventMaxCount; - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; [self.amplitude setOffline:YES]; for (int i = 0; i < eventMaxCount; i++) { [self.amplitude logEvent:@"test"]; } [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], eventMaxCount); + XCTAssertEqual([self.databaseHelper getEventCount], eventMaxCount); [self.amplitude logEvent:@"test"]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], eventMaxCount - (eventMaxCount/10) + 1); + XCTAssertEqual([self.databaseHelper getEventCount], eventMaxCount - (eventMaxCount/10) + 1); } -(void)testTruncateEventsQueuesWithOneEvent { int eventMaxCount = 1; self.amplitude.eventMaxCount = eventMaxCount; - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; [self.amplitude logEvent:@"test1"]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], eventMaxCount); + XCTAssertEqual([self.databaseHelper getEventCount], eventMaxCount); [self.amplitude logEvent:@"test2"]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], eventMaxCount); + XCTAssertEqual([self.databaseHelper getEventCount], eventMaxCount); NSDictionary *event = [self.amplitude getLastEvent]; XCTAssertEqualObjects([event objectForKey:@"event_type"], @"test2"); @@ -681,19 +683,18 @@ -(void)testInvalidJSONEventProperties { NSDictionary *properties = [NSDictionary dictionaryWithObjectsAndKeys:url, url, url, @"url", nil]; [self.amplitude logEvent:@"test" withEventProperties:properties]; [self.amplitude flushQueue]; - XCTAssertEqual([[AMPDatabaseHelper getDatabaseHelper] getEventCount], 1); + XCTAssertEqual([self.databaseHelper getEventCount], 1); } -(void)testClearUserProperties { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; [self.amplitude setEventUploadThreshold:2]; [self.amplitude clearUserProperties]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); - XCTAssertEqual([dbHelper getIdentifyCount], 1); - XCTAssertEqual([dbHelper getTotalEventCount], 1); + XCTAssertEqual([self.databaseHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 1); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 1); NSDictionary *expected = [NSDictionary dictionaryWithObject:@"-" forKey:@"$clearAll"]; NSDictionary *event = [self.amplitude getLastIdentify]; @@ -704,13 +705,12 @@ -(void)testClearUserProperties { } -(void)testSetGroup { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; [self.amplitude setGroup:@"orgId" groupName:[NSNumber numberWithInt:15]]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); - XCTAssertEqual([dbHelper getIdentifyCount], 1); - XCTAssertEqual([dbHelper getTotalEventCount], 1); + XCTAssertEqual([self.databaseHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 1); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 1); NSDictionary *groups = [NSDictionary dictionaryWithObject:@"15" forKey:@"orgId"]; NSDictionary *userProperties = [NSDictionary dictionaryWithObject:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:15] forKey:@"orgId"] forKey:@"$set"]; @@ -723,7 +723,6 @@ -(void)testSetGroup { } -(void)testLogEventWithGroups { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; NSMutableDictionary *groups = [NSMutableDictionary dictionary]; [groups setObject:[NSNumber numberWithInt: 10] forKey:[NSNumber numberWithFloat: 1.23]]; // validateGroups should coerce non-string values into strings @@ -735,9 +734,9 @@ -(void)testLogEventWithGroups { [self.amplitude logEvent:@"test" withEventProperties:nil withGroups:groups outOfSession:NO]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 1); - XCTAssertEqual([dbHelper getIdentifyCount], 0); - XCTAssertEqual([dbHelper getTotalEventCount], 1); + XCTAssertEqual([self.databaseHelper getEventCount], 1); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 0); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 1); NSDictionary *expectedGroups = [NSDictionary dictionaryWithObjectsAndKeys:@"10", @"1.23", @[@"test2", @"0"], @"array", nil]; diff --git a/AmplitudeTests/BaseTestCase.m b/AmplitudeTests/BaseTestCase.m index 2d6ddd75..d48eba08 100644 --- a/AmplitudeTests/BaseTestCase.m +++ b/AmplitudeTests/BaseTestCase.m @@ -26,16 +26,15 @@ @implementation BaseTestCase { - (void)setUp { [super setUp]; self.amplitude = [Amplitude alloc]; - self.databaseHelper = [AMPDatabaseHelper getDatabaseHelper]; + self.databaseHelper = [AMPDatabaseHelper getDatabaseHelper:nil apiKey:apiKey]; XCTAssertTrue([self.databaseHelper resetDB:NO]); - [self.amplitude init]; + [[self.amplitude init] flushQueue]; self.amplitude.sslPinningEnabled = NO; } - (void)tearDown { // Ensure all background operations are done - [self.amplitude flushQueueWithQueue:self.amplitude.initializerQueue]; [self.amplitude flushQueue]; SAFE_ARC_RELEASE(_amplitude); SAFE_ARC_RELEASE(_databaseHelper); diff --git a/AmplitudeTests/SSLPinningTests.m b/AmplitudeTests/SSLPinningTests.m index 62cdbaa3..63bd184c 100644 --- a/AmplitudeTests/SSLPinningTests.m +++ b/AmplitudeTests/SSLPinningTests.m @@ -40,7 +40,7 @@ - (void)testSSLWithoutPinning { self.amplitude.sslPinningEnabled = NO; - [self.amplitude initializeApiKey:@"1cc2c1978ebab0f6451112a8f5df4f4e"]; + [[self.amplitude setApiKey:@"1cc2c1978ebab0f6451112a8f5df4f4e"] initialize]; [self.amplitude logEvent:@"Test without SSL Pinning"]; [self.amplitude flushUploads:^() { NSDictionary *event = [self.amplitude getLastEvent]; @@ -61,7 +61,7 @@ - (void)testSSLPinningInvalidCert { self.amplitude.sslPinningEnabled = YES; [AMPURLConnection pinSSLCertificate:@[@"InvalidCertificationAuthority"]]; - [self.amplitude initializeApiKey:@"1cc2c1978ebab0f6451112a8f5df4f4e"]; + [[self.amplitude setApiKey:@"1cc2c1978ebab0f6451112a8f5df4f4e"] initialize]; [self.amplitude logEvent:@"Test Invalid SSL Pinning"]; [self.amplitude flushUploads:^() { @@ -83,7 +83,7 @@ - (void)testSSLPinningValidCert { self.amplitude.sslPinningEnabled = YES; [AMPURLConnection pinSSLCertificate:@[@"ComodoRsaCA", @"ComodoRsaDomainValidationCA"]]; - [self.amplitude initializeApiKey:@"1cc2c1978ebab0f6451112a8f5df4f4e"]; + [[self.amplitude setApiKey:@"1cc2c1978ebab0f6451112a8f5df4f4e"] initialize]; [self.amplitude logEvent:@"Test SSL Pinning"]; [self.amplitude flushUploads:^() { NSDictionary *event = [self.amplitude getLastEvent]; diff --git a/AmplitudeTests/SessionTests.m b/AmplitudeTests/SessionTests.m index 23ea5282..12381719 100644 --- a/AmplitudeTests/SessionTests.m +++ b/AmplitudeTests/SessionTests.m @@ -41,9 +41,7 @@ - (void)testSessionAutoStartedBackground { // mock amplitude object and verify enterForeground not called id mockAmplitude = [OCMockObject partialMockForObject:self.amplitude]; [[mockAmplitude reject] enterForeground]; - [mockAmplitude initializeApiKey:apiKey]; - [mockAmplitude flushQueueWithQueue:[mockAmplitude initializerQueue]]; - [mockAmplitude flushQueue]; + [[[mockAmplitude setApiKey:apiKey] initialize] flushQueue]; [mockAmplitude verify]; XCTAssertEqual([mockAmplitude queuedEventCount], 0); } @@ -55,9 +53,7 @@ - (void)testSessionAutoStartedInactive { id mockAmplitude = [OCMockObject partialMockForObject:self.amplitude]; [[mockAmplitude expect] enterForeground]; - [mockAmplitude initializeApiKey:apiKey]; - [mockAmplitude flushQueueWithQueue:[mockAmplitude initializerQueue]]; - [mockAmplitude flushQueue]; + [[[mockAmplitude setApiKey:apiKey] initialize] flushQueue]; [mockAmplitude verify]; XCTAssertEqual([mockAmplitude queuedEventCount], 0); } @@ -69,9 +65,7 @@ - (void)testSessionHandling { NSDate *date = [NSDate dateWithTimeIntervalSince1970:1000]; [[[mockAmplitude expect] andReturnValue:OCMOCK_VALUE(date)] currentTime]; - [mockAmplitude initializeApiKey:apiKey userId:nil]; - [mockAmplitude flushQueueWithQueue:[mockAmplitude initializerQueue]]; - [mockAmplitude flushQueue]; + [[[[mockAmplitude setApiKey:apiKey] setUserId:nil] initialize] flushQueue]; XCTAssertEqual([mockAmplitude queuedEventCount], 0); XCTAssertEqual([mockAmplitude sessionId], 1000000); @@ -139,9 +133,7 @@ - (void)testSessionHandling { } - (void)testEnterBackgroundDoesNotTrackEvent { - [self.amplitude initializeApiKey:apiKey userId:nil]; - [self.amplitude flushQueueWithQueue:self.amplitude.initializerQueue]; - + [[[[self.amplitude setApiKey:apiKey] setUserId:nil] initialize] flushQueue]; NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center postNotificationName:UIApplicationDidEnterBackgroundNotification object:nil userInfo:nil]; @@ -155,10 +147,7 @@ - (void)testTrackSessionEvents { [[[mockAmplitude expect] andReturnValue:OCMOCK_VALUE(date)] currentTime]; [mockAmplitude setTrackingSessionEvents:YES]; - [mockAmplitude initializeApiKey:apiKey userId:nil]; - [mockAmplitude flushQueueWithQueue:[mockAmplitude initializerQueue]]; - [mockAmplitude flushQueue]; - + [[[[mockAmplitude setApiKey:apiKey] setUserId:nil] initialize] flushQueue]; XCTAssertEqual([mockAmplitude queuedEventCount], 1); XCTAssertEqual([[mockAmplitude getLastEvent][@"session_id"] longLongValue], 1000000); XCTAssertEqualObjects([mockAmplitude getLastEvent][@"event_type"], kAMPSessionStartEvent); @@ -191,10 +180,7 @@ - (void)testSessionEventsOn32BitDevices { [[[mockAmplitude expect] andReturnValue:OCMOCK_VALUE(date)] currentTime]; [mockAmplitude setTrackingSessionEvents:YES]; - [mockAmplitude initializeApiKey:apiKey userId:nil]; - [mockAmplitude flushQueueWithQueue:[mockAmplitude initializerQueue]]; - [mockAmplitude flushQueue]; - + [[[[mockAmplitude setApiKey:apiKey] setUserId:nil] initialize] flushQueue]; XCTAssertEqual([mockAmplitude queuedEventCount], 1); XCTAssertEqual([[mockAmplitude getLastEvent][@"session_id"] longLongValue], 21474836470000); XCTAssertEqualObjects([mockAmplitude getLastEvent][@"event_type"], kAMPSessionStartEvent); @@ -220,18 +206,14 @@ - (void)testSessionEventsOn32BitDevices { } - (void)testSkipSessionCheckWhenLoggingSessionEvents { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; - NSDate *date = [NSDate dateWithTimeIntervalSince1970:1000]; NSNumber *timestamp = [NSNumber numberWithLongLong:[date timeIntervalSince1970] * 1000]; - [dbHelper insertOrReplaceKeyLongValue:@"previous_session_id" value:timestamp]; + [self.databaseHelper insertOrReplaceKeyLongValue:@"previous_session_id" value:timestamp]; self.amplitude.trackingSessionEvents = YES; - [self.amplitude initializeApiKey:apiKey userId:nil]; - - [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 2); - NSArray *events = [dbHelper getEvents:-1 limit:2]; + [[[[self.amplitude setApiKey:apiKey] setUserId:nil] initialize] flushQueue]; + XCTAssertEqual([self.databaseHelper getEventCount], 2); + NSArray *events = [self.databaseHelper getEvents:-1 limit:2]; XCTAssertEqualObjects(events[0][@"event_type"], kAMPSessionEndEvent); XCTAssertEqualObjects(events[1][@"event_type"], kAMPSessionStartEvent); } diff --git a/AmplitudeTests/SetupTests.m b/AmplitudeTests/SetupTests.m index b843975a..0e15f9bc 100644 --- a/AmplitudeTests/SetupTests.m +++ b/AmplitudeTests/SetupTests.m @@ -30,12 +30,12 @@ - (void)tearDown { } - (void)testApiKeySet { - [self.amplitude initializeApiKey:apiKey]; + [[self.amplitude setApiKey:apiKey] initialize]; XCTAssertEqual(self.amplitude.apiKey, apiKey); } - (void)testDeviceIdSet { - [self.amplitude initializeApiKey:apiKey]; + [[self.amplitude setApiKey:apiKey] initialize]; [self.amplitude flushQueue]; XCTAssertNotNil([self.amplitude deviceId]); XCTAssertEqual([self.amplitude deviceId].length, 36); @@ -43,24 +43,26 @@ - (void)testDeviceIdSet { } - (void)testUserIdNotSet { - [self.amplitude initializeApiKey:apiKey]; + [[self.amplitude setApiKey:apiKey] initialize]; [self.amplitude flushQueue]; XCTAssertNil([self.amplitude userId]); } - (void)testUserIdSet { - [self.amplitude initializeApiKey:apiKey userId:userId]; + [[self.amplitude setApiKey:apiKey] setUserId:userId]; + [self.amplitude initialize]; [self.amplitude flushQueue]; XCTAssertEqualObjects([self.amplitude userId], userId); } - (void)testInitializedSet { - [self.amplitude initializeApiKey:apiKey]; - XCTAssert([self.amplitude initialized]); + [[self.amplitude setApiKey:apiKey] initialize]; + [self.amplitude flushQueue]; + XCTAssertEqual(self.amplitude.apiKey, apiKey); } - (void)testOptOut { - [self.amplitude initializeApiKey:apiKey]; + [[self.amplitude setApiKey:apiKey] initialize]; [self.amplitude setOptOut:YES]; [self.amplitude logEvent:@"Opted Out"]; @@ -78,9 +80,8 @@ - (void)testOptOut { } - (void)testUserPropertiesSet { - [self.amplitude initializeApiKey:apiKey]; - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; - XCTAssertEqual([dbHelper getEventCount], 0); + [[self.amplitude setApiKey:apiKey] initialize]; + XCTAssertEqual([self.databaseHelper getEventCount], 0); NSDictionary *properties = @{ @"shoeSize": @10, @@ -90,9 +91,9 @@ - (void)testUserPropertiesSet { [self.amplitude setUserProperties:properties]; [self.amplitude flushQueue]; - XCTAssertEqual([dbHelper getEventCount], 0); - XCTAssertEqual([dbHelper getIdentifyCount], 1); - XCTAssertEqual([dbHelper getTotalEventCount], 1); + XCTAssertEqual([self.databaseHelper getEventCount], 0); + XCTAssertEqual([self.databaseHelper getIdentifyCount], 1); + XCTAssertEqual([self.databaseHelper getTotalEventCount], 1); NSDictionary *expected = [NSDictionary dictionaryWithObject:properties forKey:AMP_OP_SET]; @@ -104,42 +105,40 @@ - (void)testUserPropertiesSet { } - (void)testSetDeviceId { - AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper]; - - [self.amplitude initializeApiKey:apiKey]; + [[self.amplitude setApiKey:apiKey] initialize]; [self.amplitude flushQueue]; NSString *generatedDeviceId = [self.amplitude getDeviceId]; XCTAssertNotNil(generatedDeviceId); XCTAssertEqual(generatedDeviceId.length, 36); - XCTAssertEqualObjects([dbHelper getValue:@"device_id"], generatedDeviceId); + XCTAssertEqualObjects([self.databaseHelper getValue:@"device_id"], generatedDeviceId); // test setting invalid device ids [self.amplitude setDeviceId:nil]; [self.amplitude flushQueue]; XCTAssertEqualObjects([self.amplitude getDeviceId], generatedDeviceId); - XCTAssertEqualObjects([dbHelper getValue:@"device_id"], generatedDeviceId); + XCTAssertEqualObjects([self.databaseHelper getValue:@"device_id"], generatedDeviceId); id dict = [NSDictionary dictionary]; [self.amplitude setDeviceId:dict]; [self.amplitude flushQueue]; XCTAssertEqualObjects([self.amplitude getDeviceId], generatedDeviceId); - XCTAssertEqualObjects([dbHelper getValue:@"device_id"], generatedDeviceId); + XCTAssertEqualObjects([self.databaseHelper getValue:@"device_id"], generatedDeviceId); [self.amplitude setDeviceId:@"e3f5536a141811db40efd6400f1d0a4e"]; [self.amplitude flushQueue]; XCTAssertEqualObjects([self.amplitude getDeviceId], generatedDeviceId); - XCTAssertEqualObjects([dbHelper getValue:@"device_id"], generatedDeviceId); + XCTAssertEqualObjects([self.databaseHelper getValue:@"device_id"], generatedDeviceId); [self.amplitude setDeviceId:@"04bab7ee75b9a58d39b8dc54e8851084"]; [self.amplitude flushQueue]; XCTAssertEqualObjects([self.amplitude getDeviceId], generatedDeviceId); - XCTAssertEqualObjects([dbHelper getValue:@"device_id"], generatedDeviceId); + XCTAssertEqualObjects([self.databaseHelper getValue:@"device_id"], generatedDeviceId); NSString *validDeviceId = [AMPUtils generateUUID]; [self.amplitude setDeviceId:validDeviceId]; [self.amplitude flushQueue]; XCTAssertEqualObjects([self.amplitude getDeviceId], validDeviceId); - XCTAssertEqualObjects([dbHelper getValue:@"device_id"], validDeviceId); + XCTAssertEqualObjects([self.databaseHelper getValue:@"device_id"], validDeviceId); } @end diff --git a/CHANGELOG.md b/CHANGELOG.md index c6592e16..ffa03ea6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## Unreleased +* This is an API-breaking change that replaces `initializeApiKey` with two new methods `setApiKey` and `initialize`. See [Readme](https://github.com/amplitude/Amplitude-iOS#400-update-and-api-breaking-changes-to-SDK-initialization) for details. +* Migrate Sqlite database file to new filename with apiKey. +* Migrate all database logic from `init` to `setApiKey`. +* Run `init` logic on `backgroundQueue`, removing need for separate `initializerQueue`. +* Many methods (such as `setApiKey`, `setUserId`, `setUserProperties`, etc) now return the Amplitude instance, allowing you to chain multiple method calls together. + ### 3.8.4 Re-release (August 19, 2016) * Added support for integration via Carthage. Thanks to @mpurland for the original PR. Thanks to @lexrus for follow up PR to fix framework naming. diff --git a/README.md b/README.md index b1fe8397..c2e63656 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,9 @@ See our [SDK documentation](https://rawgit.com/amplitude/Amplitude-iOS/master/do #import "Amplitude.h" ``` -6. In the application:didFinishLaunchingWithOptions: method of your YourAppNameAppDelegate.m file, initialize the SDK: +6. In the application:didFinishLaunchingWithOptions: method of your YourAppNameAppDelegate.m file, set the API key you received from Step 1. Then call `initialize` to initialize the event tracking: ``` objective-c - [[Amplitude instance] initializeApiKey:@"YOUR_API_KEY_HERE"]; + [[[Amplitude instance] setApiKey:@"YOUR_API_KEY_HERE"] initialize]; ``` 7. To track an event anywhere in the app, call: @@ -41,6 +41,25 @@ See our [SDK documentation](https://rawgit.com/amplitude/Amplitude-iOS/master/do 8. Events are saved locally. Uploads are batched to occur every 30 events and every 30 seconds, as well as on app close. After calling logEvent in your app, you will immediately see data appear on the Amplitude Website. +# 4.0.0 Update and API-breaking changes to SDK initialization # + +Version 4.0.0 is a major update that simplifies how you configure the SDK during initialization. Before v4.0.0 you would initialize the SDK with your API key by calling `initializeApiKey:@"YOUR_API_KEY"`. In v4.0.0 that method has been removed and replace with two new methods `setApiKey:@"YOUR_API_KEY"` to set your API key and `initialize` to initialize the event tracking. + +**NOTE** Since `initialize` starts the event tracking logic, any SDK configuration that you want to do before the first event is logged needs to be done AFTER calling `setApiKey` and BEFORE calling `initialize`. This includes modifying any of the SDK's configurable properties and calling any of the helper methods such as `setUserId`, `setUserProperties`, `useAdvertisingIdForDeviceId`, `enableLocationListening`, etc. + +The helper methods now return the Amplitude instance, allowing you to easily chain multiple method calls together. Example: + +``` objective-c +[[[[[[Amplitude instance] setApiKey:@"API_KEY"] setUserId:userId] useAdvertisingIdForDeviceId] enableLocationListening] initialize]; +``` + +If you track session events you might do something like this: +``` objective-c +[[Amplitude instance] setApiKey:@"API_KEY"]; +[Amplitude instance].trackingSessionEvents = YES; +[[[Amplitude instance] setUserId:userId] initialize]; +``` + # Tracking Events # It's important to think about what types of events you care about as a developer. You should aim to track between 20 and 200 types of events on your site. Common event types are actions the user initiates (such as pressing a button) and events you want the user to complete (such as filling out a form, completing a level, or making a payment).