Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -816,28 +816,33 @@ public OptimizelyConfig getOptimizelyConfig() {
* @return An OptimizelyUserContext associated with this OptimizelyClient.
*/
@Nullable
public OptimizelyUserContext createUserContext(@NonNull String userId,
@NonNull Map<String, Object> attributes) {
if (optimizely != null) {
return optimizely.createUserContext(userId, attributes);
} else {
public OptimizelyUserContextAndroid createUserContext(@NonNull String userId,
@NonNull Map<String, Object> attributes) {
if (optimizely == null) {
logger.warn("Optimizely is not initialized, could not create a user context");
return null;
}

if (userId == null) {
logger.warn("The userId parameter must be nonnull.");
return null;
}

return new OptimizelyUserContextAndroid(optimizely, userId, attributes);
}

@Nullable
public OptimizelyUserContext createUserContext(@NonNull String userId) {
public OptimizelyUserContextAndroid createUserContext(@NonNull String userId) {
return createUserContext(userId, Collections.emptyMap());
}

@Nullable
public OptimizelyUserContext createUserContext() {
public OptimizelyUserContextAndroid createUserContext() {
return createUserContext(Collections.emptyMap());
}

@Nullable
public OptimizelyUserContext createUserContext(@NonNull Map<String, Object> attributes) {
public OptimizelyUserContextAndroid createUserContext(@NonNull Map<String, Object> attributes) {
if (vuid == null) {
logger.warn("Optimizely vuid is not available. A userId is required to create a user context.");
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.DefaultPersistenceDelegate;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
Expand Down Expand Up @@ -90,6 +91,7 @@ public class OptimizelyManager {
@NonNull private UserProfileService userProfileService;
@Nullable private ODPManager odpManager;
@Nullable private final String vuid;
@Nullable private CmabService cmabService;

@Nullable private OptimizelyStartListener optimizelyStartListener;
private boolean returnInMainThreadFromAsyncInit = true;
Expand All @@ -112,6 +114,7 @@ public class OptimizelyManager {
@NonNull NotificationCenter notificationCenter,
@Nullable List<OptimizelyDecideOption> defaultDecideOptions,
@Nullable ODPManager odpManager,
@Nullable CmabService cmabService,
@Nullable String vuid,
@Nullable String clientEngineName,
@Nullable String clientVersion) {
Expand All @@ -137,6 +140,7 @@ public class OptimizelyManager {
this.userProfileService = userProfileService;
this.vuid = vuid;
this.odpManager = odpManager;
this.cmabService = cmabService;
this.notificationCenter = notificationCenter;
this.defaultDecideOptions = defaultDecideOptions;

Expand Down Expand Up @@ -646,6 +650,7 @@ private OptimizelyClient buildOptimizely(@NonNull Context context, @NonNull Stri
builder.withNotificationCenter(notificationCenter);
builder.withDefaultDecideOptions(defaultDecideOptions);
builder.withODPManager(odpManager);
builder.withCmabService(cmabService);
Optimizely optimizely = builder.build();

return new OptimizelyClient(optimizely, LoggerFactory.getLogger(OptimizelyClient.class), vuid);
Expand Down Expand Up @@ -781,15 +786,19 @@ public static class Builder {
@Nullable private List<OptimizelyDecideOption> defaultDecideOptions = null;
@Nullable private ODPEventManager odpEventManager;
@Nullable private ODPSegmentManager odpSegmentManager;
@Nullable private CMABClient cmabClient;

private int odpSegmentCacheSize = 100;
private int odpSegmentCacheTimeoutInSecs = 600;
private int odpSegmentCacheTimeoutInSecs = 10*60;
private int timeoutForODPSegmentFetchInSecs = 10;
private int timeoutForODPEventDispatchInSecs = 10;
private boolean odpEnabled = true;
private boolean vuidEnabled = false;
private String vuid = null;

private int cmabCacheSize = 100;
private int cmabCacheTimeoutInSecs = 30*60;

private String customSdkName = null;
private String customSdkVersion = null;

Expand Down Expand Up @@ -1058,6 +1067,33 @@ public Builder withClientInfo(@Nullable String clientEngineName, @Nullable Strin
this.customSdkVersion = clientVersion;
return this;
}

/**
* Override the default Cmab cache size (100).
* @param size the size
* @return this {@link Builder} instance
*/
public Builder withCmabCacheSize(int size) {
this.cmabCacheSize = size;
return this;
}

/**
* Override the default Cmab cache timeout (30 minutes).
* @param interval the interval
* @param timeUnit the time unit of the timeout argument
* @return this {@link Builder} instance
*/
public Builder withCmabCacheTimeout(int interval, TimeUnit timeUnit) {
this.cmabCacheTimeoutInSecs = (int) timeUnit.toSeconds(interval);
return this;
}

public Builder withCmabClient(CmabClient cmabClient) {
this.cmabClient = cmabClient;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For swift CmabClient is internal, here it is public, why this difference?

return this;
}

/**
* Get a new {@link Builder} instance to create {@link OptimizelyManager} with.
* @param context the application context used to create default service if not provided.
Expand Down Expand Up @@ -1160,6 +1196,13 @@ public OptimizelyManager build(Context context) {
.build();
}

if (cmabClient == null) {
cmabClient = new DefaultCmabClient();
}
DefaultLRUCache<CmabCacheValue> cmabCache = new DefaultLRUCache<>(cmabCacheSize, cmabCacheTimeoutInSecs);
CmabServiceOptions cmabServiceOptions = new CmabServiceOptions(logger, cmabCache, cmabClient);
CmabService cmabService = new DefaultCmabService(cmabServiceOptions);

return new OptimizelyManager(projectId, sdkKey,
datafileConfig,
logger,
Expand All @@ -1173,6 +1216,7 @@ public OptimizelyManager build(Context context) {
notificationCenter,
defaultDecideOptions,
odpManager,
cmabService,
vuid,
customSdkName,
customSdkVersion
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
// Copyright 2025, Optimizely, Inc. and contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.optimizely.ab.android.sdk;

import com.optimizely.ab.Optimizely;
import com.optimizely.ab.OptimizelyForcedDecision;
import com.optimizely.ab.OptimizelyUserContext;
import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption;
import com.optimizely.ab.optimizelydecision.OptimizelyDecision;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

// This class extends OptimizelyUserContext from the Java-SDK core to maintain backward compatibility
// with synchronous decide API calls. It ensures proper functionality for legacy implementations
// that rely on synchronous behavior, while excluding feature flags that require asynchronous decisions.

public class OptimizelyUserContextAndroid extends OptimizelyUserContext {
public OptimizelyUserContextAndroid(@NonNull Optimizely optimizely,
@NonNull String userId,
@NonNull Map<String, ?> attributes) {
super(optimizely, userId, attributes);
}

public OptimizelyUserContextAndroid(@NonNull Optimizely optimizely,
@NonNull String userId,
@NonNull Map<String, ?> attributes,
@Nullable Map<String, OptimizelyForcedDecision> forcedDecisionsMap,
@Nullable List<String> qualifiedSegments) {
super(optimizely, userId, attributes, forcedDecisionsMap, qualifiedSegments);
}

public OptimizelyUserContextAndroid(@NonNull Optimizely optimizely,
@NonNull String userId,
@NonNull Map<String, ?> attributes,
@Nullable Map<String, OptimizelyForcedDecision> forcedDecisionsMap,
@Nullable List<String> qualifiedSegments,
@Nullable Boolean shouldIdentifyUser) {
super(optimizely, userId, attributes, forcedDecisionsMap, qualifiedSegments, shouldIdentifyUser);
}

/**
* Returns a decision result ({@link OptimizelyDecision}) for a given flag key and a user context, which contains all data required to deliver the flag.
* <ul>
* <li>If the SDK finds an error, it’ll return a decision with <b>null</b> for <b>variationKey</b>. The decision will include an error message in <b>reasons</b>.
* </ul>
* <p>
* Note: This API is specifically designed for synchronous decision-making only.
* For asynchronous decision-making, use the decideAsync() API.
* </p>
* @param key A flag key for which a decision will be made.
* @param options A list of options for decision-making.
* @return A decision result.
*/
@Override
public OptimizelyDecision decide(@NonNull String key,
@NonNull List<OptimizelyDecideOption> options) {
return optimizely.decideSync(copy(), key, options);
}

/**
* Returns a decision result ({@link OptimizelyDecision}) for a given flag key and a user context, which contains all data required to deliver the flag.
*
* <p>
* Note: This API is specifically designed for synchronous decision-making only.
* For asynchronous decision-making, use the decideAsync() API.
* </p>
* @param key A flag key for which a decision will be made.
* @return A decision result.
*/
@Override
public OptimizelyDecision decide(@NonNull String key) {
return decide(key, Collections.emptyList());
}

/**
* Returns a key-map of decision results ({@link OptimizelyDecision}) for multiple flag keys and a user context.
* <ul>
* <li>If the SDK finds an error for a key, the response will include a decision for the key showing <b>reasons</b> for the error.
* <li>The SDK will always return key-mapped decisions. When it can not process requests, it’ll return an empty map after logging the errors.
* </ul>
* <p>
* Note: This API is specifically designed for synchronous decision-making only.
* For asynchronous decision-making, use the decideForKeysAsync() API.
* </p>
* @param keys A list of flag keys for which decisions will be made.
* @param options A list of options for decision-making.
* @return All decision results mapped by flag keys.
*/
@Override
public Map<String, OptimizelyDecision> decideForKeys(@NonNull List<String> keys,
@NonNull List<OptimizelyDecideOption> options) {
return optimizely.decideForKeysSync(copy(), keys, options);
}

/**
* Returns a key-map of decision results for multiple flag keys and a user context.
*
* <p>
* Note: This API is specifically designed for synchronous decision-making only.
* For asynchronous decision-making, use the decideForKeysAsync() API.
* </p>
* @param keys A list of flag keys for which decisions will be made.
* @return All decision results mapped by flag keys.
*/
@Override
public Map<String, OptimizelyDecision> decideForKeys(@NonNull List<String> keys) {
return decideForKeys(keys, Collections.emptyList());
}

/**
* Returns a key-map of decision results ({@link OptimizelyDecision}) for all active flag keys.
*
* <p>
* Note: This API is specifically designed for synchronous decision-making only.
* For asynchronous decision-making, use the decideAllAsync() API.
* </p>
* @param options A list of options for decision-making.
* @return All decision results mapped by flag keys.
*/
@Override
public Map<String, OptimizelyDecision> decideAll(@NonNull List<OptimizelyDecideOption> options) {
return optimizely.decideAllSync(copy(), options);
}

/**
* Returns a key-map of decision results ({@link OptimizelyDecision}) for all active flag keys.
*
* <p>
* Note: This API is specifically designed for synchronous decision-making only.
* For asynchronous decision-making, use the decideAllAsync() API.
* </p>
* @return A dictionary of all decision results, mapped by flag keys.
*/
@Override
public Map<String, OptimizelyDecision> decideAll() {
return decideAll(Collections.emptyList());
}

/**
* Returns a decision result asynchronously for a given flag key and a user context.
*
* @param key A flag key for which a decision will be made.
* @param callback A callback to invoke when the decision is available.
* @param options A list of options for decision-making.
*/
public void decideAsync(@Nonnull String key,
@Nonnull OptimizelyDecisionCallback callback,
@Nonnull List<OptimizelyDecideOption> options) {
optimizely.decideAsync(copy(), key, callback, options);
}

/**
* Returns a decision result asynchronously for a given flag key and a user context.
*
* @param key A flag key for which a decision will be made.
* @param callback A callback to invoke when the decision is available.
*/
public void decideAsync(@Nonnull String key, @Nonnull OptimizelyDecisionCallback callback) {
decideAsync(key, callback, Collections.emptyList());
}

/**
* Returns decision results asynchronously for multiple flag keys.
*
* @param keys A list of flag keys for which decisions will be made.
* @param callback A callback to invoke when decisions are available.
* @param options A list of options for decision-making.
*/
public void decideForKeysAsync(@Nonnull List<String> keys,
@Nonnull OptimizelyDecisionsCallback callback,
@Nonnull List<OptimizelyDecideOption> options) {
optimizely.decideForKeysAsync(copy(), keys, callback, options);
}

/**
* Returns decision results asynchronously for multiple flag keys.
*
* @param keys A list of flag keys for which decisions will be made.
* @param callback A callback to invoke when decisions are available.
*/
public void decideForKeysAsync(@Nonnull List<String> keys, @Nonnull OptimizelyDecisionsCallback callback) {
decideForKeysAsync(keys, callback, Collections.emptyList());
}

/**
* Returns decision results asynchronously for all active flag keys.
*
* @param callback A callback to invoke when decisions are available.
* @param options A list of options for decision-making.
*/
public void decideAllAsync(@Nonnull OptimizelyDecisionsCallback callback,
@Nonnull List<OptimizelyDecideOption> options) {
optimizely.decideAllAsync(copy(), callback, options);
}

/**
* Returns decision results asynchronously for all active flag keys.
*
* @param callback A callback to invoke when decisions are available.
*/
public void decideAllAsync(@Nonnull OptimizelyDecisionsCallback callback) {
decideAllAsync(callback, Collections.emptyList());
}

}
Loading
Loading