.. _data_exporting: 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 :ref:`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 :ref:`enrolled` and :ref:`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. .. _data_exporting_detailed_user_interaction: Data Exporting - Detailed User Interaction ------------------------------------------- You can also choose to export detailed experiment participation. In order to do that, you would need to set up an Amazon S3 Bucket to which we can upload the export files. .. _aws_s3_set_up: Set up AWS S3 ------------- 1. **Log in** to `Amazon Web Services `_. Click *Services* and go to *S3*. 2. **Create a bucket**. Follow Amazon's process for creating an S3 bucket, selecting *US Standard* for S3 Region. For more information on S3 regions, see `Amazon Regions `_. 3. **Grant permissions** to your newly-created bucket. 1. Go to *Permissions* >> *Access Control List*. 2. Click **Add Account**, and use Apptimize's ID as the canonical ID / account field: :code:`77cb04d110994f05ffa1eb3eb37642f8283198d4dcd6c49ca5fcd2062c9bdbaf` 3. Select *List objects* and *Write objects*. You can select additional permission, but your bucket must have these permissions to support an Airship integration. 4. Click *Save*. .. _realtime_participation: 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. .. tabs:: .. code-tab:: java Android (Java) :caption: Android (Java) 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 exportExperimentInfo = new java.util.HashMap(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); } }); .. code-tab:: objectivec iOS (Objective C) :caption: 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 experimentInfo = (id)[[notification userInfo] objectForKey:ApptimizeTestInfoKey]; NSNumber* isFirstParticipation = (NSNumber*)[[notification userInfo] objectForKey:ApptimizeFirstParticipationKey]; if ( experimentInfo == nil ) { // 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"]; } .. code-tab:: swift iOS (Swift) :caption: 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") } .. code-tab:: javascript JavaScript :caption: 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); } .. code-tab:: java Java Server :caption: Java import com.apptimize.Apptimize; import com.apptimize.VariantInfo; import com.apptimize.events.OnParticipatedInExperimentListener; // Do this before Apptimize.setup called from wherever Apptimize is initialized Apptimize.setOnParticipatedInExperimentCallback(new OnParticipatedInExperimentListener() { // This method is called by Apptimize whenever the user participates in an experiment @Override public void onParticipatedInExperiment (VariantInfo variantInfo, boolean isFirstParticipation) { String experimentAndVariantName = variantInfo.getExperimentName() + "-" + variantInfo.getVariantName(); Map exportExperimentInfo = new java.util.HashMap(7); exportExperimentInfo.put("isFirstParticipation",isFirstParticipation ? "firstParticipation" : "notFirstParticipation"); exportExperimentInfo.put("userId", variantInfo.getUserId()); exportExperimentInfo.put("experimentName",variantInfo.getExperimentName()); exportExperimentInfo.put("variantName",variantInfo.getVariantName()); exportExperimentInfo.put("nameAndVariation",experimentAndVariantName); exportExperimentInfo.put("experimentId",variantInfo.getExperimentId().toString()); exportExperimentInfo.put("variantId",new Long(variantInfo.getVariantId()).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); } }); .. code-tab:: javascript Node.js :caption: Node.js // Do this before Apptimize.setup called from wherever Apptimize is initialized 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); } .. code-tab:: python Python :caption: Python 3 # Do this before Apptimize.setup called from wherever Apptimize is initialized def onParticipatedInExperiment(VariantInfo variantInfo): experimentAndVariantName = variantInfo.getExperimentName() + "-" + variantInfo.getVariantName() exportExperimentInfo = { "experimentName" : variantInfo.getExperimentName(), "variantName" : variantInfo.getVariantName(), "userId" : variantInfo.getUserId(), "nameAndVariation" : experimentAndVariantName, "experimentId" : str(variantInfo.getExperimentId()), "variantId" : str(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) Apptimize.setOnParticipatedInExperimentCallback(onParticipatedInExperiment) .. _realtime_enrollment: 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. .. tabs:: .. code-tab:: java Android (Java) :caption: Android (Java) 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 exportExperimentInfo = new java.util.HashMap(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 exportExperimentInfo = new java.util.HashMap(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); } }); .. code-tab:: objectivec iOS (Objective C) :caption: 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 experimentInfo = (id)[[notification userInfo] objectForKey:ApptimizeTestInfoKey]; if ( experimentInfo == nil ) { // 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 experimentInfo = (id)[[notification userInfo] objectForKey:ApptimizeTestInfoKey]; NSNumber* unenrollmentReasonValue = (NSNumber*)[[notification userInfo] objectForKey:ApptimizeUnenrollmentReasonKey]; if ( experimentInfo == nil || unenrollmentReasonValue == nil ) { // 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]; } .. code-tab:: swift iOS (Swift) :caption: 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) } .. code-tab:: javascript JavaScript :caption: 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); } **Note:** *Our Server Side SDKs are stateless and hence there is no enrollment state unlike the client side SDKs.* .. _snapshot_enrollment_and_participation: 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. .. tabs:: .. code-tab:: java Android (Java) :caption: Android (Java) // 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 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); } } .. code-tab:: objectivec iOS (Objective C) :caption: 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 experimentInfo = apptimizeTestsInfo[experimentName]; if ( experimentInfo == nil ) { // 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"]; } } .. code-tab:: swift iOS (Swift) :caption: 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") } }) .. code-tab:: javascript JavaScript :caption: 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); } }); .. note:: It is not possible to retrieve a snapshot of enrollment and participation with our Server Side SDKs because they are stateless. You can snapshot participation using variantInfo.getParticipationPhase() API on the server SDKs.