Skip to content

Commit

Permalink
[FSSDK-9918] add returnInMainThread flag for async init (#470)
Browse files Browse the repository at this point in the history
This PR adds a flag (returnInMainThread) to async initialization for controlling to return on completion in main thread or background thread.

- by default, returnInMainThread = true for backward compatibility.
- set returnInMainThread = false to return in a background thread.
  • Loading branch information
jaeopt authored Jan 11, 2024
1 parent f117429 commit 8bea0f1
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
Expand Down Expand Up @@ -48,6 +49,8 @@
import org.mockito.stubbing.Answer;
import org.slf4j.Logger;

import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
Expand All @@ -58,6 +61,7 @@
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
Expand Down Expand Up @@ -359,7 +363,7 @@ public void injectOptimizely() {
UserProfileService userProfileService = mock(UserProfileService.class);
OptimizelyStartListener startListener = mock(OptimizelyStartListener.class);

optimizelyManager.setOptimizelyStartListener(startListener);
optimizelyManager.setOptimizelyStartListener(startListener, true);
optimizelyManager.injectOptimizely(context, userProfileService, minDatafile);
try {
executor.awaitTermination(5, TimeUnit.SECONDS);
Expand Down Expand Up @@ -750,6 +754,72 @@ public void initializeSyncWithResourceDatafileNoCacheWithDefaultParams() {
verify(manager).initialize(eq(context), eq(defaultDatafile), eq(true), eq(false));
}

@Test
public void initializeAsyncCallbackInBackgroundThread() throws InterruptedException {
OptimizelyManager optimizelyManager = OptimizelyManager.builder(testProjectId)
.build(InstrumentationRegistry.getInstrumentation().getTargetContext());

CountDownLatch latch = new CountDownLatch(1);

// by default, async init returns in main thread.
// this parameter should be set to false to overrule it.
boolean returnInMainThread = false;

optimizelyManager.initialize(
InstrumentationRegistry.getInstrumentation().getContext(),
null,
returnInMainThread,
(client) -> {
Log.d("Optly", "[TESTING] " + Thread.currentThread().getName());
try {
assertNotEquals(
"OptimizelyStartListener should be called in a background thread",
"main", Thread.currentThread().getName()
);
latch.countDown();
} catch (AssertionError e) {
// we need catch and silence this assertion error, otherwise it will be caught in OptimizeManager,
// and give a wrong error message. The failure will be detected with the latch timeout below.
}
}
);

boolean completed = latch.await(1, TimeUnit.SECONDS);
if (!completed) {
fail("OptimizelyStartListener thread checking failed");
}
}

@Test
public void initializeAsyncCallbackInMainThread() throws InterruptedException {
OptimizelyManager optimizelyManager = OptimizelyManager.builder(testProjectId)
.build(InstrumentationRegistry.getInstrumentation().getTargetContext());

CountDownLatch latch = new CountDownLatch(1);

optimizelyManager.initialize(
InstrumentationRegistry.getInstrumentation().getContext(),
null,
(client) -> {
Log.d("Optly", "[TESTING] " + Thread.currentThread().getName());
try {
assertEquals(
"OptimizelyStartListener should be called in a background thread",
"main", Thread.currentThread().getName()
);
latch.countDown();
} catch (AssertionError e) {
// we need catch and silence this assertion error, otherwise it will be caught in OptimizeManager,
// and give a wrong error message. The failure will be detected with the latch timeout below.
}
}
);

boolean completed = latch.await(1, TimeUnit.SECONDS);
if (!completed) {
fail("OptimizelyStartListener thread checking failed");
}
}

// Utils

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public class OptimizelyManager {
@Nullable private final String vuid;

@Nullable private OptimizelyStartListener optimizelyStartListener;
private boolean returnInMainThreadFromAsyncInit = true;

@Nullable private final List<OptimizelyDecideOption> defaultDecideOptions;
private String sdkVersion = null;
Expand Down Expand Up @@ -175,8 +176,14 @@ OptimizelyStartListener getOptimizelyStartListener() {
return optimizelyStartListener;
}

void setOptimizelyStartListener(@Nullable OptimizelyStartListener optimizelyStartListener) {
void setOptimizelyStartListener(@Nullable OptimizelyStartListener optimizelyStartListener, boolean returnInMainThread) {
this.optimizelyStartListener = optimizelyStartListener;
this.returnInMainThreadFromAsyncInit = returnInMainThread;
}

void setOptimizelyStartListener(@Nullable OptimizelyStartListener optimizelyStartListener) {
boolean returnInMainThread = true;
setOptimizelyStartListener(optimizelyStartListener, returnInMainThread);
}

private void notifyStartListener() {
Expand Down Expand Up @@ -398,11 +405,27 @@ public void initialize(@NonNull final Context context, @NonNull OptimizelyStartL
* @see #initialize(Context, Integer, OptimizelyStartListener)
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void initialize(@NonNull final Context context, @RawRes final Integer datafileRes, @NonNull OptimizelyStartListener optimizelyStartListener) {
public void initialize(
@NonNull final Context context,
@RawRes final Integer datafileRes,
@NonNull OptimizelyStartListener optimizelyStartListener)
{
// return in main thread after async completed (backward compatible)
boolean returnInMainThread = true;
initialize(context, datafileRes, returnInMainThread, optimizelyStartListener);
}

@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void initialize(
@NonNull final Context context,
@RawRes final Integer datafileRes,
final boolean returnInMainThread,
@NonNull OptimizelyStartListener optimizelyStartListener)
{
if (!isAndroidVersionSupported()) {
return;
}
setOptimizelyStartListener(optimizelyStartListener);
setOptimizelyStartListener(optimizelyStartListener, returnInMainThread);
datafileHandler.downloadDatafile(context, datafileConfig, getDatafileLoadedListener(context,datafileRes));
}

Expand Down Expand Up @@ -553,7 +576,7 @@ public void onStartComplete(UserProfileService userProfileService) {
logger.info("No listener to send Optimizely to");
}
}
});
}, returnInMainThreadFromAsyncInit);
}
else {
if (optimizelyStartListener != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ static public void samplesAll(Context context) {
samplesForDoc_NotificatonListener(context);
samplesForDoc_OlderVersions(context);
samplesForDoc_ForcedDecision(context);
samplesForDoc_ODP(context);
samplesForDoc_ODP_async(context);
samplesForDoc_ODP_sync(context);
}

static public void samplesForDecide(Context context) {
Expand Down Expand Up @@ -859,7 +860,7 @@ static public void samplesForDoc_ForcedDecision(Context context) {
success = user.removeAllForcedDecisions();
}

static public void samplesForDoc_ODP(Context context) {
static public void samplesForDoc_ODP_async(Context context) {
OptimizelyManager optimizelyManager = OptimizelyManager.builder().withSDKKey("VivZyCGPHY369D4z8T9yG").build(context);
optimizelyManager.initialize(context, null, (OptimizelyClient client) -> {
OptimizelyUserContext userContext = client.createUserContext("user_123");
Expand All @@ -871,4 +872,19 @@ static public void samplesForDoc_ODP(Context context) {
});
}

static public void samplesForDoc_ODP_sync(Context context) {
OptimizelyManager optimizelyManager = OptimizelyManager.builder().withSDKKey("VivZyCGPHY369D4z8T9yG").build(context);

boolean returnInMainThread = false;

optimizelyManager.initialize(context, null, returnInMainThread, (OptimizelyClient client) -> {
OptimizelyUserContext userContext = client.createUserContext("user_123");
userContext.fetchQualifiedSegments();

Log.d("Optimizely", "[ODP] segments = " + userContext.getQualifiedSegments());
OptimizelyDecision optDecision = userContext.decide("odp-flag-1");
Log.d("Optimizely", "[ODP] decision = " + optDecision.toString());
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ package com.optimizely.ab.android.test_app
import android.content.Context
import android.content.IntentFilter
import android.net.wifi.WifiManager
import android.os.Parcel
import android.os.Parcelable
import android.util.Log
import com.optimizely.ab.OptimizelyDecisionContext
import com.optimizely.ab.OptimizelyForcedDecision
Expand All @@ -29,7 +27,6 @@ import com.optimizely.ab.android.event_handler.EventRescheduler
import com.optimizely.ab.android.sdk.OptimizelyClient
import com.optimizely.ab.android.sdk.OptimizelyManager
import com.optimizely.ab.bucketing.UserProfileService
import com.optimizely.ab.config.Variation
import com.optimizely.ab.config.parser.JsonParseException
import com.optimizely.ab.error.ErrorHandler
import com.optimizely.ab.error.RaiseExceptionErrorHandler
Expand All @@ -40,12 +37,8 @@ import com.optimizely.ab.notification.DecisionNotification
import com.optimizely.ab.notification.NotificationHandler
import com.optimizely.ab.notification.TrackNotification
import com.optimizely.ab.notification.UpdateConfigNotification
import com.optimizely.ab.optimizelyconfig.OptimizelyConfig
import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption
import com.optimizely.ab.optimizelydecision.OptimizelyDecision
import com.optimizely.ab.optimizelyjson.OptimizelyJSON
import org.slf4j.LoggerFactory
import java.lang.Exception
import java.util.*
import java.util.concurrent.TimeUnit

Expand Down Expand Up @@ -76,7 +69,8 @@ object APISamplesInKotlin {
samplesForDoc_NotificatonListener(context)
samplesForDoc_OlderVersions(context)
samplesForDoc_ForcedDecision(context)
samplesForDoc_ODP(context)
samplesForDoc_ODP_async(context)
samplesForDoc_ODP_sync(context)
}

fun samplesForDecide(context: Context) {
Expand Down Expand Up @@ -829,7 +823,7 @@ object APISamplesInKotlin {
success = user.removeAllForcedDecisions()
}

fun samplesForDoc_ODP(context: Context?) {
fun samplesForDoc_ODP_async(context: Context?) {
val optimizelyManager =
OptimizelyManager.builder().withSDKKey("VivZyCGPHY369D4z8T9yG").build(context)
optimizelyManager.initialize(context!!, null) { client: OptimizelyClient ->
Expand All @@ -842,6 +836,22 @@ object APISamplesInKotlin {
}
}

fun samplesForDoc_ODP_sync(context: Context?) {
val optimizelyManager =
OptimizelyManager.builder().withSDKKey("VivZyCGPHY369D4z8T9yG").build(context)

val returnInMainThread = false;

optimizelyManager.initialize(context!!, null, returnInMainThread) { client: OptimizelyClient ->
val userContext = client.createUserContext("user_123")
userContext!!.fetchQualifiedSegments()

Log.d("Optimizely", "[ODP] segments = " + userContext.qualifiedSegments)
val optDecision = userContext.decide("odp-flag-1")
Log.d("Optimizely", "[ODP] decision = $optDecision")
}
}

}


Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.optimizely.ab.android.event_handler.EventRescheduler
import com.optimizely.ab.android.sdk.OptimizelyClient
import com.optimizely.ab.android.sdk.OptimizelyManager
import com.optimizely.ab.android.shared.CountingIdlingResourceManager
import com.optimizely.ab.android.test_app.Samples.APISamplesInJava
import com.optimizely.ab.notification.DecisionNotification
import com.optimizely.ab.notification.TrackNotification
import com.optimizely.ab.notification.UpdateConfigNotification
Expand Down Expand Up @@ -131,4 +132,4 @@ class SplashScreenActivity : AppCompatActivity() {
// The Idling Resource which will be null in production.
private val countingIdlingResourceManager: CountingIdlingResourceManager? = null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
Expand All @@ -39,6 +40,8 @@
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

/**
* Tests for {@link DefaultUserProfileService}
Expand Down Expand Up @@ -101,6 +104,20 @@ public void teardown() {
cache.delete(diskCache.getFileName());
}

@Test
public void startInBackground() throws InterruptedException {
DefaultUserProfileService ups = spy(DefaultUserProfileService.class);

CountDownLatch latch = new CountDownLatch(1);
ups.startInBackground((u) -> {
latch.countDown();
});

latch.await(3, TimeUnit.SECONDS);

verify(ups).start();
}

@Test
public void saveAndStartAndLookup() {
userProfileService.save(userProfileMap1);
Expand Down
Loading

0 comments on commit 8bea0f1

Please sign in to comment.