Skip to content

feat: Add hooks support #300

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 27, 2025
Merged
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
1 change: 1 addition & 0 deletions contract-tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies {
// https://mvnrepository.com/artifact/org.nanohttpd/nanohttpd
implementation("org.nanohttpd:nanohttpd:2.3.1")
implementation("com.google.code.gson:gson:2.8.9")
implementation("com.squareup.okhttp3:okhttp:4.9.2")
implementation(project(":launchdarkly-android-client-sdk"))
// Comment the previous line and uncomment this one to depend on the published artifact:
//implementation("com.launchdarkly:launchdarkly-android-client-sdk:3.1.5")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.launchdarkly.sdktest;

import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

import java.net.URI;

public class HookCallbackService {
private final URI serviceUri;

public HookCallbackService(URI serviceUri) {
this.serviceUri = serviceUri;
}

public void post(Object params) {
RequestBody body = RequestBody.create(
TestService.gson.toJson(params == null ? "{}" : params),
MediaType.parse("application/json")
);
Request request = new Request.Builder().url(serviceUri.toString())
.method("POST", body)
.build();
try (Response response = TestService.client.newCall(request).execute()) {
assertOk(response);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

private void assertOk(Response response) {
if (!response.isSuccessful()) {
String body = "";
if (response.body() != null) {
try {
body = ": " + response.body().string();
} catch (Exception e) {}
}
throw new RuntimeException("HTTP error " + response.code() + " from callback to " + serviceUri + body);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.launchdarkly.sdktest;

import com.google.gson.annotations.SerializedName;
import com.launchdarkly.sdk.EvaluationDetail;
import com.launchdarkly.sdk.EvaluationReason;
import com.launchdarkly.sdk.LDContext;
import com.launchdarkly.sdk.LDValue;
import com.launchdarkly.sdk.android.integrations.EvaluationSeriesContext;
import com.launchdarkly.sdk.android.integrations.TrackSeriesContext;

import java.net.URI;
import java.util.List;
import java.util.Map;

/**
Expand Down Expand Up @@ -33,6 +38,7 @@ public static class SdkConfigParams {
SdkConfigTagParams tags;
SdkConfigClientSideParams clientSide;
SdkConfigServiceEndpointParams serviceEndpoints;
SdkConfigHookParams hooks;
}

public static class SdkConfigStreamParams {
Expand Down Expand Up @@ -74,6 +80,28 @@ public static class SdkConfigClientSideParams {
boolean includeEnvironmentAttributes;
}

public static class SdkConfigHookParams {
List<HookConfig> hooks;
}

public static class HookConfig {
String name;
URI callbackUri;
HookData data;
HookErrors errors;
}

public static class HookData {
Map<String, Object> beforeEvaluation;
Map<String, Object> afterEvaluation;
}

public static class HookErrors {
String beforeEvaluation;
String afterEvaluation;
String afterTrack;
}

public static class CommandParams {
String command;
EvaluateFlagParams evaluate;
Expand Down Expand Up @@ -107,6 +135,18 @@ public static class EvaluateAllFlagsResponse {
Map<String, LDValue> state;
}

public static class EvaluationSeriesCallbackParams {
EvaluationSeriesContext evaluationSeriesContext;
Map<String, Object> evaluationSeriesData;
EvaluationDetail<LDValue> evaluationDetail;
String stage;
}

public static class TrackSeriesCallbackParams {
TrackSeriesContext trackSeriesContext;
String stage;
}

public static class IdentifyEventParams {
LDContext context;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import com.launchdarkly.sdk.android.integrations.ApplicationInfoBuilder;
import com.launchdarkly.sdk.android.integrations.EventProcessorBuilder;
import com.launchdarkly.sdk.android.integrations.Hook;
import com.launchdarkly.sdk.android.integrations.PollingDataSourceBuilder;
import com.launchdarkly.sdk.android.integrations.StreamingDataSourceBuilder;
import com.launchdarkly.sdk.android.integrations.ServiceEndpointsBuilder;
Expand All @@ -31,12 +32,17 @@
import com.launchdarkly.sdktest.Representations.EvaluateAllFlagsResponse;
import com.launchdarkly.sdktest.Representations.EvaluateFlagParams;
import com.launchdarkly.sdktest.Representations.EvaluateFlagResponse;
import com.launchdarkly.sdktest.Representations.HookConfig;
import com.launchdarkly.sdktest.Representations.HookData;
import com.launchdarkly.sdktest.Representations.HookErrors;
import com.launchdarkly.sdktest.Representations.IdentifyEventParams;
import com.launchdarkly.sdktest.Representations.SdkConfigParams;

import android.app.Application;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.Map;
Expand Down Expand Up @@ -336,9 +342,34 @@ private LDConfig buildSdkConfig(SdkConfigParams params, LDLogAdapter logAdapter,
endpoints.events(params.serviceEndpoints.events);
}
}

builder.serviceEndpoints(endpoints);

if (params.hooks != null && params.hooks.hooks != null) {
List<Hook> hookList = new ArrayList<>();
for (HookConfig hookConfig : params.hooks.hooks) {
HookCallbackService callbackService = new HookCallbackService(hookConfig.callbackUri);

HookData data = new HookData();
data.beforeEvaluation = hookConfig.data != null ? hookConfig.data.beforeEvaluation : null;
data.afterEvaluation = hookConfig.data != null ? hookConfig.data.afterEvaluation : null;

HookErrors errors = new HookErrors();
errors.beforeEvaluation = hookConfig.errors != null ? hookConfig.errors.beforeEvaluation : null;
errors.afterEvaluation = hookConfig.errors != null ? hookConfig.errors.afterEvaluation : null;
errors.afterTrack = hookConfig.errors != null ? hookConfig.errors.afterTrack : null;

TestHook testHook = new TestHook(
hookConfig.name,
callbackService,
data,
errors
);

hookList.add(testHook);
}
builder.hooks(Components.hooks().setHooks(hookList));
}

return builder.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.launchdarkly.sdktest;

import com.launchdarkly.sdk.EvaluationDetail;
import com.launchdarkly.sdk.LDValue;
import com.launchdarkly.sdk.android.integrations.EvaluationSeriesContext;
import com.launchdarkly.sdk.android.integrations.Hook;

import com.launchdarkly.sdk.android.integrations.TrackSeriesContext;
import com.launchdarkly.sdktest.Representations.EvaluationSeriesCallbackParams;
import com.launchdarkly.sdktest.Representations.TrackSeriesCallbackParams;
import com.launchdarkly.sdktest.Representations.HookData;
import com.launchdarkly.sdktest.Representations.HookErrors;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class TestHook extends Hook {
private final HookCallbackService callbackService;
private final HookData hookData;
private final HookErrors hookErrors;

public TestHook(String name, HookCallbackService callbackService, HookData data, HookErrors errors) {
super(name);
this.callbackService = callbackService;
this.hookData = data;
this.hookErrors = errors;
}

@Override
public Map<String, Object> beforeEvaluation(EvaluationSeriesContext seriesContext, Map<String, Object> data) {
if (hookErrors.beforeEvaluation != null) {
throw new RuntimeException(hookErrors.beforeEvaluation);
}

EvaluationSeriesCallbackParams params = new EvaluationSeriesCallbackParams();
params.evaluationSeriesContext = seriesContext;
params.evaluationSeriesData = data;
params.stage = "beforeEvaluation";

callbackService.post(params);

Map<String, Object> newData = new HashMap<>(data);
if (hookData.beforeEvaluation != null) {
newData.putAll(hookData.beforeEvaluation);
}

return Collections.unmodifiableMap(newData);
}

@Override
public Map<String, Object> afterEvaluation(EvaluationSeriesContext seriesContext, Map<String, Object> data, EvaluationDetail<LDValue> evaluationDetail) {
if (hookErrors.afterEvaluation != null) {
throw new RuntimeException(hookErrors.afterEvaluation);
}

EvaluationSeriesCallbackParams params = new EvaluationSeriesCallbackParams();
params.evaluationSeriesContext = seriesContext;
params.evaluationSeriesData = data;
params.evaluationDetail = evaluationDetail;
params.stage = "afterEvaluation";

callbackService.post(params);

Map<String, Object> newData = new HashMap<>();
if (hookData.afterEvaluation != null) {
newData.putAll(hookData.afterEvaluation);
}

return Collections.unmodifiableMap(newData);
}

@Override
public void afterTrack(TrackSeriesContext seriesContext) {
if (hookErrors.afterTrack != null) {
throw new RuntimeException(hookErrors.afterTrack);
}

TrackSeriesCallbackParams params = new TrackSeriesCallbackParams();
params.trackSeriesContext = seriesContext;
params.stage = "afterTrack";

callbackService.post(params);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.regex.Pattern;

import fi.iki.elonen.NanoHTTPD;
import okhttp3.OkHttpClient;

public class TestService extends NanoHTTPD {
private static final int PORT = 8001;
Expand All @@ -36,13 +37,17 @@ public class TestService extends NanoHTTPD {
"auto-env-attributes",
"inline-context-all",
"anonymous-redaction",
"client-prereq-events"
"client-prereq-events",
"evaluation-hooks",
"track-hooks"
};
private static final String MIME_JSON = "application/json";
static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(LDGson.typeAdapters())
.create();

static final OkHttpClient client = new OkHttpClient();

private final Router router = new Router();
private final Application application;
private final LDLogAdapter logAdapter;
Expand Down
Loading