Skip to content

Commit 3d0fe42

Browse files
(chore): Use the project config manager in java sdk with android. Also add listener. (#276)
* first attempt at using the project config manager with android * add datafile change notification * fix unit tests * update to latest build versions * trying to fix travis * try to get travis working * remove audio from emulator start * incorrect check for currentConfig * fix travis yaml * use 3.2.0 java-sdk * fall back to tools 28.0.3 * cleanup travis yaml * trying to fix travis build * respond to comments, slight refactor. * use datafileloaded as datafile change. deprecate datafile handler interface. * one more slight refactor. remove datafile config from constructor of default datafile handler. allow old handlers to work the same as before using datafile only. fix tests * slight refactor for setConfig * cleanup test-app a little * cleanup from PR comments * removed not from null datafile or empty datafile check (#279) * add a few more null checks
1 parent ba00a81 commit 3d0fe42

File tree

14 files changed

+165
-84
lines changed

14 files changed

+165
-84
lines changed

.travis.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@ env:
66
- ADB_INSTALL_TIMEOUT=5 # minutes
77
- ANDROID_API=28 # api is same as gradle file
88
matrix:
9-
- EMULATOR_API=22
109
- EMULATOR_API=21
10+
- EMULATOR_API=22
1111
android:
1212
components:
1313
- tools
14-
- platform-tools
15-
- tools
14+
- platform-tools-$ANDROID_BUILD_TOOLS
1615
- build-tools-$ANDROID_BUILD_TOOLS
1716
- android-$ANDROID_API
1817
- android-$EMULATOR_API
@@ -35,7 +34,7 @@ cache:
3534
- $HOME/.gradle/caches/
3635
- $HOME/.gradle/wrapper/
3736
before_install:
38-
- yes | sdkmanager "platforms;android-28"
37+
#- yes | sdkmanager "platforms;android-$ANDROID_API"
3938
before_script:
4039
- echo $TRAVIS_BRANCH
4140
- echo $TRAVIS_TAG

android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyClientTest.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,9 @@ public OptimizelyClientTest(int datafileVersion,String datafile){
115115
eventHandler = spy(DefaultEventHandler.getInstance(InstrumentationRegistry.getTargetContext()));
116116
optimizely = Optimizely.builder(datafile, eventHandler).build();
117117
if(datafileVersion==3) {
118-
when(bucketer.bucket(optimizely.getProjectConfig().getExperiments().get(0), GENERIC_USER_ID)).thenReturn(optimizely.getProjectConfig().getExperiments().get(0).getVariations().get(0));
118+
when(bucketer.bucket(optimizely.getProjectConfig().getExperiments().get(0), GENERIC_USER_ID, optimizely.getProjectConfig())).thenReturn(optimizely.getProjectConfig().getExperiments().get(0).getVariations().get(0));
119119
} else {
120-
when(bucketer.bucket(optimizely.getProjectConfig().getExperimentKeyMapping().get(FEATURE_MULTI_VARIATE_EXPERIMENT_KEY), GENERIC_USER_ID)).thenReturn(optimizely.getProjectConfig().getExperimentKeyMapping().get(FEATURE_MULTI_VARIATE_EXPERIMENT_KEY).getVariations().get(1));
120+
when(bucketer.bucket(optimizely.getProjectConfig().getExperimentKeyMapping().get(FEATURE_MULTI_VARIATE_EXPERIMENT_KEY), GENERIC_USER_ID, optimizely.getProjectConfig())).thenReturn(optimizely.getProjectConfig().getExperimentKeyMapping().get(FEATURE_MULTI_VARIATE_EXPERIMENT_KEY).getVariations().get(1));
121121
}
122122
spyOnConfig();
123123
} catch (Exception configException) {
@@ -356,7 +356,7 @@ public void testGoodActivationBucketingId() {
356356
Experiment experiment = optimizelyClient.getProjectConfig().getExperimentKeyMapping().get(FEATURE_ANDROID_EXPERIMENT_KEY);
357357
attributes.put(BUCKETING_ATTRIBUTE, bucketingId);
358358
Variation v = optimizelyClient.activate(FEATURE_ANDROID_EXPERIMENT_KEY, GENERIC_USER_ID, attributes);
359-
verify(bucketer).bucket( experiment, bucketingId);
359+
verify(bucketer).bucket(experiment, bucketingId, optimizely.getProjectConfig());
360360
}
361361

362362
@Test
@@ -881,7 +881,7 @@ public void testGoodGetVariationBucketingId() {
881881
Map<String, String> attributes = new HashMap<>();
882882
attributes.put(BUCKETING_ATTRIBUTE, bucketingId);
883883
Variation v = optimizelyClient.getVariation("android_experiment_key", "userId", attributes);
884-
verify(bucketer).bucket(experiment, bucketingId);
884+
verify(bucketer).bucket(experiment, bucketingId, optimizely.getProjectConfig());
885885
}
886886

887887
@Test
@@ -1027,9 +1027,9 @@ public void testGoodGetProjectConfigForced() {
10271027
logger);
10281028
ProjectConfig config = optimizelyClient.getProjectConfig();
10291029
assertNotNull(config);
1030-
assertTrue(config.setForcedVariation(FEATURE_ANDROID_EXPERIMENT_KEY, GENERIC_USER_ID, "var_1"));
1031-
assertEquals(config.getForcedVariation(FEATURE_ANDROID_EXPERIMENT_KEY, GENERIC_USER_ID), config.getExperimentKeyMapping().get(FEATURE_ANDROID_EXPERIMENT_KEY).getVariations().get(0));
1032-
assertTrue(config.setForcedVariation(FEATURE_ANDROID_EXPERIMENT_KEY, GENERIC_USER_ID, null));
1030+
assertTrue(optimizelyClient.setForcedVariation(FEATURE_ANDROID_EXPERIMENT_KEY, GENERIC_USER_ID, "var_1"));
1031+
assertEquals(optimizelyClient.getForcedVariation(FEATURE_ANDROID_EXPERIMENT_KEY, GENERIC_USER_ID), config.getExperimentKeyMapping().get(FEATURE_ANDROID_EXPERIMENT_KEY).getVariations().get(0));
1032+
assertTrue(optimizelyClient.setForcedVariation(FEATURE_ANDROID_EXPERIMENT_KEY, GENERIC_USER_ID, null));
10331033
}
10341034

10351035
@Test

android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyManagerBuilderTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.optimizely.ab.OptimizelyRuntimeException;
3030
import com.optimizely.ab.android.datafile_handler.DefaultDatafileHandler;
3131
import com.optimizely.ab.android.event_handler.DefaultEventHandler;
32+
import com.optimizely.ab.android.shared.DatafileConfig;
3233
import com.optimizely.ab.android.user_profile.DefaultUserProfileService;
3334
import com.optimizely.ab.config.Experiment;
3435
import com.optimizely.ab.config.ProjectConfig;

android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyManagerTest.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
import com.optimizely.ab.android.shared.ServiceScheduler;
3838
import com.optimizely.ab.android.user_profile.DefaultUserProfileService;
3939
import com.optimizely.ab.bucketing.UserProfileService;
40+
import com.optimizely.ab.config.DatafileProjectConfig;
41+
import com.optimizely.ab.config.ProjectConfig;
4042
import com.optimizely.ab.config.Variation;
4143
import com.optimizely.ab.event.EventHandler;
4244

@@ -74,6 +76,7 @@ public class OptimizelyManagerTest {
7476
private ListeningExecutorService executor;
7577
private Logger logger;
7678
private OptimizelyManager optimizelyManager;
79+
private DefaultDatafileHandler defaultDatafileHandler;
7780

7881
private String minDatafile = "{\n" +
7982
"experiments: [ ],\n" +
@@ -88,19 +91,24 @@ public class OptimizelyManagerTest {
8891
"}";
8992

9093
@Before
91-
public void setup() {
94+
public void setup() throws Exception {
9295
logger = mock(Logger.class);
9396
executor = MoreExecutors.newDirectExecutorService();
94-
DatafileHandler datafileHandler = mock(DefaultDatafileHandler.class);
97+
defaultDatafileHandler = mock(DefaultDatafileHandler.class);
9598
EventHandler eventHandler = mock(DefaultEventHandler.class);
96-
optimizelyManager = new OptimizelyManager(testProjectId, null, null, logger, 3600L, datafileHandler, null, 3600L,
99+
optimizelyManager = new OptimizelyManager(testProjectId, null, null, logger, 3600L, defaultDatafileHandler, null, 3600L,
97100
eventHandler, null);
101+
String datafile = optimizelyManager.getDatafile(InstrumentationRegistry.getTargetContext(), R.raw.datafile);
102+
ProjectConfig config = new DatafileProjectConfig.Builder().withDatafile(datafile).build();
103+
104+
when(defaultDatafileHandler.getConfig()).thenReturn(config);
98105
}
99106

100107
@Test
101108
public void initializeIntUseForcedVariation() {
102109
optimizelyManager.initialize(InstrumentationRegistry.getTargetContext(), R.raw.datafile);
103110

111+
104112
assertTrue(optimizelyManager.getOptimizely().setForcedVariation("android_experiment_key", "1", "var_1"));
105113
Variation variation = optimizelyManager.getOptimizely().getForcedVariation("android_experiment_key", "1");
106114
assertEquals(variation.getKey(), "var_1");
@@ -116,7 +124,7 @@ public void initializeInt() {
116124

117125
assertEquals(optimizelyManager.getDatafileUrl(), "https://cdn.optimizely.com/json/7595190003.json" );
118126

119-
verify(optimizelyManager.getDatafileHandler()).startBackgroundUpdates(eq(InstrumentationRegistry.getTargetContext()), eq(new DatafileConfig(testProjectId, null)), eq(3600L));
127+
verify(optimizelyManager.getDatafileHandler()).startBackgroundUpdates(eq(InstrumentationRegistry.getTargetContext()), eq(new DatafileConfig(testProjectId, null)), eq(3600L), any(DatafileLoadedListener.class));
120128
assertNotNull(optimizelyManager.getOptimizely());
121129
assertNotNull(optimizelyManager.getDatafileHandler());
122130

@@ -169,6 +177,7 @@ public void initializeSyncWithEmptyDatafile() {
169177
Context appContext = mock(Context.class);
170178
when(context.getApplicationContext()).thenReturn(appContext);
171179
when(appContext.getPackageName()).thenReturn("com.optly");
180+
when(defaultDatafileHandler.getConfig()).thenReturn(null);
172181
optimizelyManager.initialize(InstrumentationRegistry.getTargetContext(), R.raw.emptydatafile);
173182
assertFalse(optimizelyManager.getOptimizely().isValid());
174183
}
@@ -227,7 +236,7 @@ public void onStart(OptimizelyClient optimizely) {
227236
};
228237
optimizelyManager.initialize(InstrumentationRegistry.getContext(), R.raw.datafile, listener);
229238

230-
verify(optimizelyManager.getDatafileHandler()).startBackgroundUpdates(any(Context.class), eq(new DatafileConfig(testProjectId, testSdkKey)), eq(3600L));
239+
verify(optimizelyManager.getDatafileHandler()).startBackgroundUpdates(any(Context.class), eq(new DatafileConfig(testProjectId, testSdkKey)), eq(3600L), any(DatafileLoadedListener.class));
231240

232241

233242
assertEquals(optimizelyManager.isDatafileCached(InstrumentationRegistry.getTargetContext()), false);
@@ -261,6 +270,7 @@ public void initializeWithEmptyDatafile() {
261270
Context appContext = mock(Context.class);
262271
when(context.getApplicationContext()).thenReturn(appContext);
263272
when(appContext.getPackageName()).thenReturn("com.optly");
273+
when(defaultDatafileHandler.getConfig()).thenReturn(null);
264274

265275
String emptyString = "";
266276

@@ -274,6 +284,7 @@ public void initializeWithMalformedDatafile() {
274284
Context appContext = mock(Context.class);
275285
when(context.getApplicationContext()).thenReturn(appContext);
276286
when(appContext.getPackageName()).thenReturn("com.optly");
287+
when(defaultDatafileHandler.getConfig()).thenReturn(null);
277288

278289
String emptyString = "malformed data";
279290

@@ -347,7 +358,7 @@ public void injectOptimizely() {
347358

348359
verify(logger).info("Sending Optimizely instance to listener");
349360
verify(startListener).onStart(any(OptimizelyClient.class));
350-
verify(optimizelyManager.getDatafileHandler()).startBackgroundUpdates(eq(context), eq(new DatafileConfig(testProjectId, null)), eq(3600L));
361+
verify(optimizelyManager.getDatafileHandler()).startBackgroundUpdates(eq(context), eq(new DatafileConfig(testProjectId, null)), eq(3600L), any(DatafileLoadedListener.class));
351362

352363
}
353364

@@ -366,7 +377,7 @@ public void injectOptimizelyWithDatafileLisener() {
366377
fail("Timed out");
367378
}
368379

369-
verify(optimizelyManager.getDatafileHandler()).startBackgroundUpdates(eq(context), eq(new DatafileConfig(testProjectId, null)), eq(3600L));
380+
verify(optimizelyManager.getDatafileHandler()).startBackgroundUpdates(eq(context), eq(new DatafileConfig(testProjectId, null)), eq(3600L), any(DatafileLoadedListener.class));
370381
verify(logger).info("Sending Optimizely instance to listener");
371382
verify(startListener).onStart(any(OptimizelyClient.class));
372383
}
@@ -435,6 +446,7 @@ public void injectOptimizelyHandlesInvalidDatafile() {
435446
ArgumentCaptor<DefaultUserProfileService.StartCallback> callbackArgumentCaptor =
436447
ArgumentCaptor.forClass(DefaultUserProfileService.StartCallback.class);
437448

449+
when(defaultDatafileHandler.getConfig()).thenReturn(null);
438450
optimizelyManager.setOptimizelyStartListener(null);
439451
optimizelyManager.injectOptimizely(context, userProfileService, "{}");
440452
try {

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

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
import com.optimizely.ab.error.ErrorHandler;
4545
import com.optimizely.ab.event.EventHandler;
4646
import com.optimizely.ab.event.internal.payload.EventBatch;
47+
import com.optimizely.ab.notification.NotificationCenter;
48+
import com.optimizely.ab.notification.UpdateConfigNotification;
4749

4850
import org.slf4j.Logger;
4951
import org.slf4j.LoggerFactory;
@@ -184,11 +186,7 @@ protected OptimizelyClient initialize(@NonNull Context context, @Nullable String
184186
defaultUserProfileService.start();
185187
}
186188
optimizelyClient = buildOptimizely(context, datafile);
187-
188-
if (datafileDownloadInterval > 0 && datafileHandler != null) {
189-
datafileHandler.startBackgroundUpdates(context, datafileConfig, datafileDownloadInterval);
190-
}
191-
189+
startDatafileHandler(context);
192190
}
193191
else {
194192
logger.error("Invalid datafile");
@@ -343,9 +341,6 @@ public void onDatafileLoaded(@Nullable String datafile) {
343341
injectOptimizely(context, userProfileService, getDatafile(context,datafileRes));
344342
}
345343
}
346-
347-
@Override
348-
public void onStop(Context context) {}
349344
};
350345
}
351346

@@ -434,17 +429,32 @@ public DatafileHandler getDatafileHandler() {
434429
return datafileHandler;
435430
}
436431

432+
private void startDatafileHandler(Context context) {
433+
if (datafileDownloadInterval <= 0) {
434+
logger.debug("Invalid download interval, ignoring background updates.");
435+
return;
436+
}
437+
438+
datafileHandler.startBackgroundUpdates(context, datafileConfig, datafileDownloadInterval, datafile1 -> {
439+
NotificationCenter notificationCenter = getOptimizely().getNotificationCenter();
440+
if (notificationCenter == null) {
441+
logger.debug("NotificationCenter null, not sending notification");
442+
return;
443+
}
444+
notificationCenter.send(new UpdateConfigNotification());
445+
});
446+
}
447+
437448
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
438449
void injectOptimizely(@NonNull final Context context, final @NonNull UserProfileService userProfileService,
439450
@NonNull final String datafile) {
440451

441-
if (datafileDownloadInterval > 0 && datafileHandler != null) {
442-
datafileHandler.startBackgroundUpdates(context, datafileConfig, datafileDownloadInterval);
443-
}
444452
try {
445453
optimizelyClient = buildOptimizely(context, datafile);
446454
optimizelyClient.setDefaultAttributes(OptimizelyDefaultAttributes.buildDefaultAttributesMap(context, logger));
447455

456+
startDatafileHandler(context);
457+
448458
if (userProfileService instanceof DefaultUserProfileService) {
449459
((DefaultUserProfileService) userProfileService).startInBackground(new DefaultUserProfileService.StartCallback() {
450460
@Override
@@ -483,20 +493,26 @@ private OptimizelyClient buildOptimizely(@NonNull Context context, @NonNull Stri
483493

484494
EventBatch.ClientEngine clientEngine = OptimizelyClientEngine.getClientEngineFromContext(context);
485495

486-
Optimizely.Builder builder = Optimizely.builder(datafile, eventHandler)
487-
.withClientEngine(clientEngine)
496+
Optimizely.Builder builder = Optimizely.builder();
497+
498+
if (datafileHandler instanceof DefaultDatafileHandler) {
499+
DefaultDatafileHandler handler = (DefaultDatafileHandler)datafileHandler;
500+
handler.setDatafile(datafile);
501+
builder.withConfigManager(handler);
502+
builder.withEventHandler(eventHandler);
503+
}
504+
else {
505+
builder = Optimizely.builder(datafile, eventHandler);
506+
}
507+
508+
builder.withClientEngine(clientEngine)
488509
.withClientVersion(BuildConfig.CLIENT_VERSION);
510+
489511
if (errorHandler != null) {
490512
builder.withErrorHandler(errorHandler);
491513
}
492-
if (userProfileService != null) {
493-
builder.withUserProfileService(userProfileService);
494-
}
495-
else {
496-
// the builder creates the default user profile service. So, this should never happen.
497-
userProfileService = DefaultUserProfileService.newInstance(datafileConfig.getKey(), context);
498-
builder.withUserProfileService(userProfileService);
499-
}
514+
515+
builder.withUserProfileService(userProfileService);
500516

501517
Optimizely optimizely = builder.build();
502518
return new OptimizelyClient(optimizely, LoggerFactory.getLogger(OptimizelyClient.class));
@@ -752,6 +768,10 @@ public OptimizelyManager build(Context context) {
752768
}
753769
}
754770

771+
if (datafileConfig == null) {
772+
datafileConfig = new DatafileConfig(projectId, sdkKey);
773+
}
774+
755775
if (datafileHandler == null) {
756776
datafileHandler = new DefaultDatafileHandler();
757777
}

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ ext {
5353
build_tools_version = "28.0.3"
5454
min_sdk_version = 14
5555
target_sdk_version = 28
56-
java_core_ver = "3.1.0"
56+
java_core_ver = "3.2.0"
5757
android_logger_ver = "1.3.6"
5858
jacksonversion= "2.9.8"
5959
support_annotations_ver = "24.2.1"

datafile-handler/src/androidTest/java/com/optimizely/ab/android/datafile_handler/DefaultDatafileHandlerTest.java

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,6 @@ public void onDatafileLoaded(@Nullable String dataFile) {
9898
assertNull(dataFile);
9999
}
100100

101-
@Override
102-
public void onStop(Context context) {
103-
104-
}
105101
});
106102

107103
}
@@ -116,11 +112,6 @@ public void testAsyncDownloadWithEnvironment() throws Exception {
116112
public void onDatafileLoaded(@Nullable String dataFile) {
117113
assertNull(dataFile);
118114
}
119-
120-
@Override
121-
public void onStop(Context context) {
122-
123-
}
124115
});
125116

126117
}
@@ -130,7 +121,7 @@ public void testBackgroundWithoutEnvironment() throws Exception {
130121
// Context of the app under test.
131122
Context appContext = InstrumentationRegistry.getTargetContext();
132123

133-
datafileHandler.startBackgroundUpdates(appContext, new DatafileConfig("1", null), 24 * 60 * 60L);
124+
datafileHandler.startBackgroundUpdates(appContext, new DatafileConfig("1", null), 24 * 60 * 60L, null);
134125

135126
assertTrue(true);
136127

@@ -144,7 +135,7 @@ public void testBackgroundWithEnvironment() throws Exception {
144135
// Context of the app under test.
145136
Context appContext = InstrumentationRegistry.getTargetContext();
146137

147-
datafileHandler.startBackgroundUpdates(appContext, new DatafileConfig("1", "2"), 24 * 60 * 60L);
138+
datafileHandler.startBackgroundUpdates(appContext, new DatafileConfig("1", "2"), 24 * 60 * 60L, null);
148139

149140
assertTrue(true);
150141

datafile-handler/src/main/java/com/optimizely/ab/android/datafile_handler/DatafileHandler.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,21 @@
1818

1919
import android.content.Context;
2020

21+
import com.optimizely.ab.config.ProjectConfig;
22+
import com.optimizely.ab.config.ProjectConfigManager;
2123
import com.optimizely.ab.android.shared.DatafileConfig;
2224

25+
import java.util.function.Function;
26+
2327
/**
2428
* DatafileHandler
29+
* @deprecated
30+
* This interface will be replaced by the ProjectConfigManager. If you are implementing this interface moving forward,
31+
* you will also need to implement the {@link ProjectConfigManager} .
2532
* class that is used to interact with the datafile_handler module. This interface can be
2633
* overridden so that the sdk user can provide a override for the default DatafileHandler.
2734
*/
35+
@Deprecated
2836
public interface DatafileHandler {
2937
/**
3038
* Synchronous call to download the datafile.
@@ -50,8 +58,9 @@ public interface DatafileHandler {
5058
* @param context application context for download
5159
* @param datafileConfig DatafileConfig for the datafile
5260
* @param updateInterval frequency of updates in seconds
61+
* @param listener function to call when a new datafile has been detected.
5362
*/
54-
void startBackgroundUpdates(Context context, DatafileConfig datafileConfig, Long updateInterval);
63+
void startBackgroundUpdates(Context context, DatafileConfig datafileConfig, Long updateInterval, DatafileLoadedListener listener);
5564

5665
/**
5766
* Stop the background updates.

datafile-handler/src/main/java/com/optimizely/ab/android/datafile_handler/DatafileLoadedListener.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,4 @@ public interface DatafileLoadedListener {
3535
*
3636
*/
3737
void onDatafileLoaded(@Nullable String dataFile);
38-
39-
/**
40-
* The datafile download stopped for some reason.
41-
*
42-
* @param context application context
43-
*/
44-
void onStop(Context context);
4538
}

0 commit comments

Comments
 (0)