Disclaimer: This website requires Please enable JavaScript in your browser settings for the best experience.

The availability of features may depend on your plan type. Contact your Customer Success Manager if you have any questions.

Dev guideRecipesAPI Reference
Dev guideAPI ReferenceUser GuideLegal TermsGitHubDev CommunityOptimizely AcademySubmit a ticketLog In
Dev guide

Implement a user profile service for the Android SDK

How to set up a custom User Profile Service or how to use the default for the Optimizely Feature Experimentation Android SDK.

Use a User Profile Service (UPS) to persist information about your users and ensure variation assignments are sticky. For example, if you are working on a backend website, you can create an implementation that reads and saves user profiles from a Redis or Memcached store.

The Android SDK defaults to a User Profile Service that stores this state directly on the device. See the Android SDK User Profile Service.

Use manager.userProfileService.lookup to read a customer’s user profile.

If the User Profile Service does not bucket a user as you expect, then check whether other flags are overriding the bucketing. For more information, see How bucketing works.

Implement a service

To implement a custom User Profile Service, rather than using the default service provided by the Android SDK, refer to the following code samples. The service needs to expose two functions with the following signatures:

  • lookup – Takes a user ID string and returns a user profile matching the schema below.
  • save – Takes a user profile and persists it.

If you want to use the User Profile Service purely for tracking purposes and not sticky bucketing, you can implement only the save method (always return nil from lookup).

The code example below shows the JSON schema for the user profile object.

Use experiment_bucket_map to override the default bucketing behavior and define an alternate experiment variation for a given user. For each experiment that you want to override, add an object to the map. Use the experiment ID as the key and include a variation_id property that specifies the desired variation. If there isn't an entry for an experiment, then the default bucketing behavior persists.

In the example below, ^[a-zA-Z0-9]+$ is the experiment ID.

{
  "title": "UserProfile",
  "type": "object",
  "properties": {
    "user_id": {"type": "string"},
    "experiment_bucket_map": {"type": "object",
                              "patternProperties": {
                                 "^[a-zA-Z0-9]+$": {"type": "object",
                                                    "properties": {"variation_id": {"type":"string"}},
                                                    "required": ["variation_id"]}
                               }
                             }
  },
  "required": ["user_id", "experiment_bucket_map"]
}

The SDK uses the User Profile Service you provide to override the default bucketing behavior when an experiment assignment has been saved.

The User Profile Service will persist variation assignments across app updates. However, the User Profile Service will not persist variation assignments across application re-installs.

When implementing your own User Profile Service, we recommend loading the user profiles into the User Profile Service on initialization and avoiding performing expensive, blocking lookups on the lookup function to minimize the performance impact of incorporating the service.

No-op in Android

In Android, the default User Profile Service is also sticky. This can be overruled by disabling UserProfileService in your Android app. To do so, define a CustomUserProfileService class inheriting on our DefaultUserProfileService class, but overriding the lookupmethod to always return null. See the example below:

class CustomUserProfileService : UserProfileService {

   @Throws(Exception::class)
   override fun lookup(userId: String): Map<String, Any>? {
      return null
   }

   @Throws(Exception::class)
   override fun save(userProfile: Map<String, Any>) {
   }

}
public class CustomUserProfileService implements UserProfileService {

    public Map<String, Object> lookup(String userId) throws Exception {
        return null;
    }

    public void save(Map<String, Object> userProfile) throws Exception {
    }

}

Pass this to your OptimizelyManager:

val customUserProfileService = CustomUserProfileService()
val optimizelyManager = OptimizelyManager.builder()
         .withSDKKey("<Your_SDK_Key>")
         .withUserProfileService(customUserProfileService)
         .build(context)
CustomUserProfileService customUserProfileService = new CustomUserProfileService();   
OptimizelyManager optimizelyManager = OptimizelyManager.builder()
        .withSDKKey("<Your_SDK_Key>")
        .withUserProfileService(customUserProfileService)
        .build(context);

With this custom implementation, the bucketing will no longer be sticky when the traffic allocation changes.