Skip to content

Commit f2228fd

Browse files
mnoman09thomaszurkan-optimizely
authored andcommitted
Feature - Initialization Sync, Async (#167)
* Features: Created two new APIs to simplify the initialization process for mobile: - initializeAsync - initializeSync Also Added three unit tests in OptimizelyManager: -InitializeAsync -InitializeSync -InitializeSyncWithEmptyDataFile * Changes made to catch null pointer exception if given R.raw path of data file is null added assertnull for optimizelyStartListener in initializeAsync test that on completion it should be null * Added Empty datafile to pass unit test of check sync with empty datafile * Changes made to download file on initializing optimizely manager * - Renamed functions of inititalize sync and async - Refactored code to look more like ios Initialize methods (Except reading files in SDK instead of asking user to do that) * Removed unused functions and test cases Added comments did code refactoring * Removed initialize just from cache method. * - Changes made for asynchronus initialize to take downloaded file first than cache file - Added new test cases for empty datafile - refactored code to get datafile from cache if not available than from Raw data file * Commented unused loaddatafile variable * Removed unused function of loaddatafilefromcachetask * 1) Added boolean variable in initialized method to check if update using download should take place after initialization 2)updated comment in datafileloader.java 3) updated testcases 4) handled if datafile is null * 1) Added boolean variable in initialized method to check if update using download should take place after initialization (#11) 2)updated comment in datafileloader.java 3) updated testcases 4) handled if datafile is null * Updated Init test cases and resolved datafile issue of not found resource id * Bug fix of esspresso test of test app not running * Updated test cases and resolved bug of Raw id not found on release (#14) * Updated Init test cases and resolved datafile issue of not found resource id * Bug fix of esspresso test of test app not running * Updated comments and Allowed null resource ID in Initialize(Context,Integer,OptimizelyStartListener)
1 parent cd7a387 commit f2228fd

File tree

10 files changed

+193
-163
lines changed

10 files changed

+193
-163
lines changed

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

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import java.util.concurrent.TimeUnit;
4949

5050
import static junit.framework.Assert.assertEquals;
51+
import static junit.framework.Assert.assertFalse;
5152
import static junit.framework.Assert.assertNotNull;
5253
import static junit.framework.Assert.assertNull;
5354
import static junit.framework.Assert.assertTrue;
@@ -93,23 +94,6 @@ public void setup() {
9394
eventHandler, null);
9495
}
9596

96-
@SuppressWarnings("WrongConstant")
97-
@Test
98-
public void initialize() {
99-
OptimizelyStartListener startListener = mock(OptimizelyStartListener.class);
100-
Context context = mock(Context.class);
101-
Context appContext = mock(Context.class);
102-
when(context.getApplicationContext()).thenReturn(appContext);
103-
when(appContext.getPackageName()).thenReturn("com.optly");
104-
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
105-
106-
optimizelyManager.initialize(appContext, startListener);
107-
108-
assertNotNull(optimizelyManager.getOptimizelyStartListener());
109-
assertNotNull(optimizelyManager.getDatafileHandler());
110-
111-
}
112-
11397
@Test
11498
public void initializeIntUseForcedVariation() {
11599
optimizelyManager.initialize(InstrumentationRegistry.getTargetContext(), R.raw.datafile);
@@ -132,6 +116,79 @@ public void initializeInt() {
132116
assertNotNull(optimizelyManager.getOptimizely());
133117
assertNotNull(optimizelyManager.getDatafileHandler());
134118

119+
}
120+
@Test
121+
public void initializeSync() {
122+
/*
123+
* Scenario#1: when datafile is not Empty
124+
* Scenario#2: when datafile is Empty
125+
*/
126+
optimizelyManager.initialize(InstrumentationRegistry.getTargetContext(), R.raw.datafile);
127+
128+
assertEquals(optimizelyManager.isDatafileCached(InstrumentationRegistry.getTargetContext()), false);
129+
130+
assertEquals(OptimizelyManager.getDatafileUrl("1"), "https://cdn.optimizely.com/public/1/datafile_v3.json" );
131+
132+
assertNotNull(optimizelyManager.getOptimizely());
133+
assertNotNull(optimizelyManager.getDatafileHandler());
134+
135+
optimizelyManager.initialize(InstrumentationRegistry.getTargetContext(),(Integer) null);
136+
verify(logger).error(eq("Invalid datafile resource ID."));
137+
}
138+
@Test
139+
public void initializeSyncWithEmptyDatafile() {
140+
//for this case to pass empty the data file or enter any garbage data given on R.raw.emptydatafile this path
141+
Context context = mock(Context.class);
142+
Context appContext = mock(Context.class);
143+
when(context.getApplicationContext()).thenReturn(appContext);
144+
when(appContext.getPackageName()).thenReturn("com.optly");
145+
optimizelyManager.initialize(InstrumentationRegistry.getTargetContext(), R.raw.emptydatafile);
146+
verify(logger).error(eq("Unable to parse compiled data file"), any(ConfigParseException.class));
147+
assertFalse(optimizelyManager.getOptimizely().isValid());
148+
}
149+
@Test
150+
public void getEmptyDatafile() {
151+
//for this case to pass empty the data file or enter any garbage data given on R.raw.emptydatafile this path
152+
Context context = mock(Context.class);
153+
Context appContext = mock(Context.class);
154+
when(context.getApplicationContext()).thenReturn(appContext);
155+
when(appContext.getPackageName()).thenReturn("com.optly");
156+
157+
String datafile= optimizelyManager.getDatafile(InstrumentationRegistry.getTargetContext(), R.raw.emptydatafile);
158+
assertNotNull(datafile,"");
159+
}
160+
@Test
161+
public void getDatafile() {
162+
/*
163+
* Scenario#1: when datafile is Cached
164+
* Scenario#2: when datafile is not cached and raw datafile is not empty
165+
*/
166+
assertEquals(optimizelyManager.isDatafileCached(InstrumentationRegistry.getTargetContext()), false);
167+
String datafile = optimizelyManager.getDatafile(InstrumentationRegistry.getTargetContext(), R.raw.datafile);
168+
assertEquals(OptimizelyManager.getDatafileUrl("1"), "https://cdn.optimizely.com/public/1/datafile_v3.json" );
169+
assertNotNull(datafile);
170+
assertNotNull(optimizelyManager.getDatafileHandler());
171+
}
172+
@Test
173+
public void initializeAsync() {
174+
/*
175+
* Scenario#1: when datafile is not Empty
176+
* Scenario#2: when datafile is Empty
177+
*/
178+
optimizelyManager.initialize(InstrumentationRegistry.getContext(), R.raw.datafile, new OptimizelyStartListener() {
179+
@Override
180+
public void onStart(OptimizelyClient optimizely) {
181+
assertNotNull(optimizelyManager.getOptimizely());
182+
assertNotNull(optimizelyManager.getDatafileHandler());
183+
assertNull(optimizelyManager.getOptimizelyStartListener());
184+
}
185+
});
186+
187+
assertEquals(optimizelyManager.isDatafileCached(InstrumentationRegistry.getTargetContext()), false);
188+
189+
assertEquals(OptimizelyManager.getDatafileUrl("1"), "https://cdn.optimizely.com/public/1/datafile_v3.json" );
190+
191+
135192
}
136193
@Test
137194
public void initializeWithEmptyDatafile() {
@@ -169,7 +226,7 @@ public void initializeWithNullDatafile() {
169226
String emptyString = null;
170227

171228
optimizelyManager.initialize(context, emptyString);
172-
verify(logger).error(eq("Unable to parse compiled data file"), any(ConfigParseException.class));
229+
verify(logger).error(eq("Invalid datafile"));
173230
}
174231

175232
@Test
@@ -182,7 +239,7 @@ public void load() {
182239
String emptyString = null;
183240

184241
optimizelyManager.initialize(context, emptyString);
185-
verify(logger).error(eq("Unable to parse compiled data file"), any(ConfigParseException.class));
242+
verify(logger).error(eq("Invalid datafile"));
186243
}
187244

188245
@Test

android-sdk/src/debug/res/raw/emptydatafile

Whitespace-only changes.

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package com.optimizely.ab.android.sdk;
1818

19-
import android.app.Activity;
19+
import android.content.Context;
2020
import android.support.annotation.NonNull;
2121
import android.support.annotation.Nullable;
2222

@@ -29,15 +29,14 @@
2929

3030
import org.slf4j.Logger;
3131

32-
import java.util.Collections;
3332
import java.util.HashMap;
3433
import java.util.Map;
3534

3635
/**
3736
* Wraps {@link Optimizely} instances
3837
*
3938
* This proxy ensures that the Android SDK will not crash if the inner Optimizely SDK
40-
* failed to start. When Optimizely fails to start via {@link OptimizelyManager#initialize(Activity, OptimizelyStartListener)}
39+
* failed to start. When Optimizely fails to start via {@link OptimizelyManager#initialize(Context,Integer, OptimizelyStartListener)}
4140
* there will be no cached instance returned from {@link OptimizelyManager#getOptimizely()}. By accessing
4241
* Optimizely through this interface checking for null is not required. If Optimizely is null warnings
4342
* will be logged.

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

Lines changed: 70 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -132,73 +132,97 @@ private void notifyStartListener() {
132132
* @return an {@link OptimizelyClient} instance
133133
*/
134134
public OptimizelyClient initialize(@NonNull Context context, @NonNull String datafile) {
135+
initialize(context, datafile,true);
136+
return optimizelyClient;
137+
}
138+
139+
/**
140+
* Initialize Optimizely Synchronously using the datafile passed in while downloading the latest datafile in the background from the CDN to cache.
141+
* It should be noted that even though it initiates a download of the datafile to cache, this method does not use that cached datafile.
142+
* You can always test if a datafile exists in cache with {@link #isDatafileCached(Context)}.
143+
* <p>
144+
* Instantiates and returns an {@link OptimizelyClient} instance. It will also cache the instance
145+
* for future lookups via getClient
146+
*
147+
* @param context any {@link Context} instance
148+
* @param datafile the datafile used to initialize the OptimizelyClient.
149+
* @param downloadToCache to check if datafile should get updated in cache after initialization.
150+
* @return an {@link OptimizelyClient} instance
151+
*/
152+
protected OptimizelyClient initialize(@NonNull Context context,@Nullable String datafile,boolean downloadToCache) {
135153
if (!isAndroidVersionSupported()) {
136154
return optimizelyClient;
137155
}
138-
139156
try {
140-
optimizelyClient = buildOptimizely(context, datafile);
157+
if(datafile!=null)
158+
optimizelyClient = buildOptimizely(context, datafile);
159+
else
160+
logger.error("Invalid datafile");
141161
} catch (ConfigParseException e) {
142162
logger.error("Unable to parse compiled data file", e);
143163
} catch (Exception e) {
144164
logger.error("Unable to build OptimizelyClient instance", e);
145165
} catch (Error e) {
146166
logger.error("Unable to build OptimizelyClient instance", e);
147167
}
148-
149-
datafileHandler.downloadDatafile(context, projectId, null);
168+
if(downloadToCache){
169+
datafileHandler.downloadDatafile(context, projectId, null);
170+
}
150171

151172
return optimizelyClient;
152173
}
153174

154175
/**
155176
* Initialize Optimizely Synchronously by loading the resource, use it to initialize Optimizely,
156177
* and downloading the latest datafile from the CDN in the background to cache.
157-
* It should be noted that even though it initiates a download of the datafile to cache, this method does not use that cached datafile.
158-
* You can always test if a datafile exists in cache with {@link #isDatafileCached(Context)}.
159178
* <p>
160-
* Instantiates and returns an {@link OptimizelyClient} instance. Will also cache the instance
179+
* Instantiates and returns an {@link OptimizelyClient} instance using the datafile cached on disk
180+
* if not available then it will expect that raw data file should exist on given id.
181+
* and initialize using raw file. Will also cache the instance
161182
* for future lookups via getClient. The datafile should be stored in res/raw.
162183
*
163184
* @param context any {@link Context} instance
164185
* @param datafileRes the R id that the data file is located under.
165186
* @return an {@link OptimizelyClient} instance
166187
*/
167188
@NonNull
168-
public OptimizelyClient initialize(@NonNull Context context, @RawRes int datafileRes) {
189+
public OptimizelyClient initialize(@NonNull Context context, @RawRes Integer datafileRes) {
169190
try {
170-
String datafile = loadRawResource(context, datafileRes);
171-
return initialize(context, datafile);
172-
} catch (IOException e) {
173-
logger.error("Unable to load compiled data file", e);
191+
192+
String datafile;
193+
datafile = getDatafile(context, datafileRes);
194+
optimizelyClient = initialize(context, datafile, true);
195+
}catch (NullPointerException e){
196+
logger.error("Unable to find compiled data file in raw resource",e);
174197
}
175198

176199
// return dummy client if not able to initialize a valid one
177200
return optimizelyClient;
178201
}
179202

180-
/**
181-
* Initialize Optimizely Synchronously. This one uses the cached datafile and expects it to exist.
182-
* You can use {@link #isDatafileCached(Context)} to see if the datafile exists in cache prior to this call.
183-
* <p>
184-
* Instantiates and returns an {@link OptimizelyClient} instance using the datafile cached on disk
185-
* if not available then it will return a dummy instance.
186-
*
187-
* @param context any {@link Context} instance
188-
* @return an {@link OptimizelyClient} instance
203+
/** This function will first try to get datafile from Cache, if file is not cached yet
204+
* than it will read from Raw file
205+
* @param context
206+
* @param datafileRes
207+
* @return datafile
189208
*/
190-
public OptimizelyClient initialize(@NonNull Context context) {
191-
192-
String datafile = datafileHandler.loadSavedDatafile(context, projectId);
193-
194-
if (datafile != null) {
195-
return initialize(context, datafile);
196-
}
197-
198-
// return dummy client if not able to initialize a valid one
199-
return optimizelyClient;
209+
public String getDatafile(Context context,@RawRes Integer datafileRes){
210+
try {
211+
if (isDatafileCached(context)) {
212+
return datafileHandler.loadSavedDatafile(context, projectId);
213+
} else if (datafileRes!=null) {
214+
return loadRawResource(context, datafileRes);
215+
}else{
216+
logger.error("Invalid datafile resource ID.");
217+
return null;
218+
}
219+
} catch (IOException e) {
220+
logger.error("Unable to load compiled data file", e);
221+
}catch (NullPointerException e){
222+
logger.error("Unable to find compiled data file in raw resource",e);
223+
}
224+
return null;
200225
}
201-
202226
/**
203227
* Starts Optimizely asynchronously
204228
* <p>
@@ -207,53 +231,39 @@ public OptimizelyClient initialize(@NonNull Context context) {
207231
* once. If there is a cached datafile the returned instance will be built from it. The cached
208232
* datafile will be updated from network if it is different from the cache. If there is no
209233
* cached datafile the returned instance will always be built from the remote datafile.
210-
*
211-
* @param activity an Activity, used to automatically unbind {@link DatafileService}
212-
* @param optimizelyStartListener callback that {@link OptimizelyClient} instances are sent to.
213-
*/
214-
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
215-
public void initialize(@NonNull Activity activity, @NonNull OptimizelyStartListener optimizelyStartListener) {
216-
if (!isAndroidVersionSupported()) {
217-
return;
218-
}
219-
activity.getApplication().registerActivityLifecycleCallbacks(new OptlyActivityLifecycleCallbacks(this));
220-
initialize(activity.getApplicationContext(), optimizelyStartListener);
221-
}
222-
223-
/**
224234
* This method does the same thing except it can be used with a generic {@link Context}.
225235
* @param context any type of context instance
236+
* @param datafileRes Null is allowed here if user don't want to put datafile in res. Null handling is done in {@link #getDatafile(Context,Integer)}
226237
* @param optimizelyStartListener callback that {@link OptimizelyClient} instances are sent to.
227-
* @see #initialize(Activity, OptimizelyStartListener)
238+
* @see #initialize(Context, Integer, OptimizelyStartListener)
228239
*/
229-
public void initialize(@NonNull Context context, @NonNull OptimizelyStartListener optimizelyStartListener) {
240+
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
241+
public void initialize(@NonNull final Context context, @RawRes final Integer datafileRes, @NonNull OptimizelyStartListener optimizelyStartListener) {
230242
if (!isAndroidVersionSupported()) {
231243
return;
232244
}
233-
this.optimizelyStartListener = optimizelyStartListener;
234-
datafileHandler.downloadDatafile(context, projectId, getDatafileLoadedListener(context));
245+
setOptimizelyStartListener(optimizelyStartListener);
246+
datafileHandler.downloadDatafile(context, projectId,getDatafileLoadedListener(context,datafileRes));
235247
}
236248

237-
DatafileLoadedListener getDatafileLoadedListener(final Context context) {
249+
DatafileLoadedListener getDatafileLoadedListener(final Context context, @RawRes final Integer datafileRes) {
238250
return new DatafileLoadedListener() {
239251
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
240252
@Override
241253
public void onDatafileLoaded(@Nullable String datafile) {
242254
// App is being used, i.e. in the foreground
243-
if (datafile != null) {
255+
if (datafile != null && !datafile.isEmpty()) {
244256
injectOptimizely(context, userProfileService, datafile);
245257
} else {
246-
// We should always call the callback even with the dummy
247-
// instances. Devs might gate the rest of their app
248-
// based on the loading of Optimizely
258+
//if datafile is null than it should be able to take from cache and if not present
259+
//in Cache than should be able to get from raw data file
260+
optimizelyClient = initialize(context,getDatafile(context,datafileRes),false);
249261
notifyStartListener();
250262
}
251263
}
252264

253265
@Override
254-
public void onStop(Context context) {
255-
256-
}
266+
public void onStop(Context context) {}
257267
};
258268
}
259269

@@ -281,11 +291,11 @@ public void stop(@NonNull Context context) {
281291
/**
282292
* Gets a cached Optimizely instance
283293
* <p>
284-
* If {@link #initialize(Activity, OptimizelyStartListener)} or {@link #initialize(Context, OptimizelyStartListener)}
294+
* If {@link #initialize(Context,Integer, OptimizelyStartListener)} or {@link #initialize(Context, Integer)}
285295
* has not been called yet the returned {@link OptimizelyClient} instance will be a dummy instance
286296
* that logs warnings in order to prevent {@link NullPointerException}.
287297
* <p>
288-
* Using {@link #initialize(Activity, OptimizelyStartListener)} or {@link #initialize(Context, OptimizelyStartListener)}
298+
* Using {@link #initialize(Context,Integer, OptimizelyStartListener)} or {@link #initialize(Context, Integer)}
289299
* will update the cached instance with a new {@link OptimizelyClient} built from a cached local
290300
* datafile on disk or a remote datafile on the CDN.
291301
*
@@ -527,7 +537,6 @@ public static class Builder {
527537
@Nullable private EventHandler eventHandler = null;
528538
@Nullable private ErrorHandler errorHandler = null;
529539
@Nullable private UserProfileService userProfileService = null;
530-
531540
Builder(@NonNull String projectId) {
532541
this.projectId = projectId;
533542
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
/**
2323
* Listens for new instances of {@link OptimizelyClient} that are built after calling
24-
* {@link OptimizelyManager#initialize(Activity, OptimizelyStartListener)} or {@link OptimizelyManager#initialize(Context, OptimizelyStartListener)}
24+
*{@link OptimizelyManager#initialize(Context, Integer,OptimizelyStartListener)}
2525
*/
2626
public interface OptimizelyStartListener {
2727
/**

0 commit comments

Comments
 (0)