Data Exporting

To export Apptimize experiment information, such as which variant a user is participating in, use our client-side APIs. Note that we automatically export this information in some of our third party integrations. By accessing this information in your application code you can use or send the data wherever you like. This is most commonly used when you want to send the data to your custom backend. The following events (approximate names) are available to your application:

OnParticipatedInExperimentNotification Triggered every time a user participates in an experiment
OnEnrolledInExperimentNotification Triggered anytime metadata or Apptimize configuration changes cause a user to become enrolled in one or more experiments.
OnUnenrolledInExperimentNotification Triggered anytime metadata or Apptimize configuration changes cause a user to become unenrolled from one or more experiments

Be sure to note the differences between enrolled and participating. Enrolled means Apptimize bucketed the user into the experiment (they met the filtering requirements and randomized allocation) while participating means the user was enrolled and then actually saw the experiment. A user might not participate if they are enrolled but never navigate to the screen that contains the experiment. In most cases we recommend that you use participation information because it is more useful in calculating statistics on your experiments.

Realtime Participation

In your application code you can listen for participation events and then forward them wherever you like. Note that these events happen over time as a user participates in your experiment.

iOS (Objective-C)
// In AppDelegate or equivalent
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(experimentParticipation:)
                                             name:ApptimizeParticipatedInExperimentNotification
                                           object:nil];

- (void)experimentParticipation:(NSNotification*)notification {
    // Get the relevant participation info
    id<ApptimizeTestInfo> experimentInfo = (id<ApptimizeTestInfo>)[[notification userInfo] objectForKey:ApptimizeTestInfoKey];
    NSNumber* isFirstParticipation = (NSNumber*)[[notification userInfo] objectForKey:ApptimizeFirstParticipationKey];

    if ( experimentInfo == null ) {
        // Shouldn't happen but just in case
        return;
    }

    NSDictionary *exportExperimentInfo = @{
        @"isFirstParticipation" : isFirstParticipation,
        @"userId" : [experimentInfo userID],
        @"anonymousUserId" : [experimentInfo anonymousUserID],
        @"experimentName" : [experimentInfo testName],
        @"variantName" : [experimentInfo enrolledVariantName],
        @"experimentId" : [experimentInfo testID],
        @"variantId" : [experimentInfo enrolledVariantID]};

    // Send the participation event
    // Examples - depending on how you track events
    // Single participating event name and experiment & variant info in the dictionary
    [CustomTracking trackEvent:@"Participated" params:exportExperimentInfo];
    // Or put the info directly in the event name
    NSString *participatedString = [NSString stringWithFormat:@"Participated|%@-%@",
                                          [experimentInfo testName],
                                          [experimentInfo enrolledVariantName]];
    [CustomTracking trackEvent:participatedString params:exportExperimentInfo];

    // Set participation as persistent state (dimension/attribute)
    // Example - depending on how you track state information
    [CustomTracking putParticipatingExperiment:experimentAndVariantName];
    // or possibly by adding it to an array dimension called "participating"
    [CustomTracking addValue:experimentAndVariantName toDimensionArray:@"participating"];
}
iOS (Swift)
// In AppDelegate or equivalent
NotificationCenter.default.addObserver(self,
                                       selector: #selector(experimentParticipation(notification:)),
                                       name: NSNotification.Name.ApptimizeParticipatedInExperiment,
                                       object: nil)

@objc func experimentParticipation(notification: NSNotification) {
     guard let experimentInfo = notification.userInfo?[ApptimizeTestInfoKey] as? ApptimizeTestInfo,
           let isFirstParticipation = notification.userInfo?[ApptimizeFirstParticipationKey] as? NSNumber else {
         return
     }

     // Get the relevant participation info
     let exportExperimentInfo = [
         "isFirstParticipation" : isFirstParticipation,
         "userId" : experimentInfo.userID(),
         "anonymousUserId" : experimentInfo.anonymousUserID(),
         "experimentName" : experimentInfo.testName(),
         "variantName" : experimentInfo.enrolledVariantName(),
         "nameAndVariation" : experimentAndVariantName,
         "experimentId" : experimentInfo.testID(),
         "variantId" : experimentInfo.enrolledVariantID()] as [String : Any?]

     // Send the participation event
     // Examples - depending on how you track events
     // Single participating event name and experiment & variant info in the dictionary
     CustomTracking.trackEvent(eventName:"Participated", params:exportExperimentInfo)
     // Or put the info directly in the event name
     let participatedString = "Participated|\(experimentInfo.testName())-\(experimentInfo.enrolledVariantName())"
     CustomTracking.trackEvent(eventName:participatedString, params:exportExperimentInfo)

     // Set participation as persistent state (dimension/attribute)
     // Example - depending on how you track state information
     CustomTracking.putParticipatingExperiment(experimentAndVariantName)
     // or possibly by adding it to an array dimension called "participating"
     CustomTracking.addValue(experimentAndVariantName, toDimensionArray:"participating")
 }
Android
import com.apptimize.Apptimize;
import com.apptimize.ApptimizeTestInfo;
import com.apptimize.Apptimize.OnExperimentRunListener;

// Do this before Apptimize.setup called from wherever Apptimize is initialized
Apptimize.setOnTestRunListener(new OnTestRunListener() {

    // This method is called by Apptimize whenever the user participates in an experiment
    @Override
    public void onTestRun(ApptimizeTestInfo testInfo, boolean isFirstParticipation) {
        String experimentAndVariantName = testInfo.getExperimentName() + "-" + testInfo.getVariantName();
        Map<String, String> exportExperimentInfo = new java.util.HashMap<String, String>(5);
        exportExperimentInfo.put("experimentName",testInfo.getExperimentName());
        exportExperimentInfo.put("variantName",testInfo.getVariantName());
        exportExperimentInfo.put("nameAndVariation",experimentAndVariantName);
        exportExperimentInfo.put("experimentId",testInfo.getTestId().toString());
        exportExperimentInfo.put("variantId",new Long(testInfo.getEnrolledVariantId()).toString());

        // Sending a participation event
        // Examples - depending on how you track events
        // Single participating event name and experiment & variant info in the dictionary
        CustomTracking.trackEvent("Participated", exportExperimentInfo);
        // Or put the info directly in the event name
        CustomTracking.trackEvent("Participated|" + experimentAndVariantName, exportExperimentInfo);

        // Setting the participation as persistent state (dimension/attribute)
        // Examples - depending on how you track state information
        CustomTracking.putParticipatingExperiment(experimentAndVariantName);
        // or possibly by adding it to an array dimension called "participating"
        CustomTracking.addValueToDimensionArray("participating", experimentAndVariantName);
    }
});
JavaScript
// In window.onLoad or equivalent
Apptimize.setOnParticipatedInExperimentCallback(onParticipatedInExperimentCallback);

function onParticipatedInExperimentCallback(variantInfo, isFirstParticipation) {
   var experimentAndVariantName = `${variantInfo.getExperimentName()}-${variantInfo.getVariantName()}`;
   var exportExperimentInfo = {
       "experimentName" : variantInfo.getExperimentName(),
       "variantName" : variantInfo.getVariantName(),
       "nameAndVariation": experimentAndVariantName,
       "experimentId": variantInfo.getExperimentId(),
       "variantId": variantInfo.getVariantId()
   };

   // Sending a participation event
   // Examples - depending on how you track events
   // Single participating event name and experiment & variant info in the dictionary
   CustomTracking.trackEvent("Participated", exportExperimentInfo);
   // Or put the info directly in the event name
   CustomTracking.trackEvent(`Participated|${experimentAndVariantName}`, exportExperimentInfo);

   // Setting the participation as persistent state (dimension/attribute)
   // Examples - depending on how you track state information
   CustomTracking.putParticipatingExperiment(experimentAndVariantName);
   // or possibly by adding it to an array dimension called "participating"
   CustomTracking.addValueToDimensionArray("participating", experimentAndVariantName);
}

Realtime Enrollment

In your application code you can listen for enrollment events and then forward them wherever you like. Note that these events happen over time as experiment configurations are updated (downloading them from the server either the first time a user runs your application or as they are changed by you in the dashboard), or if you make changes to the Apptimize SDK configuration.

iOS (Objective-C)
// In AppDelegate or equivalent
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(apptimizeUserEnrolledInExperiment:)
                                             name:ApptimizeEnrolledInExperimentNotification
                                           object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(apptimizeUserUnenrolledInExperiment:)
                                             name:ApptimizeUnenrolledInExperimentNotification
                                           object:nil];

- (void)apptimizeUserEnrolledInExperiment:(NSNotification*)notification {
    // Get the relevant participation info
    id<ApptimizeTestInfo> experimentInfo = (id<ApptimizeTestInfo>)[[notification userInfo] objectForKey:ApptimizeTestInfoKey];
    if ( experimentInfo == null ) {
        // Shouldn't happen but just in case
        return;
    }

    NSDictionary *exportExperimentInfo = @{
                                           @"userId" : [experimentInfo userID],
                                           @"anonymousUserId" : [experimentInfo anonymousUserID],
                                           @"experimentName" : [experimentInfo testName],
                                           @"variantName" : [experimentInfo enrolledVariantName],
                                           @"experimentId" : [experimentInfo testID],
                                           @"variantId" : [experimentInfo enrolledVariantID]};

    // Send the participation event
    // Examples - depending on how you track events
    // Single participating event name and experiment & variant info in the dictionary
    [CustomTracking trackEvent:@"Enrolled" params:exportExperimentInfo];
    // Or put the info directly in the event name
    NSString *enrolledString = [NSString stringWithFormat:@"Enrolled|%@-%@",
                                          [experimentInfo testName],
                                          [experimentInfo enrolledVariantName]];
    [CustomTracking trackEvent:enrolledString params:exportExperimentInfo];
}

- (void)apptimizeUserUnenrolledInExperiment:(NSNotification*)notification {
    id<ApptimizeTestInfo> experimentInfo = (id<ApptimizeTestInfo>)[[notification userInfo] objectForKey:ApptimizeTestInfoKey];
    NSNumber* unenrollmentReasonValue = (NSNumber*)[[notification userInfo] objectForKey:ApptimizeUnenrollmentReasonKey];

    if ( experimentInfo == null || unenrollmentReasonValue == null ) {
        // Shouldn't happen but just in case
        return;
    }

    UnenrollmentReason unenrollmentReason = (UnenrollmentReason)[unenrollmentReasonValue intValue];
    NSDictionary *exportExperimentInfo = @{
                                           @"uenrollmentReason" : unenrollmentReasonValue,
                                           @"userId" : [experimentInfo userID],
                                           @"anonymousUserId" : [experimentInfo anonymousUserID],
                                           @"experimentName" : [experimentInfo testName],
                                           @"variantName" : [experimentInfo enrolledVariantName],
                                           @"experimentId" : [experimentInfo testID],
                                           @"variantId" : [experimentInfo enrolledVariantID]};

    // Send the participation event
    // Examples - depending on how you track events
    // Single participating event name and experiment & variant info in the dictionary
    [CustomTracking trackEvent:@"Unenrolled" params:exportExperimentInfo];
    // Or put the info directly in the event name
    NSString *unenrolledString = [NSString stringWithFormat:@"Enrolled|%@|%@-%@",
                                          unenrollmentReasonValue,
                                          [experimentInfo testName],
                                          [experimentInfo enrolledVariantName]];
    [CustomTracking trackEvent:unenrolledString params:exportExperimentInfo];
}
iOS (Swift)
// In AppDelegate or equivalent
NotificationCenter.default.addObserver(self,
                       selector: #selector(enrolledInExperiment(notification:)),
                       name: NSNotification.Name.ApptimizeEnrolledInExperiment,
                       object: nil)
NotificationCenter.default.addObserver(self,
                       selector: #selector(unenrolledInExperiment(notification:)),
                       name: NSNotification.Name.ApptimizeUnenrolledInExperiment,
                       object: nil)

@objc func enrolledInExperiment(notification: NSNotification) {
     guard let experimentInfo = notification.userInfo?[ApptimizeTestInfoKey] as? ApptimizeTestInfo else {
         return
     }

     // Get the relevant participation info
     let experimentAndVariantName = "\(experimentInfo.testName())-\(experimentInfo.enrolledVariantName())"
     let exportExperimentInfo = [
         "userId" : experimentInfo.userID(),
         "anonymousUserId" : experimentInfo.anonymousUserID(),
         "experimentName" : experimentInfo.testName(),
         "variantName" : experimentInfo.enrolledVariantName(),
         "nameAndVariation" : experimentAndVariantName,
         "experimentId" : experimentInfo.testID(),
         "variantId" : experimentInfo.enrolledVariantID()] as [String : Any?]

     // Send the enrollment event
     // Examples - depending on how you track events
     // Single enrollment event name and experiment & variant info in the dictionary
     CustomTracking.trackEvent(eventName:"Enrolled", params:exportExperimentInfo)
     // Or put the info directly in the event name
     let enrolledString = "Enrolled|\(experimentInfo.testName())-\(experimentInfo.enrolledVariantName())"
     CustomTracking.trackEvent(eventName:enrolledString, params:exportExperimentInfo)
}

@objc func unenrolledInExperiment(notification: NSNotification) {
     guard let experimentInfo = notification.userInfo?[ApptimizeTestInfoKey] as? ApptimizeTestInfo,
           let unenrollmentReason = notification.userInfo?[ApptimizeUnenrollmentReasonKey] as? UnenrollmentReason else {
         return
     }

     // Get the relevant participation info
     let experimentAndVariantName = experimentInfo.testName() + "-" + experimentInfo.enrolledVariantName()
     let exportExperimentInfo = [
         "unenrollmentReason" : unenrollmentReason,
         "userId" : experimentInfo.userID(),
         "anonymousUserId" : experimentInfo.anonymousUserID(),
         "experimentName" : experimentInfo.testName(),
         "variantName" : experimentInfo.enrolledVariantName(),
         "nameAndVariation" : experimentAndVariantName,
         "experimentId" : experimentInfo.testID(),
         "variantId" : experimentInfo.enrolledVariantID()] as [String : Any?]

     // Send the enrollment event
     // Examples - depending on how you track events
     // Single unenrollment event name and experiment & variant info in the dictionary
     CustomTracking.trackEvent(eventName:"Unenrolled", params:exportExperimentInfo)
     // Or put the info directly in the event name
     let unenrolledString = "Unenrolled|\(unenrollmentReason)|\(experimentInfo.testName())-\(experimentInfo.enrolledVariantName())"
     CustomTracking.trackEvent(eventName:unenrolledString, params:exportExperimentInfo)
}
Android
import com.apptimize.Apptimize;
import com.apptimize.ApptimizeTestInfo;
import com.apptimize.Apptimize.OnExperimentRunListener;

// Do this before Apptimize.setup called from wherever Apptimize is initialized
Apptimize.setOnTestEnrollmentChangedListener(new OnTestEnrollmentChangedListener() {

    // This method is called by Apptimize whenever the user becomes enrolled in a test
    @Override
    public void onEnrolledInTest(ApptimizeTestInfo testInfo) {
        String experimentAndVariantName = testInfo.getExperimentName() + "-" + testInfo.getVariantName();
        Map<String, String> exportExperimentInfo = new java.util.HashMap<String, String>(5);
        exportExperimentInfo.put("experimentName",testInfo.getExperimentName());
        exportExperimentInfo.put("variantName",testInfo.getVariantName());
        exportExperimentInfo.put("nameAndVariation",experimentAndVariantName);
        exportExperimentInfo.put("experimentId",testInfo.getTestId().toString());
        exportExperimentInfo.put("variantId",new Long(testInfo.getEnrolledVariantId()).toString());

        // Sending a participation event
        // Examples - depending on how you track events
        // Single participating event name and experiment & variant info in the dictionary
        CustomTracking.trackEvent("Enrolled", exportExperimentInfo);
        // Or put the info directly in the event name
        CustomTracking.trackEvent("Enrolled|" + experimentAndVariantName, exportExperimentInfo);
    }

    // This method is called by Apptimize whenever the user becomes unenrolled in a test
    @Override
    public void onUnenrolledInTest(ApptimizeTestInfo testInfo, UnenrollmentReason reason) {
        String experimentAndVariantName = testInfo.getExperimentName() + "-" + testInfo.getVariantName();
        Map<String, String> exportExperimentInfo = new java.util.HashMap<String, String>(5);
        exportExperimentInfo.put("experimentName",testInfo.getExperimentName());
        exportExperimentInfo.put("variantName",testInfo.getVariantName());
        exportExperimentInfo.put("nameAndVariation",experimentAndVariantName);
        exportExperimentInfo.put("experimentId",testInfo.getTestId().toString());
        exportExperimentInfo.put("variantId",new Long(testInfo.getEnrolledVariantId()).toString());

        // Sending a participation event
        // Examples - depending on how you track events
        // Single participating event name and experiment & variant info in the dictionary
        CustomTracking.trackEvent("Unenrolled", exportExperimentInfo);
        // Or put the info directly in the event name
        CustomTracking.trackEvent("Unenrolled|" + unenrollmentReason +"|" + experimentAndVariantName, exportExperimentInfo);
    }
});
JavaScript
// In window.onLoad or equivalent
Apptimize.setOnEnrolledInExperimentCallback(onEnrolledInExperimentCallback);
Apptimize.setOnUnenrolledInExperimentCallback(onUnenrolledInExperimentCallback);

function onEnrolledInExperimentCallback(variantInfo) {
   var experimentAndVariantName = `${variantInfo.getExperimentName()}-${variantInfo.getVariantName()}`;
   var exportExperimentInfo = {
       "experimentName" : variantInfo.getExperimentName(),
       "variantName" : variantInfo.getVariantName(),
       "nameAndVariation": experimentAndVariantName,
       "experimentId": variantInfo.getExperimentId(),
       "variantId": variantInfo.getVariantId()
   };

   // Sending a participation event
   // Examples - depending on how you track events
   // Single participating event name and experiment & variant info in the dictionary
   CustomTracking.trackEvent("Enrolled", exportExperimentInfo);
   // Or put the info directly in the event name
   CustomTracking.trackEvent(`Enrolled|${experimentAndVariantName}`, exportExperimentInfo);
}

function onUnenrolledInExperimentCallback(variantInfo, unenrollmentReason) {
   var experimentAndVariantName = `${variantInfo.getExperimentName()}-${variantInfo.getVariantName()}`;
   var exportExperimentInfo = {
       "experimentName" : variantInfo.getExperimentName(),
       "variantName" : variantInfo.getVariantName(),
       "nameAndVariation": experimentAndVariantName,
       "experimentId": variantInfo.getExperimentId(),
       "variantId": variantInfo.getVariantId()
   };

   // Sending a participation event
   // Examples - depending on how you track events
   // Single participating event name and experiment & variant info in the dictionary
   CustomTracking.trackEvent("Unenrolled", exportExperimentInfo);
   // Or put the info directly in the event name
   CustomTracking.trackEvent(`Unenrolled|${unenrollmentReason}|${experimentAndVariantName}`, exportExperimentInfo);
}

Snapshot Enrollment and Participation

You can also lookup the current state of which variants a user is enrolled and participating in. This enables you to check whenever you like to get a snapshot of the state. Although, to be most accurate we recommend that you instead listen for participation events (above) so that you can operate on that data in real-time.

iOS (Objective-C)
// At some point in your application code where you want to take a new snapshot
// Note: in most cases it's better to listen for participation events instead

// Loop through all the enrolled experiments
NSDictionary *apptimizeTestsInfo = [Apptimize testInfo];
for ( NSString *experimentName in apptimizeTestsInfo ) {
    id<ApptimizeTestInfo> experimentInfo = apptimizeTestsInfo[experimentName];
    if ( experimentInfo == null ) {
        // Shouldn't happen but just in case
        return;
    }
    NSString *experimentAndVariantName = [NSString stringWithFormat:@"%@-%@",
                                         experimentName, experimentInfo.enrolledVariantName];

    // Set enrollment as persistent state (dimension/attribute)
    // Example - depending on how you track state information
    [CustomTracking putEnrolledExperiment:experimentAndVariantName];
    // or possibly by adding it to an array dimension called "enrolled"
    [CustomTracking addValue:experimentAndVariantName toDimensionArray:@"enrolled"];

    // Set participation (if applicable) as persistent state (dimension/attribute)
    // Example - depending on how you track state information
    if ( experimentInfo.userHasParticipated ) {
        [CustomTracking putParticipatingExperiment:experimentAndVariantName];
        // or possibly by adding it to an array dimension called "participating"
        [CustomTracking addValue:experimentAndVariantName toDimensionArray:@"participating"];
    }
}
iOS (Swift)
// At some point in your application code where you want to take a new snapshot
// Note: in most cases it's better to listen for participation events instead.

// Loop through all the enrolled experiments
guard let apptimizeTestsInfo = Apptimize.testInfo() else {
    // Shouldn't happen but just in case
    return;
}

apptimizeTestsInfo?.forEach({ (experimentName: String, experimentInfo: ApptimizeTestInfo) in
    let experimentAndVariantName = experimentName + "-" + experimentInfo.enrolledVariantName()

    // Set enrollment as persistent state (dimension/attribute)
    // Example - depending on how you track state information
    CustomTracking.putEnrolledExperiment(experimentAndVariantName)
    // or possibly by adding it to an array dimension called "enrolled"
    CustomTracking.addValue(experimentAndVariantName, toDimensionArray:"enrolled")

    // Set participation (if applicable) as persistent state (dimension/attribute)
    // Example - depending on how you track state information
    if ( experimentInfo.userHasParticipated() ) {
        CustomTracking.putParticipatingExperiment(experimentAndVariantName)
        // or possibly by adding it to an array dimension called "participating"
        CustomTracking.addValue(experimentAndVariantName, toDimensionArray:"participating")
    }
})
Android
// At some point after Apptimize.setup in your application code where you want to
// take a new snapshot
// Note: in most cases it's better to listen for participation events instead

// Loop through all the enrolled experiments
Map<String, ApptimizeTestInfo> testInfoMap = Apptimize.getTestInfo();
if (testInfoMap == null) {
    return;
}

for (String key : testInfoMap.keySet()) {
    ApptimizeTestInfo testInfo = testInfoMap.get(key);
    String experimentAndVariantName = testInfo.getTestName() + "-" + testInfo.getEnrolledVariantName();

    // Set enrollment as persistent state (dimension/attribute)
    // Example - depending on how you track state information
    CustomTracking.putEnrolledExperiment(experimentAndVariantName);
    // or possibly by adding it to an array dimension called "enrolled"
    CustomTracking.addValueToDimensionArray("enrolled", experimentAndVariantName);

    if(testInfo.userHasParticipated()) {
        // Set participation (if applicable) as persistent state (dimension/attribute)
        // Example - depending on how you track state information
        CustomTracking.putParticipatingExperiment(experimentAndVariantName);
        // or possibly by adding it to an array dimension called "participating"
        CustomTracking.addValueToDimensionArray("participating", experimentAndVariantName);
    }
}
JavaScript
// At some point after Apptimize.setup in your application code where you want to
// take a new snapshot
// Note: in most cases it's better to listen for participation events instead
var testInfo = Apptimize.getVariantInfo();

testInfo.forEach(function(variantInfo) {
   var experimentAndVariantName = `${variantInfo.getExperimentName()}-${variantInfo.getVariantName()}`;

   // Set enrollment as persistent state (dimension/attribute)
   // Example - depending on how you track state information
   CustomTracking.putEnrolledExperiment(experimentAndVariantName);
   // or possibly by adding it to an array dimension called "enrolled"
   CustomTracking.addValueToDimensionArray("enrolled", experimentAndVariantName);

   if(testInfo.getUserHasParticipated()) {
       // Set participation (if applicable) as persistent state (dimension/attribute)
       // Example - depending on how you track state information
       CustomTracking.putParticipatingExperiment(experimentAndVariantName);
       // or possibly by adding it to an array dimension called "participating"
       CustomTracking.addValueToDimensionArray("participating", experimentAndVariantName);
   }
});