Skip to content

Commit a749f11

Browse files
committed
add cmab support
1 parent b00f8f1 commit a749f11

File tree

9 files changed

+870
-8
lines changed

9 files changed

+870
-8
lines changed

android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyClient.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -818,12 +818,17 @@ public OptimizelyConfig getOptimizelyConfig() {
818818
@Nullable
819819
public OptimizelyUserContext createUserContext(@NonNull String userId,
820820
@NonNull Map<String, Object> attributes) {
821-
if (optimizely != null) {
822-
return optimizely.createUserContext(userId, attributes);
823-
} else {
821+
if (optimizely == null) {
824822
logger.warn("Optimizely is not initialized, could not create a user context");
825823
return null;
826824
}
825+
826+
if (userId == null) {
827+
logger.warn("The userId parameter must be nonnull.");
828+
return null;
829+
}
830+
831+
return new OptimizelyUserContextAndroid(optimizely, userId, attributes);
827832
}
828833

829834
@Nullable

android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyManager.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import org.slf4j.Logger;
5959
import org.slf4j.LoggerFactory;
6060

61+
import java.beans.DefaultPersistenceDelegate;
6162
import java.io.IOException;
6263
import java.io.InputStream;
6364
import java.util.Collections;
@@ -90,6 +91,7 @@ public class OptimizelyManager {
9091
@NonNull private UserProfileService userProfileService;
9192
@Nullable private ODPManager odpManager;
9293
@Nullable private final String vuid;
94+
@Nullable private CmabService cmabService;
9395

9496
@Nullable private OptimizelyStartListener optimizelyStartListener;
9597
private boolean returnInMainThreadFromAsyncInit = true;
@@ -112,6 +114,7 @@ public class OptimizelyManager {
112114
@NonNull NotificationCenter notificationCenter,
113115
@Nullable List<OptimizelyDecideOption> defaultDecideOptions,
114116
@Nullable ODPManager odpManager,
117+
@Nullable CmabService cmabService,
115118
@Nullable String vuid,
116119
@Nullable String clientEngineName,
117120
@Nullable String clientVersion) {
@@ -137,6 +140,7 @@ public class OptimizelyManager {
137140
this.userProfileService = userProfileService;
138141
this.vuid = vuid;
139142
this.odpManager = odpManager;
143+
this.cmabService = cmabService;
140144
this.notificationCenter = notificationCenter;
141145
this.defaultDecideOptions = defaultDecideOptions;
142146

@@ -646,6 +650,7 @@ private OptimizelyClient buildOptimizely(@NonNull Context context, @NonNull Stri
646650
builder.withNotificationCenter(notificationCenter);
647651
builder.withDefaultDecideOptions(defaultDecideOptions);
648652
builder.withODPManager(odpManager);
653+
builder.withCmabService(cmabService);
649654
Optimizely optimizely = builder.build();
650655

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

785791
private int odpSegmentCacheSize = 100;
786-
private int odpSegmentCacheTimeoutInSecs = 600;
792+
private int odpSegmentCacheTimeoutInSecs = 10*60;
787793
private int timeoutForODPSegmentFetchInSecs = 10;
788794
private int timeoutForODPEventDispatchInSecs = 10;
789795
private boolean odpEnabled = true;
790796
private boolean vuidEnabled = false;
791797
private String vuid = null;
792798

799+
private int cmabCacheSize = 100;
800+
private int cmabCacheTimeoutInSecs = 30*60;
801+
793802
private String customSdkName = null;
794803
private String customSdkVersion = null;
795804

@@ -1058,6 +1067,12 @@ public Builder withClientInfo(@Nullable String clientEngineName, @Nullable Strin
10581067
this.customSdkVersion = clientVersion;
10591068
return this;
10601069
}
1070+
1071+
public Builder withCmabClient(CmabClient cmabClient) {
1072+
this.cmabClient = cmabClient;
1073+
return this;
1074+
}
1075+
10611076
/**
10621077
* Get a new {@link Builder} instance to create {@link OptimizelyManager} with.
10631078
* @param context the application context used to create default service if not provided.
@@ -1160,6 +1175,13 @@ public OptimizelyManager build(Context context) {
11601175
.build();
11611176
}
11621177

1178+
if (cmabClient == null) {
1179+
cmabClient = new DefaultCmabClient();
1180+
}
1181+
DefaultLRUCache<CmabCacheValue> cmabCache = new DefaultLRUCache<>(cmabCacheSize, cmabCacheTimeoutInSecs);
1182+
CmabServiceOptions cmabServiceOptions = new CmabServiceOptions(logger, cmabCache, cmabClient);
1183+
CmabService cmabService = new DefaultCmabService(cmabServiceOptions);
1184+
11631185
return new OptimizelyManager(projectId, sdkKey,
11641186
datafileConfig,
11651187
logger,
@@ -1173,6 +1195,7 @@ public OptimizelyManager build(Context context) {
11731195
notificationCenter,
11741196
defaultDecideOptions,
11751197
odpManager,
1198+
cmabService,
11761199
vuid,
11771200
customSdkName,
11781201
customSdkVersion
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
// Copyright 2025, Optimizely, Inc. and contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.optimizely.ab.android.sdk;
16+
17+
import com.optimizely.ab.Optimizely;
18+
import com.optimizely.ab.OptimizelyForcedDecision;
19+
import com.optimizely.ab.OptimizelyUserContext;
20+
import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption;
21+
import com.optimizely.ab.optimizelydecision.OptimizelyDecision;
22+
23+
import java.util.Collections;
24+
import java.util.List;
25+
import java.util.Map;
26+
import androidx.annotation.NonNull;
27+
import androidx.annotation.Nullable;
28+
29+
// This class extends OptimizelyUserContext from the Java-SDK core to maintain backward compatibility
30+
// with synchronous decide API calls. It ensures proper functionality for legacy implementations
31+
// that rely on synchronous behavior, while excluding feature flags that require asynchronous decisions.
32+
33+
public class OptimizelyUserContextAndroid extends OptimizelyUserContext {
34+
public OptimizelyUserContextAndroid(@NonNull Optimizely optimizely,
35+
@NonNull String userId,
36+
@NonNull Map<String, ?> attributes) {
37+
super(optimizely, userId, attributes);
38+
}
39+
40+
public OptimizelyUserContextAndroid(@NonNull Optimizely optimizely,
41+
@NonNull String userId,
42+
@NonNull Map<String, ?> attributes,
43+
@Nullable Map<String, OptimizelyForcedDecision> forcedDecisionsMap,
44+
@Nullable List<String> qualifiedSegments) {
45+
super(optimizely, userId, attributes, forcedDecisionsMap, qualifiedSegments);
46+
}
47+
48+
public OptimizelyUserContextAndroid(@NonNull Optimizely optimizely,
49+
@NonNull String userId,
50+
@NonNull Map<String, ?> attributes,
51+
@Nullable Map<String, OptimizelyForcedDecision> forcedDecisionsMap,
52+
@Nullable List<String> qualifiedSegments,
53+
@Nullable Boolean shouldIdentifyUser) {
54+
super(optimizely, userId, attributes, forcedDecisionsMap, qualifiedSegments, shouldIdentifyUser);
55+
}
56+
57+
/**
58+
* Returns a decision result ({@link OptimizelyDecision}) for a given flag key and a user context, which contains all data required to deliver the flag.
59+
* <ul>
60+
* <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>.
61+
* </ul>
62+
* <p>
63+
* Note: This API is specifically designed for synchronous decision-making only.
64+
* For asynchronous decision-making, use the decideAsync() API.
65+
* </p>
66+
* @param key A flag key for which a decision will be made.
67+
* @param options A list of options for decision-making.
68+
* @return A decision result.
69+
*/
70+
@Override
71+
public OptimizelyDecision decide(@NonNull String key,
72+
@NonNull List<OptimizelyDecideOption> options) {
73+
return optimizely.decideSync(copy(), key, options);
74+
}
75+
76+
/**
77+
* Returns a decision result ({@link OptimizelyDecision}) for a given flag key and a user context, which contains all data required to deliver the flag.
78+
*
79+
* <p>
80+
* Note: This API is specifically designed for synchronous decision-making only.
81+
* For asynchronous decision-making, use the decideAsync() API.
82+
* </p>
83+
* @param key A flag key for which a decision will be made.
84+
* @return A decision result.
85+
*/
86+
@Override
87+
public OptimizelyDecision decide(@NonNull String key) {
88+
return decide(key, Collections.emptyList());
89+
}
90+
91+
/**
92+
* Returns a key-map of decision results ({@link OptimizelyDecision}) for multiple flag keys and a user context.
93+
* <ul>
94+
* <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.
95+
* <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.
96+
* </ul>
97+
* <p>
98+
* Note: This API is specifically designed for synchronous decision-making only.
99+
* For asynchronous decision-making, use the decideForKeysAsync() API.
100+
* </p>
101+
* @param keys A list of flag keys for which decisions will be made.
102+
* @param options A list of options for decision-making.
103+
* @return All decision results mapped by flag keys.
104+
*/
105+
@Override
106+
public Map<String, OptimizelyDecision> decideForKeys(@NonNull List<String> keys,
107+
@NonNull List<OptimizelyDecideOption> options) {
108+
return optimizely.decideForKeysSync(copy(), keys, options);
109+
}
110+
111+
/**
112+
* Returns a key-map of decision results for multiple flag keys and a user context.
113+
*
114+
* <p>
115+
* Note: This API is specifically designed for synchronous decision-making only.
116+
* For asynchronous decision-making, use the decideForKeysAsync() API.
117+
* </p>
118+
* @param keys A list of flag keys for which decisions will be made.
119+
* @return All decision results mapped by flag keys.
120+
*/
121+
@Override
122+
public Map<String, OptimizelyDecision> decideForKeys(@NonNull List<String> keys) {
123+
return decideForKeys(keys, Collections.emptyList());
124+
}
125+
126+
/**
127+
* Returns a key-map of decision results ({@link OptimizelyDecision}) for all active flag keys.
128+
*
129+
* <p>
130+
* Note: This API is specifically designed for synchronous decision-making only.
131+
* For asynchronous decision-making, use the decideAllAsync() API.
132+
* </p>
133+
* @param options A list of options for decision-making.
134+
* @return All decision results mapped by flag keys.
135+
*/
136+
@Override
137+
public Map<String, OptimizelyDecision> decideAll(@NonNull List<OptimizelyDecideOption> options) {
138+
return optimizely.decideAllSync(copy(), options);
139+
}
140+
141+
/**
142+
* Returns a key-map of decision results ({@link OptimizelyDecision}) for all active flag keys.
143+
*
144+
* <p>
145+
* Note: This API is specifically designed for synchronous decision-making only.
146+
* For asynchronous decision-making, use the decideAllAsync() API.
147+
* </p>
148+
* @return A dictionary of all decision results, mapped by flag keys.
149+
*/
150+
@Override
151+
public Map<String, OptimizelyDecision> decideAll() {
152+
return decideAll(Collections.emptyList());
153+
}
154+
155+
/**
156+
* Returns a decision result asynchronously for a given flag key and a user context.
157+
*
158+
* @param key A flag key for which a decision will be made.
159+
* @param callback A callback to invoke when the decision is available.
160+
* @param options A list of options for decision-making.
161+
*/
162+
public void decideAsync(@Nonnull String key,
163+
@Nonnull OptimizelyDecisionCallback callback,
164+
@Nonnull List<OptimizelyDecideOption> options) {
165+
optimizely.decideAsync(copy(), key, callback, options);
166+
}
167+
168+
/**
169+
* Returns a decision result asynchronously for a given flag key and a user context.
170+
*
171+
* @param key A flag key for which a decision will be made.
172+
* @param callback A callback to invoke when the decision is available.
173+
*/
174+
public void decideAsync(@Nonnull String key, @Nonnull OptimizelyDecisionCallback callback) {
175+
decideAsync(key, callback, Collections.emptyList());
176+
}
177+
178+
/**
179+
* Returns decision results asynchronously for multiple flag keys.
180+
*
181+
* @param keys A list of flag keys for which decisions will be made.
182+
* @param callback A callback to invoke when decisions are available.
183+
* @param options A list of options for decision-making.
184+
*/
185+
public void decideForKeysAsync(@Nonnull List<String> keys,
186+
@Nonnull OptimizelyDecisionsCallback callback,
187+
@Nonnull List<OptimizelyDecideOption> options) {
188+
optimizely.decideForKeysAsync(copy(), keys, callback, options);
189+
}
190+
191+
/**
192+
* Returns decision results asynchronously for multiple flag keys.
193+
*
194+
* @param keys A list of flag keys for which decisions will be made.
195+
* @param callback A callback to invoke when decisions are available.
196+
*/
197+
public void decideForKeysAsync(@Nonnull List<String> keys, @Nonnull OptimizelyDecisionsCallback callback) {
198+
decideForKeysAsync(keys, callback, Collections.emptyList());
199+
}
200+
201+
/**
202+
* Returns decision results asynchronously for all active flag keys.
203+
*
204+
* @param callback A callback to invoke when decisions are available.
205+
* @param options A list of options for decision-making.
206+
*/
207+
public void decideAllAsync(@Nonnull OptimizelyDecisionsCallback callback,
208+
@Nonnull List<OptimizelyDecideOption> options) {
209+
optimizely.decideAllAsync(copy(), callback, options);
210+
}
211+
212+
/**
213+
* Returns decision results asynchronously for all active flag keys.
214+
*
215+
* @param callback A callback to invoke when decisions are available.
216+
*/
217+
public void decideAllAsync(@Nonnull OptimizelyDecisionsCallback callback) {
218+
decideAllAsync(callback, Collections.emptyList());
219+
}
220+
221+
}

0 commit comments

Comments
 (0)